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

3 maneiras de excluir linhas duplicadas no SQL Server enquanto ignora a chave primária


Os exemplos a seguir usam T-SQL para excluir linhas duplicadas no SQL Server enquanto ignoram a chave primária ou a coluna de identificador exclusivo.

Mais especificamente, os exemplos excluem linhas duplicadas, mas mantêm uma. Assim, dadas duas linhas idênticas, uma é excluída e a outra permanece. Isso geralmente é chamado de “desduplicação” da tabela, “desduplicação” da tabela etc.

Dados de amostra


Suponha que temos uma tabela com os seguintes dados:
SELECT * FROM Dogs;

Resultado:
+---------+-------------+------------+
| DogId   | FirstName   | LastName   |
|---------+-------------+------------|
| 1       | Bark        | Smith      |
| 2       | Bark        | Smith      |
| 3       | Woof        | Jones      |
| 4       | Ruff        | Robinson   |
| 5       | Wag         | Johnson    |
| 6       | Wag         | Johnson    |
| 7       | Wag         | Johnson    |
+---------+-------------+------------+

Podemos ver que as duas primeiras linhas são duplicadas, assim como as três últimas linhas.

Opção 1


Primeiro, vamos executar o seguinte código para verificar quais linhas serão desduplicadas:
WITH cte AS 
    (
        SELECT 
            *,
            ROW_NUMBER() OVER ( 
                PARTITION BY FirstName, LastName 
                ORDER BY FirstName, LastName
                ) AS Row_Number
        FROM Dogs
    )
SELECT * FROM cte WHERE Row_Number <> 1;

Resultado:
+---------+-------------+------------+--------------+
| DogId   | FirstName   | LastName   | Row_Number   |
|---------+-------------+------------+--------------|
| 2       | Bark        | Smith      | 2            |
| 6       | Wag         | Johnson    | 2            |
| 7       | Wag         | Johnson    | 3            |
+---------+-------------+------------+--------------+

Usamos o ROW_NUMBER() função com a função PARTITION BY cláusula para criar nosso próprio número de linha que é incrementado quando qualquer duplicata é encontrada e reinicia quando uma não duplicada é encontrada. Um número maior que 1 indica que é uma duplicata e, portanto, retornamos apenas linhas que possuem um número maior que 1.

Podemos ver que três linhas serão excluídas quando desduplicarmos esta tabela.

Agora vamos desduplicar a tabela:
WITH cte AS 
    (
        SELECT 
            *,
            ROW_NUMBER() OVER ( 
                PARTITION BY FirstName, LastName 
                ORDER BY FirstName, LastName
                ) AS Row_Number
        FROM Dogs
    )
DELETE FROM cte WHERE Row_Number <> 1;

Resultado:
(3 rows affected)

Como esperado, três linhas foram excluídas.

Esta consulta é quase idêntica à anterior. Tudo o que fizemos foi alterar SELECT * na última linha para DELETE .

Agora vamos selecionar todas as linhas da tabela para verificar se as linhas corretas foram excluídas:
SELECT * FROM Dogs;

Resultado:
+---------+-------------+------------+
| DogId   | FirstName   | LastName   |
|---------+-------------+------------|
| 1       | Bark        | Smith      |
| 3       | Woof        | Jones      |
| 4       | Ruff        | Robinson   |
| 5       | Wag         | Johnson    |
+---------+-------------+------------+

Podemos ver que cada cachorro agora aparece apenas uma vez na tabela.

Opção 2


Supondo que a tabela foi restaurada após o exemplo anterior, aqui está outra maneira de verificar se há duplicatas:
SELECT * FROM Dogs 
WHERE DogId IN (
    SELECT DogId FROM Dogs 
    EXCEPT SELECT MIN(DogId) FROM Dogs 
    GROUP BY FirstName, LastName
    );

Resultado:
+---------+-------------+------------+
| DogId   | FirstName   | LastName   |
|---------+-------------+------------|
| 2       | Bark        | Smith      |
| 6       | Wag         | Johnson    |
| 7       | Wag         | Johnson    |
+---------+-------------+------------+

