更新SQL Server中表中的所有重复记录中的所有记录

时间:2014-08-01 23:13:26

标签: sql-server

我有一个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 | 
+----+-----------+------------+

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

任何帮助都将不胜感激。

3 个答案:

答案 0 :(得分:5)

您可以在没有连接的情况下解决此问题,这意味着它应该具有更好的性能。我们的想法是通过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 ms,经过时间= 150 ms。

我们当然也可以通过使用内部连接来解决这个问题,但是,通常这会导致更多的逻辑读取和更高的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 ms,经过时间= 233 ms。

循环结果并以较小批量更新:

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

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

答案 1 :(得分:1)

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

答案 2 :(得分:1)

根据您的问题,似乎对于object_id的每个值,您希望对于id_最低的object_id保持status_val = 0,为其他id保持= 2。如果确实如此,如果object_id最多只重复3次,那么我有一个非常简单的解决方案。使用modulo或remainder运算符来获得您想要的结果。以下是我稍后会解释的答案:

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