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。我无法真正删除这些行,因为它们是“历史记录”的一部分。