Menu

DISTINCT trong SQLite: loại bỏ trùng lặp trong truy vấn SELECT

Cách SELECT DISTINCT hoạt động trong SQLite: một cột, nhiều cột, cách xử lý NULL, COUNT(DISTINCT) và khi nào GROUP BY là công cụ tốt hơn.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

DISTINCT loại bỏ các hàng trùng lặp

Mặc định, SELECT trả về mọi hàng khớp, kể cả các hàng trùng. DISTINCT bảo SQLite gộp những hàng giống hệt nhau trên các cột bạn đã chọn, để mỗi tổ hợp duy nhất chỉ xuất hiện một lần.

Năm hàng vào, ba hàng ra. SQLite xem cột customer, bỏ các giá trị lặp lại và trả về một hàng cho mỗi giá trị duy nhất. Thứ tự không được đảm bảo; hãy thêm ORDER BY nếu bạn cần.

DISTINCT áp dụng cho toàn bộ danh sách chọn

Đây là chỗ nhiều người vấp. DISTINCT không chọn một cột để loại trùng; nó loại trùng cả hàng dựa trên mọi cột bạn đã chọn.

Mỗi cặp (customer, country) duy nhất xuất hiện một lần. Nếu cùng một khách hàng xuất hiện với hai quốc gia khác nhau, bạn sẽ thấy cả hai hàng, vì với SQLite đó không phải là bản trùng.

Không có cú pháp DISTINCT(customer) nào bỏ qua các cột khác. Cặp ngoặc trông rất hấp dẫn, nhưng SELECT DISTINCT(customer), country được phân tích giống hệt SELECT DISTINCT customer, country: cặp ngoặc chỉ nhóm một biểu thức. Nếu bạn thật sự muốn mỗi khách hàng một hàng với một quốc gia được chọn nào đó, đó là việc của GROUP BY cộng một hàm tổng hợp.

COUNT(DISTINCT col)

Một nhu cầu hay gặp: trong một cột có bao nhiêu giá trị khác nhau? COUNT(*) đếm số hàng, COUNT(col) đếm các giá trị khác NULL, còn COUNT(DISTINCT col) đếm các giá trị khác NULL không trùng nhau.

Năm đơn hàng, ba khách hàng khác nhau, ba quốc gia khác nhau. COUNT(DISTINCT ...) là dạng tổng hợp hữu ích nhất của DISTINCT: bạn sẽ dùng nó mỗi khi muốn đếm "có bao nhiêu thứ khác nhau đã xuất hiện."

Lưu ý SQLite chỉ cho phép một cột bên trong COUNT(DISTINCT ...). Để đếm các tổ hợp duy nhất của nhiều cột, hãy bọc chúng trong một truy vấn con: SELECT COUNT(*) FROM (SELECT DISTINCT a, b FROM t).

DISTINCT đối xử với NULL ra sao

NULL nổi tiếng kỳ quặc trong SQL vì NULL = NULL cho ra NULL, không phải TRUE. Nhưng DISTINCT có một ngoại lệ đặc biệt: khi loại trùng, mọi NULL được coi là bằng nhau.

Kết quả có ba hàng: 'ada@example.com', 'dan@example.com' và một NULL duy nhất. Ba email NULL đã gộp lại thành một. Quy tắc này cũng áp dụng cho GROUP BY và các phép toán tập hợp như UNION, rất đáng nhớ khi bạn đang truy tìm "vì sao hàng NULL kia chỉ xuất hiện một lần thay vì ba lần?"

DISTINCT chạy trước ORDER BY và LIMIT

Các mệnh đề trong một câu SELECT tuân theo một thứ tự logic: FROM → WHERE → GROUP BY → HAVING → SELECT/DISTINCT → ORDER BY → LIMIT. Vì vậy DISTINCT lọc bản trùng trước, rồi ORDER BY sắp xếp phần còn lại, rồi LIMIT cắt bớt.

WHERE giữ lại bốn hàng, DISTINCT gộp các bản trùng của Boris, ORDER BY sắp xếp theo bảng chữ cái, LIMIT trả về hai hàng đầu. Nên lần theo từng bước một lần: nhầm lẫn về thứ tự kết quả thường đến từ việc quên bước nào chạy lúc nào.

