Ето седем опции за намиране на дублиращи се редове в SQL Server, когато тези редове имат първичен ключ или друга колона с уникален идентификатор.
С други думи, таблицата съдържа два или повече реда, които споделят абсолютно еднакви стойности във всички колони, с изключение на колоната с уникален идентификатор.
Примерни данни
Да предположим, че имаме таблица със следните данни:
SELECT * FROM Dogs;
Резултат:
+---------+-------------+------------+ | DogId | FirstName | LastName | |---------+-------------+------------| | 1 | Bark | Smith | | 2 | Bark | Smith | | 3 | Woof | Jones | | 4 | Ruff | Robinson | | 5 | Wag | Johnson | | 6 | Wag | Johnson | | 7 | Wag | Johnson | +---------+-------------+------------+
Можем да видим, че първите два реда са дублирани (с изключение на DogId
колона, която съдържа уникална стойност във всички редове и може да се използва като колона с първичен ключ на таблицата). Можем също да видим, че последните три реда са дублирани (с изключение на DogId
колона).
Уникалната колона с идентификатор гарантира, че няма дублиращи се редове, което обикновено е много желана черта в RDBMS. В този случай обаче има потенциал да попречи на способността ни да намираме дубликати. По дефиниция колоната с уникален идентификатор гарантира, че няма дубликати. За щастие можем да преодолеем този проблем доста лесно, както показват следните примери.
Опция 1
Вероятно най-лесният/простият начин да го направите е с проста заявка, която използва GROUP BY
клауза:
SELECT
FirstName,
LastName,
COUNT(*) AS Count
FROM Dogs
GROUP BY FirstName, LastName;
Резултат:
+-------------+------------+---------+ | FirstName | LastName | Count | |-------------+------------+---------| | Wag | Johnson | 3 | | Woof | Jones | 1 | | Ruff | Robinson | 1 | | Bark | Smith | 2 | +-------------+------------+---------+
Успяхме да изключим колоната с първичен ключ/уникален идентификатор, като го пропуснем от нашата заявка.
Резултатът ни казва, че има три реда, съдържащи Уаг Джонсън и два реда, съдържащи Барк Смит. Това са дубликати (или три екземпляра в случая на Уаг Джонсън).
Опция 2
Можем да изключим недубликатите от резултата, като включим HAVING
клауза в нашата заявка:
SELECT
FirstName,
LastName,
COUNT(*) AS Count
FROM Dogs
GROUP BY FirstName, LastName
HAVING COUNT(*) > 1;
Резултат:
+-------------+------------+---------+ | FirstName | LastName | Count | |-------------+------------+---------| | Wag | Johnson | 3 | | Bark | Smith | 2 | +-------------+------------+---------+
Опция 3
Можем също да проверим за дубликати на свързани колони. Например, можем да използваме CONCAT()
функция за свързване на нашите две колони:
SELECT
DISTINCT CONCAT(FirstName, ' ', LastName) AS DogName,
COUNT(*) AS Count
FROM Dogs
GROUP BY CONCAT(FirstName, ' ', LastName);
Резултат:
+---------------+---------+ | DogName | Count | |---------------+---------| | Bark Smith | 2 | | Ruff Robinson | 1 | | Wag Johnson | 3 | | Woof Jones | 1 | +---------------+---------+
Опция 4
Можем да използваме ROW_NUMBER()
функция с PARTITION BY
клауза за създаване на нова колона с номер на ред, който се увеличава всеки път, когато има дубликат, но се нулира отново, когато има уникален ред:
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY FirstName, LastName
ORDER BY FirstName, LastName
) AS Row_Number
FROM Dogs;
Резултат:
+---------+-------------+------------+--------------+ | DogId | FirstName | LastName | Row_Number | |---------+-------------+------------+--------------| | 1 | Bark | Smith | 1 | | 2 | Bark | Smith | 2 | | 4 | Ruff | Robinson | 1 | | 5 | Wag | Johnson | 1 | | 6 | Wag | Johnson | 2 | | 7 | Wag | Johnson | 3 | | 3 | Woof | Jones | 1 | +---------+-------------+------------+--------------+
Едно от предимствата на този метод е, че можем да видим всеки един дублиран ред, заедно с неговата колона с уникален идентификатор, поради факта, че не групираме резултатите.
Опция 5
Можем също да използваме предишния пример като общ табличен израз в по-голяма заявка:
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;
Резултат:
+---------+-------------+------------+--------------+ | DogId | FirstName | LastName | Row_Number | |---------+-------------+------------+--------------| | 2 | Bark | Smith | 2 | | 6 | Wag | Johnson | 2 | | 7 | Wag | Johnson | 3 | +---------+-------------+------------+--------------+
Тази опция изключва недубликатите от изхода.
Той също така изключва точно един ред от всеки дубликат от изхода. Това ни отваря вратата да обърнем последния SELECT *
в DELETE
за да премахнете дублирането на таблицата, като запазите по един от всеки дубликат.
Опция 6
Ето по-сбит начин да получите същия изход като предишния пример:
SELECT * FROM Dogs
WHERE DogId IN (
SELECT DogId FROM Dogs
EXCEPT SELECT MIN(DogId) FROM Dogs
GROUP BY FirstName, LastName
);
Резултат:
+-------+-----------+----------+ | DogId | FirstName | LastName | +-------+-----------+----------+ | 2 | Bark | Smith | | 6 | Wag | Johnson | | 7 | Wag | Johnson | +-------+-----------+----------+
Този пример не изисква генериране на собствен номер на отделен ред.
Опция 7
И накрая, ето малко по-заплетена техника за връщане на дублиращи се редове:
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
);
Резултат:
+---------+-------------+------------+---------+-------------+------------+ | DogId | FirstName | LastName | DogId | FirstName | LastName | |---------+-------------+------------+---------+-------------+------------| | 2 | Bark | Smith | 1 | Bark | Smith | | 7 | Wag | Johnson | 5 | Wag | Johnson | | 7 | Wag | Johnson | 6 | Wag | Johnson | +---------+-------------+------------+---------+-------------+------------+
Дори резултатът изглежда по-заплетен, но все пак ни показва дубликатите!