【问题标题】:SQL Server - Duplicate rows and add date iterator column between two date valuesSQL Server - 重复行并在两个日期值之间添加日期迭代器列
【发布时间】:2020-07-30 05:58:49
【问题描述】:

我有一张表,类型为 varchar、varchar、date 和 date:

NAME | ID   | FROM       | THRU
Bob  | A123 | 10/30/2010 | 11/2/2010
Bob  | B567 | 10/30/2010 | 11/2/2010

我想添加一个服务日期 (DOS) 列,该列复制行并在 FROM 和 THRU 日期之间的每一天进行迭代,包括 FROM 和 THRU 日期。完成的表格应如下所示:

NAME | ID   | FROM       | THRU       | DOS
Bob  | A123 | 10/30/2010 | 11/02/2010 | 10/30/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 10/31/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 11/01/2010
Bob  | A123 | 10/30/2010 | 11/02/2010 | 11/02/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 10/30/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 10/31/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 11/01/2010
Bob  | B567 | 10/30/2010 | 11/02/2010 | 11/02/2010

我看到另一个使用 cte 但没有保留原始日期值并添加 DOS 列的答案。我如何在 SQL Server 中完成此操作?

【问题讨论】:

  • 10/30 不是 datetime 的有效值。 datetime 数据类型返回一个精确到 1/300 秒的值,不,好吧,不确定那是什么;月/日(那么,哪一年?),月/年(那么哪一天?)?您拥有的真正价值或真正的数据类型是什么?如果你真的MM/dd这样的格式存储日期,那么当你从一年到另一年时,这将是不可能的。
  • @Larnu 你是对的。我添加了一个年份值以更清晰,并更改了日期时间。
  • 这是一个很好的日历表用例(日历表每个日期都有一行,并且包含所有日期)。网上有很多关于如何快速创建的资源。一旦你有了它,你就可以加入你现有的表,比如SELECT yourtable.*, calendartable.calendardate FROM yourtable WHERE calendartable.calendardate BETWEEN yourtable.FROM and yourtable.THRU

标签: sql sql-server tsql


【解决方案1】:

我认为日历表在这里不是很合适的工具。由于您想要连续日期,因此统计表似乎是一个不错的选择。

首先让我们设置您的数据。

declare @Something table
(
    NAME varchar(10)
    , ID varchar(10)
    , DateFrom date
    , THRU date
)

insert @Something values
('Bob', 'A123', '20101030', '20101102')
, ('Bob', 'B567', '20101030', '20101102')

接下来我们需要我们的计数表。我在我的系统上保留一个视图,它在零读取的情况下快速起泡。随意调整行数以满足您的需要。

create View [dbo].[cteTally] as

WITH
    E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
    E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
    E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
    cteTally(N) AS 
    (
        SELECT  ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
    )
select N from cteTally

现在查询您的情况非常简单。

select s.Name
    , s.ID
    , s.DateFrom
    , s.THRU
    , DOS = DATEADD(day, t.N - 1, DateFrom)
from @Something s
join cteTally t on t.N <= datediff(day, DateFrom, THRU) + 1
order by s.Name
    , s.ID
    , t.N

【讨论】:

  • 我的表有超过 7.5 亿行。对于这么大的表,日历表或计数表会更快吗?
  • 可能没有太大区别。无论哪种方式,您都将 7.5 亿行加入到另一个表中以显着增加行数。
【解决方案2】:

听起来您需要Calendar table。然后它变得像这样简单:

SELECT YT.Name,
       YT.ID,
       YT.[From],
       YT.Thru,
       CT.CalendarDate AS DOS
FROM dbo.YourTable YT
     JOIN dbo.CalendarTable CT ON CONVERT(date,YT.[From]) <= CT.CalendarDate
                              AND CONVERT(date,YT.Thru) >= CT.CalendarDate;

注意,我使用了自己的日历表,没有与链接相同的列(名称),但是,链接提供了如何设计所需的所有信息。您只需要确保使用适合您的表格的列名。

