Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Podczas gdy pętla w SQL Server 2008 iteruje przez zakres dat, a następnie INSERT

SQL jest językiem opartym na zbiorach, a pętle powinny być ostatecznością. Tak więc podejście oparte na zestawach polegałoby na tym, aby najpierw wygenerować wszystkie wymagane daty i wstawić je za jednym razem, a nie zapętlać i wstawiać pojedynczo. Aaron Bertrand napisał świetną serię na temat generowania zestawu lub sekwencji bez pętli:

Część 3 jest szczególnie istotna, ponieważ dotyczy dat.

Zakładając, że nie masz tabeli Kalendarz, możesz użyć skumulowanej metody CTE, aby wygenerować listę dat między datą początkową a końcową.

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;

Pominąłem trochę szczegółów na temat tego, jak to działa, ponieważ jest to omówione w połączonym artykule, w istocie zaczyna się od zakodowanej na sztywno tabeli z 10 wierszami, a następnie łączy się z tą tabelą, aby uzyskać 100 wierszy (10 x 10), a następnie dołącza do tej tabeli 100 wierszy do siebie, aby uzyskać 10 000 wierszy (zatrzymałem się w tym momencie, ale jeśli potrzebujesz kolejnych wierszy, możesz dodać kolejne sprzężenia).

Na każdym kroku wyjściem jest pojedyncza kolumna o nazwie N o wartości 1 (aby wszystko było proste). W tym samym czasie, gdy definiuję sposób generowania 10 000 wierszy, tak naprawdę mówię SQL Server, aby generował tylko potrzebną liczbę za pomocą TOP oraz różnica między datą rozpoczęcia i zakończenia — TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1) . Pozwala to uniknąć niepotrzebnej pracy. Musiałem dodać 1 do różnicy, aby upewnić się, że obie daty zostały uwzględnione.

Korzystanie z funkcji rankingu ROW_NUMBER() Dodaję kolejny numer do każdego z wygenerowanych wierszy, a następnie dodaję ten kolejny numer do daty rozpoczęcia, aby uzyskać listę dat. Od ROW_NUMBER() zaczyna się od 1, muszę od tego odjąć 1, aby mieć pewność, że uwzględniono datę rozpoczęcia.

Wtedy byłby to tylko przypadek wykluczenia dat, które już istnieją za pomocą NOT EXISTS . Umieściłem wyniki powyższego zapytania w ich własnym CTE o nazwie 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])

Przykład SQL Fiddle

Jeśli utworzysz tabelę kalendarza (zgodnie z opisem w powiązanych artykułach), wstawianie tych dodatkowych wierszy może nie być konieczne, możesz po prostu wygenerować zestaw wyników w locie, na przykład:

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;

UZUPEŁNIENIE

Aby odpowiedzieć na twoje rzeczywiste pytanie, twoja pętla zostałaby napisana w następujący sposób:

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

Przykład SQL Fiddle

Nie opowiadam się za takim podejściem, tylko dlatego, że coś robi się tylko raz, nie oznacza, że ​​nie powinienem demonstrować prawidłowego sposobu robienia tego.

DALSZE WYJAŚNIENIE

Ponieważ skumulowana metoda CTE mogła nadmiernie skomplikować podejście oparte na zbiorach, uprości je, używając nieudokumentowanej tabeli systemowej master..spt_values . Jeśli biegasz:

SELECT Number
FROM master..spt_values
WHERE Type = 'P';

Zobaczysz, że otrzymujesz wszystkie liczby od 0 -2047.

Teraz, jeśli biegniesz:

DECLARE @StartDate DATE = '2015-01-01',
        @EndDate DATE = GETDATE();


SELECT Date = DATEADD(DAY, number, @StartDate)
FROM master..spt_values
WHERE type = 'P';

Otrzymasz wszystkie daty od daty rozpoczęcia do 2047 dni w przyszłości. Jeśli dodasz kolejną klauzulę gdzie, możesz ograniczyć ją do dat przed datą końcową:

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;

Teraz masz wszystkie potrzebne daty w pojedynczym zapytaniu opartym na zestawie, możesz wyeliminować wiersze, które już istnieją w tabeli, używając 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));

Na koniec możesz wstawić te daty do swojej tabeli za pomocą 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));

Mam nadzieję, że w pewien sposób pokazuje to, że podejście oparte na zbiorach jest nie tylko znacznie wydajniejsze, ale także prostsze.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak dostać 14 dni przed podanym terminem unikając świąt

  2. Ulepszenia Service Broker w SQL Server 2016

  3. SQL Server:INNER JOIN po UNION prowadzi do powolnego dopasowania skrótu (agregacja)

  4. Jak kopiować bazy danych SQL Server z jednego wystąpienia do drugiego?

  5. T-SQL używający SUM do sumy bieżącej