SQL Server 2008 中的 While 循环遍历日期范围,然后插入

Posted

技术标签:

【中文标题】SQL Server 2008 中的 While 循环遍历日期范围,然后插入【英文标题】:While loop in SQL Server 2008 iterating through a date-range and then INSERT 【发布时间】:2015-03-18 18:14:08 【问题描述】:

我有一个包含几列的表,其中之一是时间戳列。但是目前在这个表中没有每天的记录。这意味着,在时间戳字段中有 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 是全新的。我了解编程概念和逻辑,但这似乎与我习惯的不同。

非常感谢您提前提供的所有帮助和见解!

【问题讨论】:

【参考方案1】:

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

Generate a set or sequence without loops – part 1 Generate a set or sequence without loops – part 2 Generate a set or sequence without loops – part 3

第 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])

Example on 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

Example on 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));

希望这在某种程度上表明基于集合的方法不仅效率更高,而且也更简单。

【讨论】:

这确实有效,如果您查看我发布的 SQL Fiddle 中的结果,您将看到末尾带有 NULL 的所有行。while 循环可能不是您关心的问题,但是如果您要问如何做某事,我认为告诉您最好的方法是正确的,而不是足够的方法。我认为堆叠式 CTE 方法可能令人望而生畏,实际上削弱了基于集合的方法的简单性 - 我已经发布了希望是更简单的演示的内容。【参考方案2】:

我使用的一个简单的解决方案,没有像 GarethD 这样的经验,

将假设您的表格确实有以下数据

DECLARE @t TABLE(Dt Date,Name varchar(10))
INSERT INTO @t VALUES
('2021-05-18','hi'),('2021-05-20','heloo'),('2021-05-25','welocme'),('2021-05-27','goto')



DECLARE @startDate DATE, @endDate DATE
Set @startDate = '2021-05-18'
set @endDate = GETDATE()

;WITH Calender AS (
    SELECT @startDate AS YourDate
    UNION ALL
    SELECT DATEADD(day,1,YourDate) FROM Calender
    WHERE DATEADD(day,1,YourDate) <= @endDate
)
INSERT INTO @t SELECT
    Dt = YourDate,Name = DATENAME (WEEKDAY,YourDate)

FROM Calender c
LEFT JOIN @t t 
ON t.Dt = c.YourDate
WHERE t.dt IS NULL
option (maxrecursion 0)

SELECT * FROM @t ORDER BY dt

【讨论】:

以上是关于SQL Server 2008 中的 While 循环遍历日期范围,然后插入的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server 2008 使用 WHILE LOOP 插入

在 SQL Server 2008 中执行 while 循环

SQL Server 2008 中的 SQL Server 2008 R2 中的 dm_os_volume_stats 等效项是啥?

SQL Server 2008 中的 PIVOT SQL 数据

如何查看sql server 2008的SQL语句执行错误日志

在sql server2008中的日期类型是啥