我有一个系统,每小时在同一分钟内插入寄存器,例如:
DateTime Value
2023/05/01 06:14 10
2023/05/01 06:29 15
2023/05/01 06:44 21
2023/05/01 06:59 12
所以我想做一个查询,返回给定范围内可能丢失的记录。
DateTime Value
2023/05/01 06:14 10
2023/05/01 06:29 15
2023/05/01 06:44 21
2023/05/01 06:59 12
2023/05/01 07:29 10
2023/05/01 07:44 17
2023/05/01 08:14 20
在此记录中,我希望查询返回我:
DateTime
2023/05/01 07:14
2023/05/01 07:59
我读过一些类似问题的帖子:Find Missing Dates in Data
但我找不到解决方案。
有什么想法吗?非常感谢!
你必须根据分钟设置间隔(我设置为15分钟)
我根据你的数据得到了开始日期和结束日期,你也可以设置
DECLARE @StartDate DATETIME
DECLARE @EndDate DATETIME
DECLARE @interval int=15
SET @StartDate =(select min(_DateTime) from #test)
SET @EndDate = (select max(_DateTime) from #test)
;WITH Dates(Date) AS
(
SELECT DATEADD(MINUTE, @interval, @StartDate) AS Date
UNION ALL
SELECT DATEADD(MINUTE, @interval, Date) AS Date
FROM Dates
WHERE Date < @EndDate
)
SELECT a.Date
FROM Dates a
left join #test b on a.Date=b._DateTime
where b._DateTime is null
option (maxrecursion 0)
结果: |日期| |--| |2023-05-01 07:14:00.000| |2023-05-01 07:59:00.000|
基础数据:
create table #test(_DateTime DateTime,_value int)
insert into #test(_DateTime,_value) values('2023/05/01 06:14', 10)
insert into #test(_DateTime,_value) values('2023/05/01 06:29', 15)
insert into #test(_DateTime,_value) values('2023/05/01 06:44', 21)
insert into #test(_DateTime,_value) values('2023/05/01 06:59', 12)
insert into #test(_DateTime,_value) values('2023/05/01 07:29', 10)
insert into #test(_DateTime,_value) values('2023/05/01 07:44', 17)
insert into #test(_DateTime,_value) values('2023/05/01 08:14', 20)
这是一个解决方案。我假设两天之间的时差不超过 24 小时,如果不是这样,您需要增加 CTE 值:
DECLARE @times TABLE (
datetime datetime, value int)
INSERT INTO @times
SELECT datetime, value
FROM (
VALUES ('2023/05/01 06:14',10)
, ('2023/05/01 06:29',15)
, ('2023/05/01 06:44',21)
, ('2023/05/01 06:59',12)
, ('2023/05/01 07:29',10)
, ('2023/05/01 07:44',17)
, ('2023/05/01 08:14',20)
, ('2023/05/01 18:14',20)
) t (datetime, value)
;WITH cte AS (
SELECT ROW_NUMBER() OVER(ORDER BY @@spid) AS row
FROM (VALUES(1),(1),(1),(1)) x(v)
CROSS apply (VALUES(1),(1),(1),(1),(1),(1)) y(y)
)
SELECT prevDate, missingDate, datetime
FROM (
SELECT DATEDIFF(HOUR, LAG(datetime) OVER(ORDER BY datetime), datetime) AS diff
, LAG(datetime) OVER(ORDER BY datetime) AS prevDate
, *
FROM @times
) z
CROSS APPLY (
SELECT missingDate
, c.*
FROM cte c
CROSS apply (VALUES(14),(29),(44),(59)) m(m)
CROSS apply (
SELECT DATEADD(hour, c.row-1, DATEADD(minute, m.m, DATEADD(MINUTE, -DATEPART(MINUTE, z.prevDate), z.prevDate))) AS missingDate
) sd
WHERE c.row BETWEEN 1 AND diff + 1
AND missingDate > prevDate
AND missingDate < datetime
) q
ORDER BY datetime
这样做是获取每个日期时间与其先前值之间的差距,然后在一系列 CTE 的帮助下生成这两天之间缺失时间的列表,以及固定时间列表 ((14),(29) ,(44),(59)).
DATEADD(hour, c.row-1, DATEADD(minute, m.m, DATEADD(MINUTE, -DATEPART(MINUTE, z.prevDate), z.prevDate)))
这部分通过从前一个日期中删除分钟来生成缺失的日期,然后在两个日期之间添加小时和固定分钟。
根据我的理解,每个条目之间应该有 15 分钟的时间间隔。即 07:14-07:29 和 07:59-08:14 的时间范围就是这种情况,其中 07:14 和 07:59 被指定为缺失的时间范围。
实现这一点的有效方法是首先识别每个后续时间戳之间的时间间隔差异。这可以在子查询中使用 LEAD 函数来完成。
为了便于说明,让我们考虑以下示例。假设有一列日期如下:
现在,让我们假设一个人希望在存在大于 15 分钟的时间间隔的日期上增加 15 分钟。
这可以通过以下方式完成:
SELECT date, next_date, next_date - date as difference, date + interval '15' minute from (select date, LEAD(date, 1)
OVER (ORDER BY date)
AS next_date from table) as subquery where next_date-date>'15 minutes';
在此表中,最后一列在原始时间戳上增加了 15 分钟——但不显示那些间隔为 15 分钟或更短的时间戳。
因此,可以选择仅显示此列以显示所需的时间戳原始时间戳 + 15 分钟,但仅限于时间戳之间的差异大于 15 分钟。
生成所有可能的时间,然后对您的表进行左连接:
DECLARE @start_time DATETIME = '2023-05-01 06:14';
DECLARE @end_time DATETIME = '2023-05-01 08:14'
DECLARE @interval_in_sec INT = 60*15;
WITH all_times AS -- using row_number from the [master]..spt_values table to generate all possible times.
(
SELECT TOP (DATEDIFF(SECOND, @start_time, @end_time) / @interval_in_sec)
DATEADD(SECOND, ROW_NUMBER() OVER (ORDER BY number) * @interval_in_sec, @start_time) AS dt
FROM [master]..spt_values -- system tables can be changed by the vendor without pre-warning, it would be better to create your own numbers table
)
SELECT TS.dt AS Date_Time
FROM all_times TS LEFT JOIN table_name TB
ON TS.dt = TB.Date_Time
WHERE TB.Date_Time IS NULL
ORDER BY TS.dt
在SQL Server 2022中,我们可以使用
GENERATE_SERIES
函数:
WITH all_times AS
(
SELECT
DATEADD(SECOND, value * @interval_in_sec, @start_time) AS dt
FROM GENERATE_SERIES(0,DATEDIFF(SECOND, @start_time, @end_time) / @interval_in_sec,1)
)
...