Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Sementara loop di SQL Server 2008 iterasi melalui rentang tanggal dan kemudian INSERT

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:

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])

Contoh pada SQL Fiddle

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.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Cara Memformat Nilai Negatif dengan Tanda Kurung di SQL Server (T-SQL)

  2. Kerangka Entitas - Ukuran baris lebih besar dari ukuran baris maksimum yang diizinkan yaitu 8060

  3. Apa yang setara dengan Oracle dari fungsi IsNull() SQL Server?

  4. Mengapa CTE (Common Table Expressions) dalam beberapa kasus memperlambat kueri dibandingkan dengan tabel sementara di SQL Server

  5. Pernyataan SQL IF diabaikan