select 并删除时间戳差异 < 1 天的 SQL 条记录
select and remove SQL records with a timestamp difference < 1 day
我需要 select 或从徽章记录中删除日期差异小于 1 天的记录
如果 24 小时内存在 2 条或更多条记录,则必须删除所有记录,但只有一条记录。 (第一个还是最后一个,这个不重要)
select 查找它们的查询没问题,所以我可以手动删除它们。
只有相同徽章编号的记录必须被比较和删除。
这可以使用 TSQL 吗?
示例:
+------------------+--------------+
| TimeStamp | Badge |
+------------------+--------------+
| 19-10-2021 10:18 | Badge1 |
| 20-10-2021 12:18 | Badge1 |
| 22-10-2021 13:23 | Badge1 |
| 22-10-2021 11:18 | Badge1 | <--- remove
| 22-10-2021 13:18 | Badge1 | <--- remove
| 23-10-2021 14:18 | Badge1 |
| 21-10-2021 09:18 | Badge12 |
| 23-10-2021 10:18 | Badge12 |
| 23-10-2021 23:18 | Badge12 | <--- remove
| 25-10-2021 12:18 | Badge12 |
+------------------+---------+----+
由于任何给定记录状态对所有前面记录(具有相同徽章值)的状态的链式依赖性,我认为没有单一的语句解决方案。下面使用游标遍历记录,同时跟踪先前保留的值。
DECLARE @Data TABLE (Id INT IDENTITY(1,1) PRIMARY KEY, TimeStamp DATETIME, Badge VARCHAR(100))
INSERT @Data
VALUES
('2021-10-19 10:18', 'Badge1'),
('2021-10-20 12:18', 'Badge1'),
('2021-10-22 13:23', 'Badge1'),
('2021-10-22 11:18', 'Badge1'), -- remove ??? (prior record is out of sequence)
('2021-10-22 13:18', 'Badge1'), -- remove
('2021-10-23 14:18', 'Badge1'),
('2021-10-21 09:18', 'Badge12'),
('2021-10-23 10:18', 'Badge12'),
('2021-10-23 23:18', 'Badge12'), -- remove
('2021-10-25 12:18', 'Badge12')
DECLARE @Id INT
DECLARE @TimeStamp DATETIME
DECLARE @Badge VARCHAR(100)
DECLARE @PriorTimeStamp DATETIME = NULL
DECLARE @PriorBadge VARCHAR(100) = NULL
DECLARE Csr CURSOR FOR
SELECT Id, Timestamp, Badge
FROM @Data
ORDER BY Badge, Timestamp, Id
--ORDER BY Badge, Id
OPEN Csr
FETCH NEXT FROM Csr INTO @Id, @TimeStamp, @Badge
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Badge = @PriorBadge AND @TimeStamp < DATEADD(DAY, 1, @PriorTimeStamp)
BEGIN
DELETE FROM @Data WHERE CURRENT OF CSR
END
ELSE BEGIN -- Keep
SET @PriorBadge = @Badge
SET @PriorTimeStamp = @TimeStamp
END
FETCH NEXT FROM Csr INTO @Id, @TimeStamp, @Badge
END
CLOSE Csr
DEALLOCATE Csr
SELECT *
FROM @data
ORDER BY ID
结果:
Id
TimeStamp
Badge
1
2021-10-19 10:18:00.000
Badge1
2
2021-10-20 12:18:00.000
Badge1
4
2021-10-22 11:18:00.000
Badge1
6
2021-10-23 14:18:00.000
Badge1
7
2021-10-21 09:18:00.000
Badge12
8
2021-10-23 10:18:00.000
Badge12
10
2021-10-25 12:18:00.000
Badge12
请注意,“2021-10-22 13:23”值已保留,而“2021-10-22 11:18”值因日期升序而被删除。如果原始数据顺序很重要,请换掉上面的 ORDER BY
子句。
有关演示,请参阅 this db<>fiddle。
[EDIT#1] op 在其原始 post 中没有它,但她使用的是 2008。以下解决方案使用 LAG,直到 2012 年才可用。我还添加了一个标签原来的 post 所以没有其他人有同样的问题。
[编辑#2] 我显然误读了 OP post 编辑为原始 post 中的第三条评论的说明。我的解释是删除距离上一行不到 24 小时的任何行,下面的代码就是这样做的。根据 OPs post 说明@SOS 的工作原理,OP 似乎意味着检查一个日期,然后删除该日期之后小于 24 小时的所有行,然后才查看下一个要检查的日期是什么.下面的代码不会这样做。
我同意上面@TN 的观点...您有一个故障日期,我将把它视为相关删除实际上是您的错误。
首先,这是我的测试版本table...
DROP TABLE IF EXISTS #TestTable
;
GO
SET DATEFORMAT dmy
;
SELECT TimeStamp = CONVERT(DATETIME,v.TimeStamp)
,v.Badge
INTO #TestTable
FROM (VALUES
('19-10-2021 10:18','Badge1' )
,('20-10-2021 12:18','Badge1' )
,('22-10-2021 13:23','Badge1' )-- This is the row that should be removed instead of the one below according to the specs you wrote.
,('22-10-2021 11:18','Badge1' )-- remove ??? (prior record is out of sequence, so not)
,('22-10-2021 13:18','Badge1' )-- remove
,('23-10-2021 14:18','Badge1' )
,('21-10-2021 09:18','Badge12')
,('23-10-2021 10:18','Badge12')
,('23-10-2021 23:18','Badge12') -- remove
,('25-10-2021 12:18','Badge12')
)v(TimeStamp,Badge)
;
接下来,使用 LAG(OVER) 的一些演示来解决当前行和上一行之间的分钟数...我们不需要任何形式的递归。
SELECT *
,DeltaMinutes = DATEDIFF(mi,LAG(TimeStamp,1,DATEADD(dd,-2,TimeStamp)) OVER (PARTITION BY Badge ORDER BY TimeStamp),TimeStamp)
FROM #TestTable
;
这会产生以下结果。请注意 DeltaMinutes 列和正确的排序顺序。以蓝色突出显示的行是乱序的行。
在那之后,我们所要做的就是 select DeltaMinutes >= 1440 分钟的行,这是一天中的分钟数。如果您将 >= 更改为 < ,那么它将显示距上一行不到 1 天的行。
WITH cte AS
(
SELECT *
,DeltaMinutes = DATEDIFF(mi,LAG(TimeStamp,1,DATEADD(dd,-2,TimeStamp)) OVER (PARTITION BY Badge ORDER BY TimeStamp),TimeStamp)
FROM #TestTable
)
SELECT TimeStamp,Badge
FROM cte
WHERE DeltaMinutes >= 1440 --Minutes in a day.
ORDER BY CONVERT(INT,SUBSTRING(Badge,6,5)) --Sort badge numbers in numeric instead of string order.
,TimeStamp
;
结果看起来像这样...
当然,LAG 仅在 2012 年及以后可用。如果您使用的是 2008/2008R2 或更低版本,我们可以使用另一个 non-recursive 技巧。
而且,尽管如此,我不得不说我认为您的规范可能不正确,正如@TN 暗示的那样。我认为您真正想要的是保留第一行(例如)并从输出中删除该行 1440 分钟内的所有行。第一行之后至少 1440 分钟的下一行将成为新的“锚定”行,并且该行之后不到 1440 分钟的所有行将从输出中删除。如果那是您真正的意思,那么 post 返回,我们将以类似的方式解决该问题。
使用当前数据,您可能看不到任何差异,但如果数据发生变化,您就会看到。
感谢@SOS,
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=296b97685b544d67b05d15c94c0dea4d
成功了,我更改了
中的最后一个 sql
...
select * from YourTable where timestamp not in
(
SELECT tbl.TimeStamp
FROM dateCte cte
CROSS APPLY (
SELECT TOP 1 *
FROM YourTable base
WHERE base.Badge = cte.Badge
AND base.TimeStamp >= cte.RangeStart
AND base.TimeStamp < cte.RangeEnd
) tbl
)
OPTION (MaxRecursion 0)
;
这样我就可以看到并删除不需要的记录。
如果你post解决方案我可以接受它已解决
[编辑#1] 我显然误读了 OP post 编辑为原始 post 中的第三条评论的说明。我的解释是删除距离上一行不到 24 小时的任何行,下面的代码就是这样做的。根据 OPs post 说明@SOS 的工作原理,OP 似乎意味着检查一个日期,然后删除该日期之后小于 24 小时的所有行,然后才查看下一个要检查的日期是什么.下面的代码不会这样做。
这是一个将在 2008 年使用的答案。如果您在 TimeStamp 和 Badge 上有适当的唯一索引(反之亦然),它甚至不必进行排序。在任何一种情况下,这都快得多并且资源消耗更少,因为它只进行两次 table 扫描而不是三角连接。不要忘记更改 table 的名称以适合您的实际 table 名称。
WITH CTE AS
(
SELECT RowNum = ROW_NUMBER() OVER (PARTITION BY Badge ORDER BY TimeStamp)
,TimeStamp
,Badge
FROM #YourTable
)
SELECT cur.TimeStamp
,cur.Badge
-- ,prv.* -- Uncomment to learn more
-- ,Delta = DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp) --Uncomment to learn more
,Action = CASE WHEN DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp)<1440 THEN 'Remove' ELSE 'Keep' END
FROM CTE cur
LEFT JOIN CTE prv ON cur.Badge = prv.Badge AND cur.RowNum = prv.RowNum + 1
WHERE ISNULL(DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp),1440) < 1440 --Comment this out to return all entries to learn.
;
上面的结果:
p.s。我无法真正删除这些行,因为它们是“历史记录”的一部分。
我需要 select 或从徽章记录中删除日期差异小于 1 天的记录 如果 24 小时内存在 2 条或更多条记录,则必须删除所有记录,但只有一条记录。 (第一个还是最后一个,这个不重要) select 查找它们的查询没问题,所以我可以手动删除它们。
只有相同徽章编号的记录必须被比较和删除。
这可以使用 TSQL 吗?
示例:
+------------------+--------------+
| TimeStamp | Badge |
+------------------+--------------+
| 19-10-2021 10:18 | Badge1 |
| 20-10-2021 12:18 | Badge1 |
| 22-10-2021 13:23 | Badge1 |
| 22-10-2021 11:18 | Badge1 | <--- remove
| 22-10-2021 13:18 | Badge1 | <--- remove
| 23-10-2021 14:18 | Badge1 |
| 21-10-2021 09:18 | Badge12 |
| 23-10-2021 10:18 | Badge12 |
| 23-10-2021 23:18 | Badge12 | <--- remove
| 25-10-2021 12:18 | Badge12 |
+------------------+---------+----+
由于任何给定记录状态对所有前面记录(具有相同徽章值)的状态的链式依赖性,我认为没有单一的语句解决方案。下面使用游标遍历记录,同时跟踪先前保留的值。
DECLARE @Data TABLE (Id INT IDENTITY(1,1) PRIMARY KEY, TimeStamp DATETIME, Badge VARCHAR(100))
INSERT @Data
VALUES
('2021-10-19 10:18', 'Badge1'),
('2021-10-20 12:18', 'Badge1'),
('2021-10-22 13:23', 'Badge1'),
('2021-10-22 11:18', 'Badge1'), -- remove ??? (prior record is out of sequence)
('2021-10-22 13:18', 'Badge1'), -- remove
('2021-10-23 14:18', 'Badge1'),
('2021-10-21 09:18', 'Badge12'),
('2021-10-23 10:18', 'Badge12'),
('2021-10-23 23:18', 'Badge12'), -- remove
('2021-10-25 12:18', 'Badge12')
DECLARE @Id INT
DECLARE @TimeStamp DATETIME
DECLARE @Badge VARCHAR(100)
DECLARE @PriorTimeStamp DATETIME = NULL
DECLARE @PriorBadge VARCHAR(100) = NULL
DECLARE Csr CURSOR FOR
SELECT Id, Timestamp, Badge
FROM @Data
ORDER BY Badge, Timestamp, Id
--ORDER BY Badge, Id
OPEN Csr
FETCH NEXT FROM Csr INTO @Id, @TimeStamp, @Badge
WHILE @@FETCH_STATUS = 0
BEGIN
IF @Badge = @PriorBadge AND @TimeStamp < DATEADD(DAY, 1, @PriorTimeStamp)
BEGIN
DELETE FROM @Data WHERE CURRENT OF CSR
END
ELSE BEGIN -- Keep
SET @PriorBadge = @Badge
SET @PriorTimeStamp = @TimeStamp
END
FETCH NEXT FROM Csr INTO @Id, @TimeStamp, @Badge
END
CLOSE Csr
DEALLOCATE Csr
SELECT *
FROM @data
ORDER BY ID
结果:
Id | TimeStamp | Badge |
---|---|---|
1 | 2021-10-19 10:18:00.000 | Badge1 |
2 | 2021-10-20 12:18:00.000 | Badge1 |
4 | 2021-10-22 11:18:00.000 | Badge1 |
6 | 2021-10-23 14:18:00.000 | Badge1 |
7 | 2021-10-21 09:18:00.000 | Badge12 |
8 | 2021-10-23 10:18:00.000 | Badge12 |
10 | 2021-10-25 12:18:00.000 | Badge12 |
请注意,“2021-10-22 13:23”值已保留,而“2021-10-22 11:18”值因日期升序而被删除。如果原始数据顺序很重要,请换掉上面的 ORDER BY
子句。
有关演示,请参阅 this db<>fiddle。
[EDIT#1] op 在其原始 post 中没有它,但她使用的是 2008。以下解决方案使用 LAG,直到 2012 年才可用。我还添加了一个标签原来的 post 所以没有其他人有同样的问题。
[编辑#2] 我显然误读了 OP post 编辑为原始 post 中的第三条评论的说明。我的解释是删除距离上一行不到 24 小时的任何行,下面的代码就是这样做的。根据 OPs post 说明@SOS 的工作原理,OP 似乎意味着检查一个日期,然后删除该日期之后小于 24 小时的所有行,然后才查看下一个要检查的日期是什么.下面的代码不会这样做。
我同意上面@TN 的观点...您有一个故障日期,我将把它视为相关删除实际上是您的错误。
首先,这是我的测试版本table...
DROP TABLE IF EXISTS #TestTable
;
GO
SET DATEFORMAT dmy
;
SELECT TimeStamp = CONVERT(DATETIME,v.TimeStamp)
,v.Badge
INTO #TestTable
FROM (VALUES
('19-10-2021 10:18','Badge1' )
,('20-10-2021 12:18','Badge1' )
,('22-10-2021 13:23','Badge1' )-- This is the row that should be removed instead of the one below according to the specs you wrote.
,('22-10-2021 11:18','Badge1' )-- remove ??? (prior record is out of sequence, so not)
,('22-10-2021 13:18','Badge1' )-- remove
,('23-10-2021 14:18','Badge1' )
,('21-10-2021 09:18','Badge12')
,('23-10-2021 10:18','Badge12')
,('23-10-2021 23:18','Badge12') -- remove
,('25-10-2021 12:18','Badge12')
)v(TimeStamp,Badge)
;
接下来,使用 LAG(OVER) 的一些演示来解决当前行和上一行之间的分钟数...我们不需要任何形式的递归。
SELECT *
,DeltaMinutes = DATEDIFF(mi,LAG(TimeStamp,1,DATEADD(dd,-2,TimeStamp)) OVER (PARTITION BY Badge ORDER BY TimeStamp),TimeStamp)
FROM #TestTable
;
这会产生以下结果。请注意 DeltaMinutes 列和正确的排序顺序。以蓝色突出显示的行是乱序的行。
在那之后,我们所要做的就是 select DeltaMinutes >= 1440 分钟的行,这是一天中的分钟数。如果您将 >= 更改为 < ,那么它将显示距上一行不到 1 天的行。
WITH cte AS
(
SELECT *
,DeltaMinutes = DATEDIFF(mi,LAG(TimeStamp,1,DATEADD(dd,-2,TimeStamp)) OVER (PARTITION BY Badge ORDER BY TimeStamp),TimeStamp)
FROM #TestTable
)
SELECT TimeStamp,Badge
FROM cte
WHERE DeltaMinutes >= 1440 --Minutes in a day.
ORDER BY CONVERT(INT,SUBSTRING(Badge,6,5)) --Sort badge numbers in numeric instead of string order.
,TimeStamp
;
结果看起来像这样...
当然,LAG 仅在 2012 年及以后可用。如果您使用的是 2008/2008R2 或更低版本,我们可以使用另一个 non-recursive 技巧。
而且,尽管如此,我不得不说我认为您的规范可能不正确,正如@TN 暗示的那样。我认为您真正想要的是保留第一行(例如)并从输出中删除该行 1440 分钟内的所有行。第一行之后至少 1440 分钟的下一行将成为新的“锚定”行,并且该行之后不到 1440 分钟的所有行将从输出中删除。如果那是您真正的意思,那么 post 返回,我们将以类似的方式解决该问题。
使用当前数据,您可能看不到任何差异,但如果数据发生变化,您就会看到。
感谢@SOS,
https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=296b97685b544d67b05d15c94c0dea4d 成功了,我更改了
中的最后一个 sql ...
select * from YourTable where timestamp not in
(
SELECT tbl.TimeStamp
FROM dateCte cte
CROSS APPLY (
SELECT TOP 1 *
FROM YourTable base
WHERE base.Badge = cte.Badge
AND base.TimeStamp >= cte.RangeStart
AND base.TimeStamp < cte.RangeEnd
) tbl
)
OPTION (MaxRecursion 0)
;
这样我就可以看到并删除不需要的记录。 如果你post解决方案我可以接受它已解决
[编辑#1] 我显然误读了 OP post 编辑为原始 post 中的第三条评论的说明。我的解释是删除距离上一行不到 24 小时的任何行,下面的代码就是这样做的。根据 OPs post 说明@SOS 的工作原理,OP 似乎意味着检查一个日期,然后删除该日期之后小于 24 小时的所有行,然后才查看下一个要检查的日期是什么.下面的代码不会这样做。
这是一个将在 2008 年使用的答案。如果您在 TimeStamp 和 Badge 上有适当的唯一索引(反之亦然),它甚至不必进行排序。在任何一种情况下,这都快得多并且资源消耗更少,因为它只进行两次 table 扫描而不是三角连接。不要忘记更改 table 的名称以适合您的实际 table 名称。
WITH CTE AS
(
SELECT RowNum = ROW_NUMBER() OVER (PARTITION BY Badge ORDER BY TimeStamp)
,TimeStamp
,Badge
FROM #YourTable
)
SELECT cur.TimeStamp
,cur.Badge
-- ,prv.* -- Uncomment to learn more
-- ,Delta = DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp) --Uncomment to learn more
,Action = CASE WHEN DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp)<1440 THEN 'Remove' ELSE 'Keep' END
FROM CTE cur
LEFT JOIN CTE prv ON cur.Badge = prv.Badge AND cur.RowNum = prv.RowNum + 1
WHERE ISNULL(DATEDIFF(mi,prv.TimeStamp,cur.TimeStamp),1440) < 1440 --Comment this out to return all entries to learn.
;
上面的结果:
p.s。我无法真正删除这些行,因为它们是“历史记录”的一部分。