SQL如何编写返回缺失日期范围的查询?

Posted

技术标签:

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

我试图弄清楚如何编写一个查询来查看某些记录并查找 today9999-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

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

【问题讨论】:

差距和岛屿。 dba.stackexchange.com/questions/167068/… Bleh,SQL Server 2008 为什么所有答案都被删除了?感谢那些发布答案的人。我发现其中两个解决方案有效。 @Ryan 一个答案是使用 SQL Server 2008 上不可用的功能,该问题已被标记。 他们使用的是在 SQL Server 2012 中引入的LEAD() 【参考方案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,只有模式。另外,您也可以直接在这里发布答案。

以上是关于SQL如何编写返回缺失日期范围的查询?的主要内容,如果未能解决你的问题,请参考以下文章

如何编写按日期范围和维度成员值进行切片的 mdx 查询

用于显示多个日期范围之间的间隔的 SQL 查询

如何从日期范围查询中查找表中的一组缺失日期

您如何编写一条SQL语句来为日期值创建新列,然后在WHERE子句中查询它

如何编写此 LINQ 查询的 SQL 版本?

如何在 PostgreSQL 中合并两个查询?