Dưới đây là bảy tùy chọn để tìm các hàng trùng lặp trong SQL Server, khi các hàng đó có khóa chính hoặc cột định danh duy nhất khác.
Nói cách khác, bảng chứa hai hoặc nhiều hàng chia sẻ chính xác các giá trị giống nhau trên tất cả các cột ngoại trừ cột định danh duy nhất của nó.
Dữ liệu Mẫu
Giả sử chúng ta có một bảng với dữ liệu sau:
SELECT * FROM Dogs;
Kết quả:
+---------+-------------+------------+ | DogId | FirstName | LastName | |---------+-------------+------------| | 1 | Bark | Smith | | 2 | Bark | Smith | | 3 | Woof | Jones | | 4 | Ruff | Robinson | | 5 | Wag | Johnson | | 6 | Wag | Johnson | | 7 | Wag | Johnson | +---------+-------------+------------+
Chúng ta có thể thấy rằng hai hàng đầu tiên là trùng lặp (ngoại trừ DogId
, chứa một giá trị duy nhất trên tất cả các hàng và có thể được sử dụng làm cột khóa chính của bảng). Chúng ta cũng có thể thấy rằng ba hàng cuối cùng là trùng lặp (ngoại trừ DogId
cột).
Cột ID duy nhất đảm bảo rằng không có hàng trùng lặp, đây thường là một đặc điểm rất được mong đợi trong RDBMS. Tuy nhiên, trong trường hợp này, nó có khả năng cản trở khả năng tìm thấy các bản sao của chúng tôi. Theo định nghĩa, cột ID duy nhất đảm bảo rằng không có bản sao. May mắn thay, chúng tôi có thể khắc phục vấn đề này khá dễ dàng, như các ví dụ sau đây cho thấy.
Tùy chọn 1
Có lẽ cách dễ nhất / đơn giản nhất để làm điều đó là với một truy vấn đơn giản sử dụng GROUP BY
mệnh đề:
SELECT
FirstName,
LastName,
COUNT(*) AS Count
FROM Dogs
GROUP BY FirstName, LastName;
Kết quả:
+-------------+------------+---------+ | FirstName | LastName | Count | |-------------+------------+---------| | Wag | Johnson | 3 | | Woof | Jones | 1 | | Ruff | Robinson | 1 | | Bark | Smith | 2 | +-------------+------------+---------+
Chúng tôi có thể loại trừ khóa chính / cột ID duy nhất bằng cách bỏ qua nó khỏi truy vấn của chúng tôi.
Kết quả cho chúng ta biết rằng có ba hàng chứa Wag Johnson và hai hàng chứa Bark Smith. Đây là những bản sao (hoặc bản ba trong trường hợp của Wag Johnson).
Tùy chọn 2
Chúng tôi có thể loại trừ các trường hợp không trùng lặp khỏi kết quả bằng cách bao gồm HAVING
mệnh đề trong truy vấn của chúng tôi:
SELECT
FirstName,
LastName,
COUNT(*) AS Count
FROM Dogs
GROUP BY FirstName, LastName
HAVING COUNT(*) > 1;
Kết quả:
+-------------+------------+---------+ | FirstName | LastName | Count | |-------------+------------+---------| | Wag | Johnson | 3 | | Bark | Smith | 2 | +-------------+------------+---------+
Tùy chọn 3
Chúng tôi cũng có thể kiểm tra các bản sao trên các cột được nối. Ví dụ:chúng ta có thể sử dụng CONCAT()
hàm để nối hai cột của chúng ta:
SELECT
DISTINCT CONCAT(FirstName, ' ', LastName) AS DogName,
COUNT(*) AS Count
FROM Dogs
GROUP BY CONCAT(FirstName, ' ', LastName);
Kết quả:
+---------------+---------+ | DogName | Count | |---------------+---------| | Bark Smith | 2 | | Ruff Robinson | 1 | | Wag Johnson | 3 | | Woof Jones | 1 | +---------------+---------+
Tùy chọn 4
Chúng tôi có thể sử dụng ROW_NUMBER()
chức năng với PARTITION BY
mệnh đề để tạo một cột mới với số hàng tăng lên mỗi khi có một bản sao, nhưng đặt lại lần nữa khi có một hàng duy nhất:
SELECT
*,
ROW_NUMBER() OVER (
PARTITION BY FirstName, LastName
ORDER BY FirstName, LastName
) AS Row_Number
FROM Dogs;
Kết quả:
+---------+-------------+------------+--------------+ | 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 | +---------+-------------+------------+--------------+
Một lợi ích của phương pháp này là chúng tôi có thể thấy từng hàng trùng lặp, cùng với cột định danh duy nhất của nó, do thực tế là chúng tôi không nhóm các kết quả.
Tùy chọn 5
Chúng ta cũng có thể sử dụng ví dụ trước làm biểu thức bảng chung trong một truy vấn lớn hơn:
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;
Kết quả:
+---------+-------------+------------+--------------+ | DogId | FirstName | LastName | Row_Number | |---------+-------------+------------+--------------| | 2 | Bark | Smith | 2 | | 6 | Wag | Johnson | 2 | | 7 | Wag | Johnson | 3 | +---------+-------------+------------+--------------+
Tùy chọn này loại trừ các bản không trùng lặp khỏi đầu ra.
Nó cũng loại trừ chính xác một hàng của mỗi bản sao khỏi đầu ra. Điều này sẽ mở ra cánh cửa để chúng tôi quay SELECT *
cuối cùng thành một DELETE
để xóa bảng trong khi vẫn giữ một trong mỗi bản sao.
Tùy chọn 6
Dưới đây là một cách ngắn gọn hơn để có được kết quả giống như ví dụ trước:
SELECT * FROM Dogs
WHERE DogId IN (
SELECT DogId FROM Dogs
EXCEPT SELECT MIN(DogId) FROM Dogs
GROUP BY FirstName, LastName
);
Kết quả:
+-------+-----------+----------+ | DogId | FirstName | LastName | +-------+-----------+----------+ | 2 | Bark | Smith | | 6 | Wag | Johnson | | 7 | Wag | Johnson | +-------+-----------+----------+
Ví dụ này không yêu cầu tạo số hàng riêng của chúng tôi.
Tùy chọn 7
Và cuối cùng, đây là một kỹ thuật phức tạp hơn một chút để trả về các hàng trùng lặp:
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
);
Kết quả:
+---------+-------------+------------+---------+-------------+------------+ | DogId | FirstName | LastName | DogId | FirstName | LastName | |---------+-------------+------------+---------+-------------+------------| | 2 | Bark | Smith | 1 | Bark | Smith | | 7 | Wag | Johnson | 5 | Wag | Johnson | | 7 | Wag | Johnson | 6 | Wag | Johnson | +---------+-------------+------------+---------+-------------+------------+
Ngay cả khi kết quả trông phức tạp hơn, nhưng này, nó vẫn hiển thị cho chúng ta các bản sao!