Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Докато цикъл в SQL Server 2008 итерация през диапазон от дати и след това INSERT

SQL е базиран на набор език и циклите трябва да са последна мярка. Така че базираният на набор подход би бил първо да генерирате всички дати, от които се нуждаете, и да ги вмъкнете наведнъж, вместо да правите цикли и да вмъквате една по една. Арън Бертран е написал страхотна поредица за генериране на набор или последователност без цикли:

Част 3 е особено подходяща, тъй като се занимава с дати.

Ако приемем, че нямате календарна таблица, можете да използвате подредения CTE метод, за да генерирате списък с дати между началната и крайната ви дата.

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;

Пропуснах някои подробности за това как работи това, тъй като е описано в свързаната статия, по същество започва с твърдо кодирана таблица от 10 реда, след това се присъединява към тази таблица със себе си, за да получи 100 реда (10 x 10), след което се присъединява към тази таблица от 100 реда до себе си, за да получите 10 000 реда (спрях на този етап, но ако имате нужда от допълнителни редове, можете да добавите допълнителни съединения).

На всяка стъпка изходът е една колона, наречена N със стойност 1 (за да опростим нещата). В същото време, когато определям как да генерирам 10 000 реда, всъщност казвам на SQL Server да генерира само необходимия брой, като използва TOP и разликата между вашата начална и крайна дата - TOP(DATEDIFF(DAY, @StartDate, @EndDate) + 1) . Така се избягва ненужната работа. Трябваше да добавя 1 към разликата, за да съм сигурен, че и двете дати са включени.

Използване на функцията за класиране ROW_NUMBER() Добавям нарастващо число към всеки от генерираните редове, след което добавям това нарастващо число към вашата начална дата, за да получа списъка с дати. От ROW_NUMBER() започва от 1, трябва да извадя 1 от това, за да се уверя, че началната дата е включена.

Тогава би било просто случай на изключване на дати, които вече съществуват, като се използва NOT EXISTS . Приложих резултатите от горната заявка в техния собствен CTE, наречен 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])

Пример за SQL Fiddle

Ако трябваше да създадете календарна таблица (както е описано в свързаните статии), тогава може да не е необходимо да вмъквате тези допълнителни редове, можете просто да генерирате своя набор от резултати в движение, нещо като:

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;

ДОБАВЛЕНИЕ

За да отговорите на действителния ви въпрос, цикълът ви ще бъде написан по следния начин:

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

Пример за SQL Fiddle

Не подкрепям този подход, само защото нещо се прави само веднъж, не означава, че не трябва да демонстрирам правилния начин да го направя.

ДОПЪЛНИТЕЛНО ОБЯСНЕНИЕ

Тъй като подреденият CTE метод може да е усложнил прекалено подхода, базиран на множество, ще го опростя, като използвам недокументираната системна таблица master..spt_values . Ако стартирате:

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

Ще видите, че получавате всички числа от 0 -2047.

Сега, ако стартирате:

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


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

Получавате всички дати от вашата начална дата до 2047 дни в бъдещето. Ако добавите допълнителна клауза where, можете да ограничите това до дати преди вашата крайна дата:

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;

Сега имате всички дати, от които се нуждаете в една заявка, базирана на набор, можете да елиминирате редовете, които вече съществуват във вашата таблица, като използвате 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));

Накрая можете да вмъкнете тези дати във вашата таблица с помощта на 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));

Надяваме се, че това донякъде ще покаже, че подходът, базиран на набори, е не само много по-ефективен, но е и по-прост.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Поведение при кръстосано свързване (SQLServer 2008)

  2. ExecuteScalar срещу ExecuteNonQuery при връщане на стойност на идентичност

  3. запишете данни в DB

  4. Какво е „идентификатор от няколко части“ и защо не може да бъде обвързан?

  5. Невъзможност за отдалечен достъп до екземпляр на SQL Server 2008 R2