SQL ist eine mengenbasierte Sprache und Schleifen sollten der letzte Ausweg sein. Der satzbasierte Ansatz wäre also, zuerst alle benötigten Daten zu generieren und sie auf einmal einzufügen, anstatt sie einzeln zu wiederholen und einzufügen. Aaron Bertrand hat eine großartige Serie über das Generieren eines Sets oder einer Sequenz ohne Loops geschrieben:
- Einen Satz oder eine Sequenz ohne Schleifen generieren – Teil 1
- Einen Satz oder eine Sequenz ohne Schleifen generieren – Teil 2
- Einen Satz oder eine Sequenz ohne Schleifen generieren – Teil 3
Teil 3 ist besonders relevant, da es um Daten geht.
Angenommen, Sie haben keine Kalendertabelle, können Sie die gestapelte CTE-Methode verwenden, um eine Liste mit Daten zwischen Ihrem Start- und Enddatum zu generieren.
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2)
SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N) - 1, @StartDate)
FROM N3;
Ich habe einige Details darüber übersprungen, wie dies funktioniert, da es im verlinkten Artikel behandelt wird. Im Wesentlichen beginnt es mit einer fest codierten Tabelle mit 10 Zeilen, verbindet diese Tabelle dann mit sich selbst, um 100 Zeilen (10 x 10) zu erhalten, und tritt dann dieser Tabelle bei von 100 Zeilen an sich selbst, um 10.000 Zeilen zu erhalten (ich habe an dieser Stelle aufgehört, aber wenn Sie weitere Zeilen benötigen, können Sie weitere Joins hinzufügen).
Bei jedem Schritt ist die Ausgabe eine einzelne Spalte namens N
mit einem Wert von 1 (um die Dinge einfach zu halten). Während ich definiere, wie 10.000 Zeilen generiert werden sollen, weise ich SQL Server tatsächlich an, nur die benötigte Anzahl zu generieren, indem ich TOP
verwende und die Differenz zwischen Ihrem Start- und Enddatum - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1)
. Das vermeidet unnötige Arbeit. Ich musste 1 zur Differenz addieren, um sicherzustellen, dass beide Daten enthalten waren.
Verwenden der Ranking-Funktion ROW_NUMBER()
Ich füge jeder der generierten Zeilen eine inkrementelle Nummer hinzu, dann füge ich diese inkrementelle Nummer zu Ihrem Startdatum hinzu, um die Liste der Daten zu erhalten. Seit ROW_NUMBER()
beginnt bei 1, ich muss 1 davon abziehen, um sicherzustellen, dass das Startdatum enthalten ist.
Dann müssten nur bereits existierende Daten mit NOT EXISTS
ausgeschlossen werden . Ich habe die Ergebnisse der obigen Abfrage in einen eigenen CTE namens dates
eingeschlossen :
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
WITH N1 (N) AS (SELECT 1 FROM (VALUES (1), (1), (1), (1), (1), (1), (1), (1), (1), (1)) n (N)),
N2 (N) AS (SELECT 1 FROM N1 AS N1 CROSS JOIN N1 AS N2),
N3 (N) AS (SELECT 1 FROM N2 AS N1 CROSS JOIN N2 AS N2),
Dates AS
( SELECT TOP (DATEDIFF(DAY, @StartDate, @EndDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY N) - 1, @StartDate)
FROM N3
)
INSERT INTO MyTable ([TimeStamp])
SELECT Date
FROM Dates AS d
WHERE NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE d.Date = t.[TimeStamp])
Wenn Sie eine Kalendertabelle erstellen (wie in den verlinkten Artikeln beschrieben), ist es möglicherweise nicht erforderlich, diese zusätzlichen Zeilen einzufügen. Sie können Ihre Ergebnismenge einfach spontan generieren, etwa so:
SELECT [Timestamp] = c.Date,
t.[FruitType],
t.[NumOffered],
t.[NumTaken],
t.[NumAbandoned],
t.[NumSpoiled]
FROM dbo.Calendar AS c
LEFT JOIN dbo.MyTable AS t
ON t.[Timestamp] = c.[Date]
WHERE c.Date >= @StartDate
AND c.Date < @EndDate;
NACHTRAG
Um Ihre eigentliche Frage zu beantworten, würde Ihre Schleife wie folgt geschrieben:
DECLARE @StartDate AS DATETIME
DECLARE @EndDate AS DATETIME
DECLARE @CurrentDate AS DATETIME
SET @StartDate = '2015-01-01'
SET @EndDate = GETDATE()
SET @CurrentDate = @StartDate
WHILE (@CurrentDate < @EndDate)
BEGIN
IF NOT EXISTS (SELECT 1 FROM myTable WHERE myTable.Timestamp = @CurrentDate)
BEGIN
INSERT INTO MyTable ([Timestamp])
VALUES (@CurrentDate);
END
SET @CurrentDate = DATEADD(DAY, 1, @CurrentDate); /*increment current date*/
END
Beispiel für SQL Fiddle
Ich befürworte diese Vorgehensweise nicht, nur weil etwas nur einmal gemacht wird, heißt das nicht, dass ich nicht die richtige Vorgehensweise demonstrieren sollte.
WEITERE ERLÄUTERUNGEN
Da die gestapelte CTE-Methode den mengenbasierten Ansatz möglicherweise zu kompliziert gemacht hat, werde ich ihn vereinfachen, indem ich die undokumentierte Systemtabelle master..spt_values
verwende . Wenn Sie ausführen:
SELECT Number
FROM master..spt_values
WHERE Type = 'P';
Sie werden sehen, dass Sie alle Zahlen von 0 -2047 erhalten.
Wenn Sie jetzt Folgendes ausführen:
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P';
Sie erhalten alle Daten von Ihrem Startdatum bis 2047 Tage in der Zukunft. Wenn Sie eine weitere Where-Klausel hinzufügen, können Sie diese auf Daten vor Ihrem Enddatum beschränken:
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate;
Jetzt haben Sie alle Daten, die Sie benötigen, in einer einzigen satzbasierten Abfrage. Sie können die Zeilen, die bereits in Ihrer Tabelle vorhanden sind, mit NOT EXISTS
entfernen
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate
AND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[Timestamp] = DATEADD(DAY, number, @StartDate));
Abschließend können Sie diese Daten mit INSERT
in Ihre Tabelle einfügen
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
INSERT YourTable ([Timestamp])
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P'
AND DATEADD(DAY, number, @StartDate) <= @EndDate
AND NOT EXISTS (SELECT 1 FROM MyTable AS t WHERE t.[Timestamp] = DATEADD(DAY, number, @StartDate));
Hoffentlich zeigt dies in gewisser Weise, dass der satzbasierte Ansatz nicht nur viel effizienter, sondern auch einfacher ist.