Sql 使用结果集在条目之间生成日期

Sql 使用结果集在条目之间生成日期,sql,sql-server,sql-server-2008,Sql,Sql Server,Sql Server 2008,我是一个非常基本的SQL用户。我知道基础知识和一些中级技能,但是我在编写这个查询时遇到了麻烦 我有一个名为History的表,其中包含特定数据段的唯一条目。下面是此表的精简版本: +-------------------------+----+-----------+-------+ | Time | ID | ChangeNum | Value | +-------------------------+----+-----------+-------+ |

我是一个非常基本的SQL用户。我知道基础知识和一些中级技能,但是我在编写这个查询时遇到了麻烦

我有一个名为History的表,其中包含特定数据段的唯一条目。下面是此表的精简版本:

+-------------------------+----+-----------+-------+
| Time                    | ID | ChangeNum | Value |
+-------------------------+----+-----------+-------+
| 2014-07-03 00:00:00.000 | 3  | 0         |  A
+-------------------------+----+-----------+-------+
| 2014-10-02 00:00:00.000 | 3  | 1         |  B
+-------------------------+----+-----------+-------+
| 2014-11-27 00:00:00.000 | 3  | 2         |  C
+-------------------------+----+-----------+-------+
| 2015-01-15 00:00:00.000 | 3  | 3         |  D
+-------------------------+----+-----------+-------+
| 2015-02-14 00:00:00.000 | 3  | 4         |  E
+-------------------------+----+-----------+-------+
| 2015-09-02 00:00:00.000 | 3  | 5         |  F
+-------------------------+----+-----------+-------+
| 2015-09-04 00:00:00.000 | 3  | 6         |  G
+-------------------------+----+-----------+-------+
| 2016-09-13 00:00:00.000 | 3  | 7         |  H
+-------------------------+----+-----------+-------+
| 2016-09-14 00:00:00.000 | 3  | 8         |  I 
+-------------------------+----+-----------+-------+
| 2017-02-12 00:00:00.000 | 3  | 9         |  J
+-------------------------+----+-----------+-------+
| 2017-02-18 00:00:00.000 | 3  | 10        |  K
+-------------------------+----+-----------+-------+
我需要做的是创建一个视图,在这些日期范围之间生成数据,同时保持其余值不变。例如,下面是表的一个子集

    +-------------------------+----+-----------+
    | Time                    | ID | ChangeNum |
    +-------------------------+----+-----------+
    | 2014-07-03 00:00:00.000 | 3  | 0         |
    +-------------------------+----+-----------+
    | 2014-07-04 00:00:00.000 | 3  | 0         |
    +-------------------------+----+-----------+
    | 2014-07-05 00:00:00.000 | 3  | 0         |
    +-------------------------+----+-----------+
    | 2014-07-04 00:00:00.000 | 3  | 0         |
    +-------------------------+----+-----------+
    |        truncated for readability ...     |
    +-------------------------+----+-----------+
    | 2014-10-01 00:00:00.000 | 3  | 0         |
    +-------------------------+----+-----------+
    | 2014-10-02 00:00:00.000 | 3  | 1         |
    +-------------------------+----+-----------+

我见过这样的帖子,我可以用CTE生成一个日期范围,这很简单。但是,这涉及到在历史记录表中循环一个结果集,获取日期范围的上下限(第一行的时间字段,然后是下一行的时间字段),然后在这些行之间生成数据。这可能比我想象的要容易,但我有点迷路了。我最初的想法是使用游标,但我不知道在表中落后/领先的情况下如何做到这一点。有什么帮助吗?谢谢。

下面是一个有趣的方法,尽管我根据您的示例数据做了一些假设:

--This is your current table
CREATE TABLE #TEST
(timefield datetime,
id int,
ChangeNum int)

