sql >> Databáze >  >> RDS >> Sqlserver

Zatímco smyčka v SQL Server 2008 iteruje přes časové období a poté INSERT

SQL je jazyk založený na sadě a smyčky by měly být až poslední možností. Přístup založený na sadě by tedy byl nejprve vygenerovat všechna požadovaná data a vložit je najednou, spíše než opakovat a vkládat jedno po druhém. Aaron Bertrand napsal skvělou sérii o generování sady nebo sekvence bez smyček:

Část 3 je zvláště relevantní, protože se zabývá daty.

Za předpokladu, že nemáte tabulku kalendáře, můžete použít metodu skládaného CTE k vygenerování seznamu dat mezi počátečním a koncovým datem.

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;

Přeskočil jsem nějaké podrobnosti o tom, jak to funguje, protože je to popsáno v odkazovaném článku, v podstatě to začíná pevně zakódovanou tabulkou o 10 řádcích, pak tuto tabulku spojí se sebou, aby získalo 100 řádků (10 x 10), pak se připojí k této tabulce 100 řádků k sobě, aby získal 10 000 řádků (v tomto bodě jsem přestal, ale pokud potřebujete další řádky, můžete přidat další spojení).

V každém kroku je výstupem jeden sloupec nazvaný N s hodnotou 1 (aby byly věci jednoduché). Současně s definováním, jak vygenerovat 10 000 řádků, ve skutečnosti říkám SQL Serveru, aby vygeneroval pouze potřebný počet pomocí TOP a rozdíl mezi počátečním a koncovým datem - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1) . Vyhnete se tak zbytečné práci. Musel jsem k rozdílu přidat 1, abych zajistil, že budou zahrnuta obě data.

Pomocí funkce hodnocení ROW_NUMBER() Ke každému vygenerovanému řádku přidám přírůstkové číslo a poté toto přírůstkové číslo přidám k datu zahájení, abych získal seznam dat. Od ROW_NUMBER() začíná 1, musím z toho odečíst 1, aby bylo zahrnuto datum zahájení.

Pak by šlo pouze o případ vyloučení již existujících dat pomocí NOT EXISTS . Výsledky výše uvedeného dotazu jsem přiložil do jejich vlastního CTE nazvaného 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])

Příklad na SQL Fiddle

Pokud byste vytvořili tabulku kalendáře (jak je popsáno v propojených článcích), pak nemusí být nutné vkládat tyto další řádky, můžete jednoduše vygenerovat sadu výsledků za běhu, něco jako:

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;

DODATEK

Chcete-li odpovědět na vaši aktuální otázku, vaše smyčka by byla napsána takto:

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

Příklad na SQL Fiddle

Neobhajuji tento přístup, to, že se něco dělá jen jednou, neznamená, že bych neměl demonstrovat správný způsob, jak to dělat.

DALŠÍ VYSVĚTLENÍ

Protože metoda vrstveného CTE může příliš zkomplikovat přístup založený na množinách, zjednoduším jej pomocí nezdokumentované systémové tabulky master..spt_values . Pokud spustíte:

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

Uvidíte, že získáte všechna čísla od 0 do 2047.

Nyní, když spustíte:

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


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

Získáte všechna data od data zahájení do 2047 dnů v budoucnosti. Pokud přidáte další klauzuli where, můžete to omezit na data před datem ukončení:

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;

Nyní máte všechna potřebná data v jediném dotazu založeném na sadě, můžete odstranit řádky, které již v tabulce existují, pomocí 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));

Nakonec můžete tato data vložit do tabulky 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));

Doufejme, že to do jisté míry ukáže, že přístup založený na množinách je nejen mnohem efektivnější, ale je také jednodušší.



  1. jak uložit video do databáze pomocí mysql?

  2. Jak určit velikost tabulek v Oracle

  3. Poskytovatel Oracle pro Oledb chybí ve VS 2015 Shell

  4. Výstupní parametr uložených procedur MySQL přes ADODB v MS Access (VBA) správný na jednom počítači a náhodný na jiném