Neste caso, usamos o EXCEPT operador junto com o MIN() função. Poderíamos substituir MIN() com MAX() dependendo de quais linhas queremos que sejam deletadas.

Para excluir as linhas, podemos simplesmente substituir SELECT * com DELETE :
DELETE FROM Dogs 
WHERE DogId IN (
    SELECT DogId FROM Dogs 
    EXCEPT SELECT MIN(DogId) FROM Dogs 
    GROUP BY FirstName, LastName
    );

Resultado:
(3 rows affected)

E confira o que restou:
SELECT * FROM Dogs;

Resultado:
+---------+-------------+------------+
| DogId   | FirstName   | LastName   |
|---------+-------------+------------|
| 1       | Bark        | Smith      |
| 3       | Woof        | Jones      |
| 4       | Ruff        | Robinson   |
| 5       | Wag         | Johnson    |
+---------+-------------+------------+

Opção 3


Outra maneira de fazer isso é unir a tabela em si mesma e verificar se há duplicatas dessa maneira.

Supondo que a tabela tenha sido restaurada após o exemplo anterior, aqui está nossa terceira opção para selecionar duplicatas:
SELECT * 
FROM Dogs d1, Dogs d2 
WHERE d1.FirstName = d2.FirstName 
AND d1.LastName = d2.LastName
AND d1.DogId <> d2.DogId 
AND d1.DogId = (
    SELECT MAX(DogId) 
    FROM Dogs d3 
    WHERE d3.FirstName = d1.FirstName 
    AND d3.LastName = d1.LastName
);

Resultado:
+---------+-------------+------------+---------+-------------+------------+
| DogId   | FirstName   | LastName   | DogId   | FirstName   | LastName   |
|---------+-------------+------------+---------+-------------+------------|
| 2       | Bark        | Smith      | 1       | Bark        | Smith      |
| 7       | Wag         | Johnson    | 5       | Wag         | Johnson    |
| 7       | Wag         | Johnson    | 6       | Wag         | Johnson    |
+---------+-------------+------------+---------+-------------+------------+

Esse resultado não é tão claro quanto o do exemplo anterior, mas ainda podemos ver quais linhas são duplicadas.

Agora podemos modificar essa consulta para excluir linhas duplicadas:
DELETE FROM Dogs WHERE DogId IN (
    SELECT d2.DogId 
    FROM Dogs d1, Dogs d2 
    WHERE d1.FirstName = d2.FirstName 
    AND d1.LastName = d2.LastName 
    AND d1.DogId <> d2.DogId 
    AND d1.DogId=( 
        SELECT MAX(DogId) 
        FROM Dogs d3 
        WHERE d3.FirstName = d1.FirstName 
        AND d3.LastName = d1.LastName
    )
);

Resultado:
(3 rows affected)

Mais uma vez, três linhas foram excluídas.

Vamos verificar a tabela novamente:
SELECT * FROM Dogs;

Resultado:
+---------+-------------+------------+
| DogId   | FirstName   | LastName   |
|---------+-------------+------------|
| 2       | Bark        | Smith      |
| 3       | Woof        | Jones      |
| 4       | Ruff        | Robinson   |
| 7       | Wag         | Johnson    |
+---------+-------------+------------+

Você pode notar que desta vez as outras linhas foram excluídas. Em outras palavras, agora temos DogId s 2, 3, 4 e 7, enquanto nos exemplos anteriores ficamos com 1, 3, 4 e 5.

Podemos facilmente alterar este exemplo para excluir as mesmas linhas dos exemplos anteriores. Para fazer isso, podemos usar o MIN() função em vez de MAX() função:
DELETE FROM Dogs WHERE DogId IN (
    SELECT d2.DogId 
    FROM Dogs d1, Dogs d2 
    WHERE d1.FirstName = d2.FirstName 
    AND d1.LastName = d2.LastName 
    AND d1.DogId <> d2.DogId 
    AND d1.DogId=( 
        SELECT MIN(DogId) 
        FROM Dogs d3 
        WHERE d3.FirstName = d1.FirstName 
        AND d3.LastName = d1.LastName
    )
);