【问题标题】:Delete rows following a duplicate删除重复后的行
【发布时间】:2011-05-25 11:14:57
【问题描述】:

我有一个用户登录和注销标记列表。不幸的是,LOGIN 条目可能并不总是后跟 LOGOUT 条目。
当按 [event_date] 排序时,我希望删除与上一行具有相同 [event][user_id] 的任何行 有关如何执行此操作的任何建议?

示例表

CREATE TABLE #LOG (
    [id] int IDENTITY(1,1),
    [user_id] int,
    [event] varchar(50),
    [event_date] datetime
);
INSERT INTO #LOG ([user_id], [event], [event_date])
SELECT 1,'LOGIN',{ts '2010-12-15 15:31:59'}
UNION ALL SELECT 1,'LOGOUT',{ts '2010-12-15 15:32:55'}
UNION ALL SELECT 1,'LOGIN',{ts '2010-12-15 15:38:04'}
UNION ALL SELECT 1,'LOGOUT',{ts '2010-12-15 15:38:17'}
UNION ALL SELECT 1,'LOGOUT',{ts '2010-12-15 15:38:45'} -- Delete
UNION ALL SELECT 2,'LOGIN',{ts '2010-12-15 16:59:39'}
UNION ALL SELECT 2,'LOGOUT',{ts '2010-12-15 17:00:08'}
UNION ALL SELECT 2,'LOGOUT',{ts '2010-12-15 17:00:39'} -- Delete
UNION ALL SELECT 2,'LOGOUT',{ts '2010-12-15 17:01:16'} -- Delete
UNION ALL SELECT 2,'LOGIN',{ts '2010-12-15 17:01:38'}
UNION ALL SELECT 2,'LOGIN',{ts '2010-12-15 17:02:26'} -- Delete
UNION ALL SELECT 2,'LOGOUT',{ts '2010-12-15 17:02:39'}

【问题讨论】:

  • +1 教我一些新语法。我以前从未见过{ts 'xxx'}before。
  • 这是什么 {ts 'xxx'}?
  • 从外观上看,{ts 'xxx'} 似乎与时间戳有关,非常有趣!
  • 它允许解析器确定数据类型,以免将其混淆为字符串,然后可能需要隐式转换为日期。
  • 啊,{ts 'xxx'} 是 ODBC 日期时间格式。你可以在这里找到它:msdn.microsoft.com/en-us/library/ms190234%28v=sql.90%29.aspx

标签: sql sql-server sql-server-2005 sql-delete gaps-and-islands


【解决方案1】:
;WITH T1 AS
(
SELECT * , 
        ROW_NUMBER() OVER (ORDER BY event_date)-
        ROW_NUMBER() OVER (PARTITION BY [user_id], [event] 
                               ORDER BY event_date) AS Grp
FROM #LOG
),T2 AS
(
SELECT 
   ROW_NUMBER() OVER (PARTITION BY [user_id], [event], Grp 
                          ORDER BY event_date) RN
FROM T1
)
DELETE FROM T2 
WHERE RN > 1

【讨论】:

  • +1 :在没有索引的小型数据集上,这确实比其他选项更有效。这给我留下了深刻的印象,我希望它不需要更多地使用 SQL Server 如何优化 ROW_NUMBER() :) 使用 user_id,event_date 上的覆盖索引,但是,没有 ROW_NUMBER() 的版本似乎更有效。
  • 我对CTE略知一二。我也在我的递归查询中使用了它。但是如何从 T2 中删除影响#LOG?我错过了什么?
  • @Dems - 谢谢,我还没有测试过性能,我会把它留给 OP 来对照他们的数据。我在 Twitter 上看到 SQL Server 2011 将支持 lag 并增加对窗口函数的支持,我认为这应该会使这种类型的查询更容易。 @Muhammad 从 CTE 中删除与从视图中删除相同。它会删除基表中的相应行。
  • @Muhammad :将 CTE 想象成具有模式绑定的视图。通过从视图中删除,您正在从基础表中删除。
  • @Martin :扩展窗口函数支持会很好。我可以开始接受我的同事声称 SAS 可以完成 SQL Sever 不擅长的统计工作:)
【解决方案2】:

可以选择使用 SQL Server 的 ROW_NUMBER 功能

SQL 语句

;WITH q AS (
    SELECT  Rownumber = ROW_NUMBER() OVER (ORDER BY user_id, event_date)
    , user_id
    , event
    , event_date
    FROM    #LOG
)
DELETE FROM #LOG
FROM    #LOG l
        INNER JOIN (
            SELECT  q2.*
            FROM    q q1
                    INNER JOIN q q2 ON  q2.Rownumber = q1.Rownumber + 1
                                        AND q2.user_id = q1.user_id
                                        AND q2.event = q1.event
        ) q ON  q.user_id = l.user_id
                AND q.event_date = l.event_date

SELECT  *
FROM    #LOG

【讨论】:

    【解决方案3】:

    我的理解是您要删除模式始终为In,Out,In,Out,etc 的条目。

    这意味着如果前面的记录(按 user_id,然后 event_date 排序)属于同一事件,则删除记录。

    我会使用两个选项来解决这个问题...

    DELETE
      #log
    WHERE
      event = (
               SELECT
                 TOP 1
                 event
               FROM
                 #log AS [preceding]
               WHERE
                 [preceding].user_id = #log.user_id
                 AND [preceding].event_date < #log.event_date
               ORDER BY
                 [preceding].event_date DESC
              )
    

    或者……

    WITH ordered_log AS (
      SELECT
        ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_date) AS user_event_id,
        *
      FROM
        #log
    )
    
    DELETE
      ordered_log
    FROM
      ordered_log
    INNER JOIN
      ordered_log   AS [preceding]
        ON  [preceding].login_id      = [ordered_log].login_id
        AND [preceding].user_event_id = [ordered_log].user_event_id - 1
    WHERE
      [preceding].event = [ordered_log].event
    

    无论哪种方式,我强烈推荐一个索引覆盖user_id 然后event_date


    注意:第一个版本无法处理两个事件具有相同时间戳的可能性。然而,后者确实如此。

    【讨论】:

      【解决方案4】:

      如果您必须删除重复的行。那么就不需要设置order by子句了。

      下面试试

      Delete l from #LOG l
      Inner Join 
      (
          Select id from #LOG l
          Inner Join(
              Select user_id, event from #LOG 
              group by user_id, event
              having COUNT(user_id) > 1 and COUNT(event) > 1
          )T
          on (l.user_id = t.user_id) and (l.event = t.event)
      )T
      on T.id = l.id
      

      【讨论】:

        猜你喜欢
        • 2019-08-08
        • 2017-09-12
        • 1970-01-01
        • 2012-11-07
        • 2018-04-07
        • 1970-01-01
        • 2012-12-07
        相关资源
        最近更新 更多