Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Atualizar todos, exceto um dos registros duplicados na tabela no SQL Server


Você pode resolver esse problema sem uma junção, o que significa que deve ter um desempenho melhor. A ideia é agrupar os dados pelo seu object_id, contando o número da linha de cada object_id. Isso é o que "partição por" faz. Então você pode atualizar onde o row_num é> 1. Isso atualizará todos os object_id duplicados, exceto o primeiro!
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 

Em uma tabela de teste de 82.944 registros, o desempenho foi tal (sua milhagem pode variar!):Tabela 'teste'. Contagem de varredura 5, leituras lógicas 82283, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob 0. Tempo de CPU =141 ms, tempo decorrido =150 ms.

Certamente também podemos resolver esse problema usando uma junção interna, no entanto, em geral, isso deve levar a leituras mais lógicas e CPU mais alta:

Tabela 'teste'. Contagem de varredura 10, leituras lógicas 83622, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob 0. Tabela 'Arquivo de trabalho'. Contagem de varredura 0, leituras lógicas 0, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob 0.Tabela 'Tabela de trabalho'. Contagem de varredura 4, leituras lógicas 167426, leituras físicas 0, leituras antecipadas 0, leituras lógicas lob 0, leituras físicas lob 0, leituras antecipadas lob 0. Tempo de CPU =342 ms, tempo decorrido =233 ms.

Para percorrer os resultados e atualizar em lotes menores:
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

Isso ajudará a manter o bloqueio se outras sessões simultâneas estiverem tentando acessar essa tabela.