【讨论】:

    【解决方案3】:

    如果您没有日历表(强烈推荐),另一种选择是临时计数表

    示例

    Select A.* 
          ,DOS = B.D
     From  YourTable A
     Cross Apply (
                    Select Top (DateDiff(DAY,[FROM],[THRU])+1) D=DateAdd(DAY,-1+Row_Number() Over (Order By (Select Null)),[FROM]) 
                      From  master..spt_values n1,master..spt_values n2
                 ) B
    

    退货

    NAME    ID      FROM        THRU        DOS
    Bob     A123    2010-10-30  2010-11-02  2010-10-30
    Bob     A123    2010-10-30  2010-11-02  2010-10-31
    Bob     A123    2010-10-30  2010-11-02  2010-11-01
    Bob     A123    2010-10-30  2010-11-02  2010-11-02
    Bob     B567    2010-10-30  2010-11-02  2010-10-30
    Bob     B567    2010-10-30  2010-11-02  2010-10-31
    Bob     B567    2010-10-30  2010-11-02  2010-11-01
    Bob     B567    2010-10-30  2010-11-02  2010-11-02
    

    【讨论】:

    • 看起来和我的很相似。 +1
    • @SeanLange Apples and Oranges :) 你有一个加入,我有一个交叉申请。但是,我敢打赌你的性能会更好。 +1
    • 如果是,可能无法在这个小数据集上检测到。唯一的性能差异在于创建计数表。在我们之间,我们有 4 个或更多“正确”答案中的 2 个。 :)
    • @SeanLange 我们必须承担的负担。
    【解决方案4】:

    我经常将递归 CTE 用于此类事情:

    with cte as (
          select t.ame, t.id, t.from, t.thru, t.from as dos
          from t
          union all
          select cte.ame, cte.id, cte.from, cte.thur, dateadd(day, 1, dos)
          from cte
          where dos < t.thru
         )
    select cte.*
    from cte
    option (maxrecursion 0);
    

    【讨论】:

    • 如果范围很小,没什么大不了的,但对于大范围(大约 1,000),这真的会陷入困境。递归 ctes 生成顺序值实际上是隐藏的 RBAR。 sqlservercentral.com/articles/T-SQL/74118
    • @SeanLange 。 . .问题是当你进入大范围时,joins 也可能需要很长时间。一个很好的起点(在我看来)是 Aaron Bertrand 的测量值(sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1)。我的偏好是因为这是标准 SQL,不需要额外的表,并且适用于任意数量的值。我不会声称这是执行速度最快的方法。
    • 在他的文章中,递归 cte 是他测试过的第二慢的选项。
    • 我从来没有对标准的 ANSI 兼容 sql 给予太多信任。在我从业的 20 年中,我为系统切换数据库的次数恰好为零。即使发生了这种情况,也会有许多其他的事情需要发生。我将递归 cte 技术与视图一起使用,因此它不会存储在任何地方并且速度很快。
    【解决方案5】:

    您可以将服务日期创建为计算列。要增加日期,你可以试试这个:

    SELECT DATEADD(day, 1, '2017/08/25') AS DateAdd;
    

    【讨论】:

    • 这如何回答他们的问题?他们想要 FROM 和 THRU 之间的每个日期。
    • 这如何生成额外的行?像这样的表达式提供了额外的列,而不是额外的行。
    【解决方案6】:

    似乎CROSS APPLY 可以完成这项工作

    CREATE TABLE T(
      [NAME] varchar(3), 
      [ID] varchar(4), 
      [FROM] datetime, 
      [THRU] datetime
    );
    
    INSERT INTO T
        ([NAME], [ID], [FROM], [THRU])
    VALUES
        ('Bob', 'A123', '2001-10-30 00:00:00', '2001-11-02 00:00:00'),
        ('Bob', 'B567', '2001-10-30 00:00:00', '2001-11-02 00:00:00');
    
    SELECT T.*,
           DATEADD(Day, TT.N, [FROM]) DOS
    FROM T CROSS APPLY (VALUES (0), (1), (2), (3)) TT(N)
    

    返回:

    +------+------+---------------------+---------------------+---------------------+
    | NAME |  ID  |        FROM         |        THRU         |         DOS         |
    +------+------+---------------------+---------------------+---------------------+
    | Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 30/10/2001 00:00:00 |
    | Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 31/10/2001 00:00:00 |
    | Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 01/11/2001 00:00:00 |
    | Bob  | A123 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 02/11/2001 00:00:00 |
    | Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 30/10/2001 00:00:00 |
    | Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 31/10/2001 00:00:00 |
    | Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 01/11/2001 00:00:00 |
    | Bob  | B567 | 30/10/2001 00:00:00 | 02/11/2001 00:00:00 | 02/11/2001 00:00:0  |
    +------+------+---------------------+---------------------+---------------------+
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-03-11
      • 2019-05-01
      • 1970-01-01
      • 2017-12-31
      • 2019-06-15
      • 2022-01-01
      • 1970-01-01
      • 2014-11-04
      相关资源
      最近更新 更多