SQL如何编写返回缺失日期范围的查询?
Posted
技术标签:
【中文标题】SQL如何编写返回缺失日期范围的查询?【英文标题】:SQL how to write a query that return missing date ranges? 【发布时间】: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
我不确定从哪里开始。这可能吗?
【问题讨论】:
差距和岛屿。 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如何编写返回缺失日期范围的查询?的主要内容,如果未能解决你的问题,请参考以下文章