更新 SQL Server 表中除一条重复记录外的所有重复记录

Posted

技术标签:

【中文标题】更新 SQL Server 表中除一条重复记录外的所有重复记录【英文标题】:Update all but one of duplicate records in table in SQL Server 【发布时间】:2014-08-01 23:13:25 【问题描述】:

我有一个 SQL Server 表,其中一列 (object_id) 中有重复条目,例如:

+----+-----------+------------+
| id | object_id | status_val |
+----+-----------+------------+
|  1 |         1 |          0 | 
|  2 |         1 |          0 | 
|  3 |         1 |          0 | 
|  4 |         2 |          0 | 
|  5 |         3 |          0 | 
|  6 |         3 |          0 | 
+----+-----------+------------+

object_id 列中存在重复时,我需要更新它们的所有状态。所以上表中object_id 1 和 3 是重复的。所以我想将他们的status_val 更改为 2,但其中一个条目除外。结果如下:

| id | object_id | status_val |
+----+-----------+------------+
|  1 |         1 |          0 | 
|  2 |         1 |          2 | 
|  3 |         1 |          2 | 
|  4 |         2 |          0 | 
|  5 |         3 |          0 | 
|  6 |         3 |          2 | 
+----+-----------+------------+

重复的哪一行的状态已更新并不重要。

任何帮助将不胜感激。

【问题讨论】:

看我的回答,它非常小而且简单。 【参考方案1】:

你可以在没有连接的情况下解决这个问题,这意味着它应该有更好的性能。这个想法是按您的 object_id 对数据进行分组,计算每个 object_id 的行号。这就是“分区依据”所做的。然后您可以更新 row_num > 1 的位置。这将更新除第一个之外的所有重复的 object_id!

update t set t.status_val = 'some_status' 
from (
    select *, row_number() over(partition by object_id order by (select null)) row_num  
    from foo
) t 
where row_num > 1 

在 82944 条记录的测试表上,性能是这样的(您的里程可能会有所不同!): 表“测试”。扫描计数 5,逻辑读取 82283,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 CPU 时间 = 141 毫秒,运行时间 = 150 毫秒。

我们当然也可以通过使用内连接来解决这个问题,但是,通常这会导致更多的逻辑读取和更高的 CPU:

表“测试”。扫描计数 10,逻辑读取 83622,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“工作文件”。扫描计数 0,逻辑读取 0,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 表“工作台”。扫描计数 4,逻辑读取 167426,物理读取 0,预读读取 0,lob 逻辑读取 0,lob 物理读取 0,lob 预读读取 0。 CPU 时间 = 342 毫秒,运行时间 = 233 毫秒。

循环遍历结果并小批量更新:

declare @rowcount int = 1;
declare @batch_size int = 1000;

while @rowcount > 0 
begin
    update top(@batch_size) t set t.status_val = 'already updated'
    from (
        select *, row_number() over(partition by object_id order by (select null)) row_num  
        from foo
        where status_val <> 'already updated' 
    ) t 
    where row_num > 1 
    set @rowcount = @@rowcount;
end

如果其他并发会话试图访问此表,这将有助于保持锁定。

【讨论】:

感谢您的回答。我在周末对此进行了测试,效果很好!再次感谢。 嗨。我实际上尝试针对更大的数据集进行测试,发现它花费的时间太长。表中有 > 200 万条条目,查询已运行 3 多个小时。有没有办法可以批量更新,这样它就不会同时在整个表上运行?也许我可以从单独的独立 Windows 服务运行它? 这可能是提交大小的 bc。只要“status_val”可以过滤掉您已经更新的行,您就可以在循环中执行此操作。我将使用循环选项更新我的答案。【参考方案2】:
UPDATE Table
SET Table.status_val = '2'
FROM Table
INNER JOIN
(SELECT id, row_number()OVER(PARTITION BY object_id ORDER BY id) as seq FROM Table) other_table
ON Table.id = other_table.id AND seq <> 1

【讨论】:

【参考方案3】:

根据您的问题,似乎对于 object_id 的每个值,您希望为具有最低 id 的 object_id 保持 status_val = 0,而对于其他的则保持 = 2。如果确实如此,并且如果 object_id 最多重复 3 次,那么我有一个非常简单的解决方案给你。使用模或余数运算符来得到你想要的。这是我稍后会解释的答案:

update [MyTable]
set status_val = 2
where (id%3) != 1

当您将id 的任何值除以 3 时,余数只能是 0,1 或 2。因此,对于每个 id%3 不为 1 的 object_id,我们将 status_val 更改为 2。

在执行上面的代码之前,看看这个查询的输出——

select id, (id%3) as flg, object_id, status_val 
from MyTable

【讨论】:

以上是关于更新 SQL Server 表中除一条重复记录外的所有重复记录的主要内容,如果未能解决你的问题,请参考以下文章

需要为 SQL Server 中另一个值重复的记录更新一列 [重复]

sql查询 如何获取查找某ID的一条记录在表中是第几条记录

删除一张表中重复数据并保留一条ID最小的记录

Solr 如何删除数组中除记录外的所有记录?

sqlserver外键关系有啥用?

sql server 中如何插入一条时间记录