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
)
);