INSERT INTO #TEST (TIMEFIELD, ID, CHANGENUM)
VALUES
('2014-07-03 00:00:00.000', 3, 0),
('2014-10-02 00:00:00.000', 3, 1),
('2014-11-27 00:00:00.000', 3, 2),
('2015-01-15 00:00:00.000', 3, 3),
('2015-02-14 00:00:00.000', 3, 4)

--This is your destination table
CREATE TABLE #TEST2
(timefield datetime,
 id int,
 ChangeNum int)

--This is where we INSERT from your source to destination table
DECLARE @TIMEFIELD datetime = '2014-07-03 00:00:00.000' --Your start date
DECLARE @ChangeNum int = 0 --Starting ChangeNum

WHILE @TIMEFIELD <= '2015-02-14 00:00:00.000' --Your end date
BEGIN
 INSERT INTO #TEST2
 SELECT @TIMEFIELD, 3, @ChangeNum;

 SET @TIMEFIELD = DATEADD(DD, 1, @TIMEFIELD);

IF EXISTS (SELECT * FROM #TEST WHERE TIMEFIELD = @TIMEFIELD)
  BEGIN
   SET @ChangeNum = (SELECT ChangeNum FROM #TEST WHERE TIMEFIELD = @TIMEFIELD)
   --This part can be modified to account for more columns
  END
END

SELECT * FROM #TEST2 --The new table
编辑更改的内容,使其适用于所有ID,即使是不同的日期和更改:

CREATE TABLE #TEST
(timefield datetime,
id int,
ChangeNum int)

INSERT INTO #TEST (TIMEFIELD, ID, CHANGENUM)
VALUES
('2014-07-03 00:00:00.000', 3, 0),
('2014-10-02 00:00:00.000', 3, 1),
('2014-11-27 00:00:00.000', 3, 2),
('2015-01-15 00:00:00.000', 3, 3),
('2015-02-14 00:00:00.000', 3, 4),
('2014-11-27 00:00:00.000', 2, 2),
('2015-01-15 00:00:00.000', 2, 3),
('2015-02-14 00:00:00.000', 2, 4),
('2014-10-02 00:00:00.000', 1, 1),
('2014-11-27 00:00:00.000', 1, 2),
('2015-01-15 00:00:00.000', 1, 3),
('2015-02-14 00:00:00.000', 1, 4)

CREATE TABLE #TEST2
(timefield datetime,
id int,
ChangeNum int)

DECLARE @ID int = (SELECT MIN(ID) FROM #TEST)
DECLARE @ChangeNum int = (SELECT MIN(ChangeNum) FROM #TEST WHERE @ID = ID)
DECLARE @TIMEFIELD datetime = (SELECT MIN(TIMEFIELD) FROM #TEST WHERE @ID = ID)

WHILE @ID <= (SELECT MAX(ID) FROM #TEST)
BEGIN

WHILE @TIMEFIELD <= (SELECT MAX(TIMEFIELD) FROM #TEST WHERE ID = @ID)
BEGIN
   INSERT INTO #TEST2
   SELECT @TIMEFIELD, @ID, @ChangeNum
   SET @TIMEFIELD = DATEADD(DD, 1, @TIMEFIELD)

   IF EXISTS (SELECT * FROM #TEST WHERE TIMEFIELD = @TIMEFIELD AND ID = @ID)
   BEGIN
    SET @ChangeNum = (SELECT ChangeNum FROM #TEST WHERE TIMEFIELD = @TIMEFIELD AND ID = @ID);
   END

  END

  IF EXISTS (SELECT MIN(ID) FROM #TEST WHERE ID > @ID)
   BEGIN
    SET @ID = (SELECT MIN(ID) FROM #TEST WHERE ID > @ID)
    SET @ChangeNum = (SELECT MIN(ChangeNum) FROM #TEST WHERE @ID = ID)
    SET @TIMEFIELD = (SELECT MIN(TIMEFIELD) FROM #TEST WHERE @ID = ID)
   END
END

SELECT * FROM #TEST2

这很有趣。正如肖恩所说,你需要一个理货表,或者用我的话来说是一个日期表。这并没有考虑到如果在同一天进行两次更改会发生什么。我还缩短了时间框架,使测试更容易。这并不说明返回了多个ID

CREATE TABLE #test (ID INT, ChangeNum INT, [Value] varchar(1), [Time] datetime);

    DECLARE @StartDate datetime
        , @CutoffDate datetime;

INSERT INTO #test ([time], ID, ChangeNum, [Value])
VALUES 
('2011-07-03 00:00:00.000',  3, 0, 'A'),
('2011-07-10 00:00:00.000',  3, 1, 'B'),
('2011-07-15 00:00:00.000',  3, 2, 'C'),    
('2011-07-01 00:00:00.000',  2, 0, 'Q'),
('2011-07-06 00:00:00.000',  2, 1, 'R'),
('2011-08-03 00:00:00.000',  2, 2, 'S');    

    SELECT @StartDate = MIN([Time])
        ,@CutoffDate = MAX([time])
    FROM #test;

WITH Dates
AS (SELECT d
    FROM (
        SELECT d = DATEADD(DAY, rn - 1, @StartDate)
        FROM (SELECT TOP (DATEDIFF(DAY, @StartDate, @CutoffDate)) rn = ROW_NUMBER() OVER (
                    ORDER BY s1.[object_id])
            FROM sys.all_objects AS s1
            CROSS JOIN sys.all_objects AS s2
            ORDER BY s1.[object_id]
            ) AS x
        ) AS y
    )
     ,ChangeRanges
    AS (
        SELECT ID
            , [Time] BEGIN_DATE
            , CASE WHEN LEAD ([Time], 1,0) OVER (PARTITION BY ID ORDER BY ID, [time]) = '1900-01-01 00:00:00.000' THEN [Time] 
                    ELSE DATEADD(DAY, -1, LEAD ([Time], 1,0) OVER (PARTITION BY ID ORDER BY ID, [time])) END END_DATE 
            , ChangeNum
        FROM #test
)       
    SELECT d.d [DATE]
            ,cr.ID
            ,cr.ChangeNum
    FROM ChangeRanges cr
    JOIN Dates d ON d.d >= cr.BEGIN_DATE AND d.d <= cr.END_DATE
    WHERE ID = 3
    GROUP BY cr.ID, d.d, cr.ChangeNum
    ORDER BY cr.ID, d.d

DROP TABLE #test

您肯定不需要光标来执行此操作。你需要一个理货台。这两个答案都使用理货台,我从来没有听说过。这可以通过使用常规SQL语法(例如不使用变量或表创建)来实现吗?此外,这不是一个很好的“缺口和孤岛”解决方案的候选方案,还是我弄错了?@psrpsrpsr我的答案不使用理货表,但我认为没有变量或理货表就无法做到这一点。你可能需要一个或另一个来生成丢失的日期记录。只是澄清我的答案并不需要理货表,这不是绝对必要的。我同意@AaronDietz。理货台不是绝对必要的。有太多的情况下,循环解决方案因为太麻烦或无法工作,例如报告。如果你只需要SSMS中的列表,循环就可以了。我也没有注意到你在2008年。我的答案在那个版本中不起作用。我要说的是,在顶部标记您的答案SQL Server 2012+选项,如果其他人碰巧路过,这仍然是一个简洁而有用的答案。太棒了-这相当直接。我根据我的环境的具体情况修改了它——需要某些表和变量,我特定的SQL引擎语法,等等——但这很有魅力。谢谢@dxh9845很高兴听到这个消息,这是一个有趣的事情!我不确定你现在是否需要这个,但我不喜欢它是为ID=3硬编码的,而且几乎不可伸缩,所以我编辑了一个新的答案,它将适用于每个ID/ChangeNum/Date组合。老实说,非常有趣-我让它在循环中工作,但你比我快。谢谢