SQL adalah bahasa berbasis set dan loop harus menjadi pilihan terakhir. Jadi pendekatan berbasis set adalah pertama-tama menghasilkan semua tanggal yang Anda butuhkan dan memasukkannya sekaligus, daripada mengulang dan memasukkan satu per satu. Aaron Bertrand telah menulis seri yang bagus tentang menghasilkan satu set atau urutan tanpa loop:
- Buat set atau urutan tanpa loop – bagian 1
- Buat set atau urutan tanpa loop – bagian 2
- Buat set atau urutan tanpa loop – bagian 3
Bagian 3 secara khusus relevan karena berhubungan dengan tanggal.
Dengan asumsi Anda tidak memiliki tabel Kalender, Anda dapat menggunakan metode CTE bertumpuk untuk menghasilkan daftar tanggal antara tanggal mulai dan tanggal akhir Anda.
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;
Saya telah melewatkan beberapa detail tentang cara kerjanya seperti yang tercakup dalam artikel tertaut, pada dasarnya ini dimulai dengan tabel kode keras 10 baris, kemudian bergabung dengan tabel ini dengan dirinya sendiri untuk mendapatkan 100 baris (10 x 10) kemudian bergabung dengan tabel ini dari 100 baris ke dirinya sendiri untuk mendapatkan 10.000 baris (saya berhenti pada titik ini tetapi jika Anda memerlukan baris lebih lanjut, Anda dapat menambahkan gabungan lebih lanjut).
Pada setiap langkah, outputnya adalah satu kolom yang disebut N
dengan nilai 1 (untuk menjaga hal-hal sederhana). Pada saat yang sama dengan mendefinisikan cara menghasilkan 10.000 baris, saya sebenarnya memberi tahu SQL Server untuk hanya menghasilkan angka yang dibutuhkan dengan menggunakan TOP
dan perbedaan antara tanggal mulai dan tanggal akhir Anda - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1)
. Ini menghindari pekerjaan yang tidak perlu. Saya harus menambahkan 1 ke perbedaan untuk memastikan kedua tanggal disertakan.
Menggunakan fungsi peringkat ROW_NUMBER()
Saya menambahkan nomor tambahan ke setiap baris yang dihasilkan, lalu saya menambahkan nomor tambahan ini ke tanggal mulai Anda untuk mendapatkan daftar tanggal. Sejak ROW_NUMBER()
dimulai pada 1, saya perlu mengurangi 1 dari ini untuk memastikan tanggal mulai disertakan.
Maka itu hanya akan menjadi kasus mengecualikan tanggal yang sudah ada menggunakan NOT EXISTS
. Saya telah menyertakan hasil kueri di atas dalam CTE mereka sendiri yang disebut dates
:
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])
Jika Anda ingin membuat tabel kalender (seperti yang dijelaskan dalam artikel tertaut) maka mungkin tidak perlu menyisipkan baris tambahan ini, Anda cukup membuat set hasil dengan cepat, seperti:
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;
TAMBAHKAN
Untuk menjawab pertanyaan Anda yang sebenarnya, loop Anda akan ditulis sebagai berikut:
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
Contoh pada SQL Fiddle
Saya tidak menganjurkan pendekatan ini, hanya karena sesuatu hanya dilakukan sekali bukan berarti saya tidak boleh menunjukkan cara yang benar untuk melakukannya.
Penjelasan LEBIH LANJUT
Karena metode CTE bertumpuk mungkin terlalu rumit dalam pendekatan berbasis himpunan, saya akan menyederhanakannya dengan menggunakan tabel sistem tidak berdokumen master..spt_values
. Jika Anda menjalankan:
SELECT Number
FROM master..spt_values
WHERE Type = 'P';
Anda akan melihat bahwa Anda mendapatkan semua angka dari 0 -2047.
Sekarang jika Anda menjalankan:
DECLARE @StartDate DATE = '2015-01-01',
@EndDate DATE = GETDATE();
SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P';
Anda mendapatkan semua tanggal dari tanggal mulai hingga 2047 hari di masa mendatang. Jika Anda menambahkan lebih lanjut di mana klausa Anda dapat membatasi ini pada tanggal sebelum tanggal akhir Anda:
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;
Sekarang Anda memiliki semua tanggal yang Anda butuhkan dalam satu set kueri berbasis Anda dapat menghilangkan baris yang sudah ada di tabel Anda menggunakan NOT EXISTS
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));
Akhirnya Anda dapat memasukkan tanggal-tanggal ini ke dalam tabel Anda menggunakan INSERT
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));
Semoga ini menunjukkan bahwa pendekatan berbasis himpunan tidak hanya jauh lebih efisien, tetapi juga lebih sederhana.