【问题标题】:SQL how to write a query that return missing date ranges?SQL如何编写返回缺失日期范围的查询?
【发布时间】:2018-07-09 21:39:57
【问题描述】:

我试图弄清楚如何编写一个查询来查看某些记录并查找 today 和 9999-12-31 之间的缺失日期范围。 我的数据如下所示:

ID      |start_dt                   |end_dt                     |prc_or_disc_1
10412   |2018-07-17 00:00:00.000    |2018-07-20 00:00:00.000    |1050.000000
10413   |2018-07-23 00:00:00.000    |2018-07-26 00:00:00.000    |1040.000000

所以对于这些数据,我希望我的查询返回:

2018-07-10 | 2018-07-16
2018-07-21 | 2018-07-22
2018-07-27 | 9999-12-31

我不确定从哪里开始。这可能吗?

【问题讨论】:

  • Bleh,SQL Server 2008
  • 为什么所有答案都被删除了?感谢那些发布答案的人。我发现其中两个解决方案有效。
  • @Ryan 一个答案是使用 SQL Server 2008 上不可用的功能,该问题已被标记。
  • 他们使用的是在 SQL Server 2012 中引入的LEAD()。

标签: sql sql-server sql-server-2014


【解决方案1】:

您可以使用 MS SQL 中的 lag() 函数来做到这一点(但从 2012 年开始可用?)。

 with myData as
    (
      select *, 
      lag(end_dt,1) over (order by start_dt) as lagEnd
      from myTable),
    myMax as
    (
      select Max(end_dt) as maxDate from myTable
    )
    select dateadd(d,1,lagEnd) as StartDate, dateadd(d, -1, start_dt) as EndDate
    from myData
    where lagEnd is not null and dateadd(d,1,lagEnd) < start_dt
    union all
    select dateAdd(d,1,maxDate) as StartDate, cast('99991231' as Datetime) as EndDate 
    from myMax
    where maxDate < '99991231';

如果 lag() 在 MS SQL 2008 中不可用,那么您可以使用 row_number() 和加入来模仿它。

【讨论】:

    【解决方案2】:
    select
        CASE WHEN DATEDIFF(day, end_dt, ISNULL(LEAD(start_dt) over (order by ID), '99991231')) > 1 then end_dt +1 END as F1,
        CASE WHEN DATEDIFF(day, end_dt, ISNULL(LEAD(start_dt) over (order by ID), '99991231')) > 1 then ISNULL(LEAD(start_dt) over (order by ID) - 1, '99991231') END as F2
    from t
    

    工作 SQLFiddle 示例是 -> Here

    2008 版

    SELECT 
        X.end_dt + 1 as F1,
        ISNULL(Y.start_dt-1, '99991231') as F2
    FROM t X
    LEFT JOIN (
    SELECT 
          *
        , (SELECT MAX(ID) FROM t WHERE ID < A.ID) as ID2
    FROM t A) Y ON X.ID = Y.ID2
    WHERE DATEDIFF(day, X.end_dt, ISNULL(Y.start_dt, '99991231')) > 1
    

    工作 SQLFiddle 示例是 -> Here

    【讨论】:

      【解决方案3】:

      这应该在 2008 年有效,它假定您的表格中的范围不重叠。它还将消除当前行的 end_date 比下一行的开始日期早一天的行。

        with dtRanges as (
             select start_dt, end_dt, row_number() over (order by start_dt) as rownum 
             from table1
        )
      
        select t2.end_dt + 1, coalesce(start_dt_next -1,'99991231')
        FROM 
          (  select dr1.start_dt, dr1.end_dt,dr2.start_dt as start_dt_next
             from dtRanges dr1
             left join dtRanges dr2 on dr2.rownum = dr1.rownum + 1
          ) t2
        where 
        t2.end_dt + 1 <>  coalesce(start_dt_next,'99991231')
      

      【讨论】:

        【解决方案4】:

        http://sqlfiddle.com/#!18/65238/1

        SELECT
          *
        FROM
        (
          SELECT
            end_dt+1                            AS start_dt,
            LEAD(start_dt-1, 1, '9999-12-31')
              OVER (ORDER BY start_dt)
                                                AS end_dt
          FROM
            yourTable
        )
          gaps
        WHERE
          gaps.end_dt >= gaps.start_dt
        

        不过,我强烈建议您使用“排他性”的结束日期。也就是说,范围是所有内容,但不包括end_dt。

        这样,一天的范围变成'2018-07-09', '2018-07-10'。

        很明显,我的范围是一天,如果你从另一个中减去一个,你就会得到一天。

        此外,如果您更改为需要小时粒度或分钟粒度您无需更改数据。它只是工作。总是。可靠。直观。

        如果您在网络上搜索,您会发现大量文档说明为什么从软件角度来看,包含开始和排他结束是一个非常的好主意。 (然后,在上面的查询中,您可以删除不可靠的 +1 和 -1。)

        【讨论】:

        • 为此我收到以下错误:将表达式转换为数据类型日期时间的算术溢出错误。
        • 哦,这是因为我的日期之一是 9999-12-31
        • 看起来这行得通,但它没有做的一件事是找到从今天到 9999-12-31 的日期。例如,如果我的第一条记录从我的示例数据中消失了,它将找不到该日期范围。
        • 那是因为你没有要求那个。用完整的具体要求更新您的问题。
        • 也许不清楚,但我说“发现今天和 9999-12-31 之间的日期范围缺失”,如有任何混淆,请见谅。
        【解决方案5】:

        这可以解决您的问题,但如果存在重叠、边缘情况等,请提供一些示例数据。

        在您的结束日期后一天和下一行的开始日期前 1 天服用。

        DECLARE @ TABLE (ID int, start_dt DATETIME, end_dt DATETIME, prc VARCHAR(100))
        
        INSERT INTO @ (id, start_dt, end_dt, prc)
        VALUES 
        (10410,   '2018-07-09 00:00:00.00','2018-07-12 00:00:00.000','1025.000000'),
        (10412,   '2018-07-17 00:00:00.00','2018-07-20 00:00:00.000','1050.000000'),
        (10413,   '2018-07-23 00:00:00.00','2018-07-26 00:00:00.000','1040.000000')
        
        
        SELECT DATEADD(DAY, 1, end_dt)
        , DATEADD(DAY, -1, LEAD(start_dt, 1, '9999-12-31') OVER(ORDER BY id) )
        FROM @
        

        【讨论】:

        • 还返回没有间隙的记录(因此具有负持续时间)。需要过滤掉这些案例。
        【解决方案6】:

        您可能想看看这个: http://sqlfiddle.com/#!18/3a224/1 您只需将开始范围编辑为今天,将结束范围编辑为 9999-12-31。

        【讨论】:

        • 提供的链接没有要运行的 sql,只有模式。另外,您也可以直接在这里发布答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-02-22
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-15
        • 1970-01-01
        相关资源
        最近更新 更多