DISTINCT và GROUP BY

Nếu chỉ để loại trùng, hai truy vấn sau trả về cùng các hàng:

Kết quả như nhau. Khác biệt nằm ở những gì bạn có thể làm tiếp theo:

  • DISTINCT dành cho "cho tôi các hàng duy nhất", và chỉ vậy.
  • GROUP BY dành cho "chia hàng thành nhóm và tính gì đó cho từng nhóm": COUNT(*), SUM(amount), MAX(created_at), v.v.

Nếu bạn đang định dùng DISTINCT rồi nhận ra mình còn muốn tổng theo từng khách hàng, đó là tín hiệu để chuyển sang GROUP BY:

Mỗi khách hàng một hàng, kèm các giá trị tổng hợp bạn cần. DISTINCT không làm được điều này: nó không có cách nào diễn đạt "mỗi nhóm một hàng cộng thêm một tổng."

Vài điều cần để ý

  • Hiệu năng. DISTINCT thường buộc SQLite phải sắp xếp hoặc băm các hàng để tìm bản trùng. Với tập kết quả lớn, một chỉ mục trên các cột cần loại trùng sẽ giúp ích. Nếu bạn đang SELECT DISTINCT trên mọi cột của một bảng rộng, hãy tự hỏi bạn có thật sự cần mọi cột không.
  • DISTINCT * hiếm khi dùng. Nó hợp lệ, SELECT DISTINCT * FROM t loại trùng cả hàng, nhưng nếu bảng có khóa chính thì mọi hàng vốn đã là duy nhất, nên câu lệnh này chẳng làm được gì hữu ích.
  • Đừng nhầm với UNIQUE. UNIQUE là một ràng buộc trên bảng, ngăn giá trị trùng được chèn vào ngay từ đầu. DISTINCT là bộ lọc lúc truy vấn, che đi các bản trùng trong kết quả. Hai công cụ khác nhau cho hai việc khác nhau.

Tiếp theo: biểu thức CASE

Khi đã định hình được các hàng kết quả bằng SELECT, WHERE, ORDER BY và DISTINCT, bước tiếp theo là logic điều kiện bên trong truy vấn. Biểu thức CASE cho phép trả về các giá trị khác nhau tùy theo điều kiện, tương đương một chuỗi if/else trong SQL, và trang tiếp theo sẽ nói về chúng.

Câu hỏi thường gặp

SELECT DISTINCT hoạt động thế nào trong SQLite?

SELECT DISTINCT loại bỏ các hàng trùng lặp khỏi tập kết quả. SQLite so sánh mọi cột trong danh sách chọn và giữ lại một hàng cho mỗi tổ hợp duy nhất. Nó được áp dụng sau WHERE và JOIN nhưng trước ORDER BY và LIMIT.

Có thể dùng DISTINCT trên nhiều cột trong SQLite không?

Có: DISTINCT luôn áp dụng cho toàn bộ danh sách chọn, không phải một cột. SELECT DISTINCT city, country FROM users trả về mỗi cặp (city, country) duy nhất. Không có cú pháp DISTINCT(city) nào bỏ qua các cột khác; nếu cần điều đó, hãy dùng GROUP BY kèm một hàm tổng hợp.

DISTINCT xử lý giá trị NULL thế nào trong SQLite?

Khi loại trùng, DISTINCT coi NULL bằng các NULL khác, nên nhiều hàng có NULL sẽ gộp lại thành một. Điều này khác với cách = hoạt động trong mệnh đề WHERE, nơi NULL = NULL cho kết quả không xác định. Đây là quy tắc đặc biệt chỉ dành cho DISTINCT, GROUP BY và UNION.

DISTINCT khác GROUP BY trong SQLite ở điểm nào?

Nếu chỉ để loại trùng, SELECT DISTINCT col và SELECT col FROM t GROUP BY col cho kết quả giống hệt nhau. Khác biệt nằm ở mục đích: dùng DISTINCT khi bạn chỉ cần các hàng duy nhất, và GROUP BY khi bạn còn muốn tính các giá trị tổng hợp như COUNT(*) hay SUM(amount) cho từng nhóm.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu