而SQL Server 2008中的循环遍历日期范围然后INSERT

Arm*_*min 11 sql sql-server-2008

我有一个包含几列的表,其中一列是Timestamp列.但目前在这张表中并没有每天的记录.意思是,有1月1日和1月2日的记录,但在时间戳字段中没有1月3日或1月4日的记录.但是,有记录在1月5日和1月6日继续,依此类推.基本上,周末和其他随机日子都缺失了.

我正在尝试编写一个脚本,将该表从StartDate扫描到EndDate(我选择的任何日期范围),并迭代这个日期范围,并且如果该日期范围内的任何日期都不存在记录,在Timestamp字段中插入具有该特定日期的新记录,但为其余字段插入空/ NULL数据.

这是我到目前为止的伪代码,我认为这是正确的方法:

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
    SELECT * FROM myTable WHERE myTable.Timestamp = "@CurrentDate"
    IF @@ROWCOUNT < 1
        print @CurrentDate
        /*insert a new row query here*/

    SET @CurrentDate = convert(varchar(30), dateadd(day,1, @CurrentDate), 101); /*increment current date*/
END
Run Code Online (Sandbox Code Playgroud)

这是SQLFiddle - http://sqlfiddle.com/#!6/06c73/1

我正在SQL Server Management Studio 2008中编写我的第一个脚本,我认为可能是中级用户的一些东西.我是一名PHP/MySQL开发人员,对这些技术非常熟悉,但我是SQL和VBScript的新手.我理解编程概念和逻辑,但这似乎与我以前的不同.

我非常感谢所有的帮助和洞察力!

Gar*_*thD 20

SQL是一种基于集合的语言,循环应该是最后的手段.所以基于集合的方法是首先生成所需的所有日期并一次性插入它们,而不是一次循环插入一个.Aaron Bertrand撰写了一篇关于生成没有循环的集合或序列的精彩系列:

第3部分特别相关,因为它涉及日期.

假设您没有Calendar表,则可以使用堆叠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;
Run Code Online (Sandbox Code Playgroud)

我已经跳过了一些关于它是如何工作的细节,因为它在链接文章中有所涉及,实质上它以10行的硬编码表开始,然后将此表与自身连接以获得100行(10 x 10)然后加入此表100行自己获得10,000行(此时我停止了,但如果你需要更多的行,你可以添加更多的连接).

在每一步中,输出都是一个名为N1 的单个列(为了简单起见).在定义如何生成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])
Run Code Online (Sandbox Code Playgroud)

关于SQL小提琴的例子


如果您要创建一个日历表(如链接文章中所述),则可能没有必要插入这些额外的行,您可以动态生成结果集,例如:

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;
Run Code Online (Sandbox Code Playgroud)

附录

要回答您的实际问题,您的循环将按如下方式编写:

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
Run Code Online (Sandbox Code Playgroud)

关于SQL小提琴的例子

我并不主张这种做法,只是因为有些事情只是做了一次并不意味着我不应该表现出正确的做法.


进一步解释

由于堆叠CTE方法可能使基于集合的方法过于复杂,因此我将通过使用未记录的系统表来简化它master..spt_values.如果您运行:

SELECT Number
FROM master..spt_values
WHERE Type = 'P';
Run Code Online (Sandbox Code Playgroud)

您将看到从0到-2047获得所有数字.

现在,如果您运行:

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


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

您将获得从开始日期到将来的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;
Run Code Online (Sandbox Code Playgroud)

现在,您可以在基于单个集的查询中获得所需的所有日期,这样您就可以消除表中已存在的行 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));
Run Code Online (Sandbox Code Playgroud)

最后,您可以使用将这些日期插入表中 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));
Run Code Online (Sandbox Code Playgroud)

希望这可以通过某种方式表明基于集合的方法不仅更有效,而且更简单.