一尘不染

SQL Server 2008中的while循环迭代日期范围,然后插入

sql

我有一个带有几列的表,其中一列是Timestamp列。但是当前在此表中没有每天的记录。意思是,在“时间戳记”字段中有1月1日和1月2日的记录,但没有1月3日或1月4日的记录。但是,有记录持续到1月5日和1月6日,依此类推。基本上,周末和其他随机时间都丢失了。

我正在尝试编写一个脚本,该脚本将从StartDate扫描到EndDate(无论我选择的日期范围)是什么,并遍历该日期范围,并且如果该日期范围内的任何日期都不存在记录,则进行迭代,请在“时间戳记”字段中插入具有该特定日期的新记录,但在其余字段中插入空/
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

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

我正在SQL Server Management Studio 2008中编写我的第一个脚本,我认为可能适合中级用户。我是PHP /
MySQL开发人员,对这些技术非常熟悉,但是我是SQL和VBScript的新手。我了解编程的概念和逻辑,但这似乎与我习惯的有很大不同。

我非常感谢所有帮助和事先的见解!


阅读 140

收藏
2021-03-17

共1个答案

一尘不染

SQL是一种基于集合的语言,循环应是最后的选择。因此,基于集合的方法将是首先生成所需的所有日期并将其一次性插入,而不是一次循环插入一次。亚伦·伯特兰(Aaron
Bertrand)写了一系列很棒的文章,介绍如何生成没有循环的集合或序列:

第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的自己的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小提琴上的示例


如果要创建日历表(如链接文章中所述),则可能不必插入这些额外的行,则可以即时生成结果集,例如:

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小提琴上的示例

我不提倡这种方法,只是因为某件事只做一次就并不意味着我不应该证明这样做的正确方法。


进一步说明

由于堆叠的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));

希望这可以表明基于集合的方法不仅效率更高,而且更简单。

2021-03-17