Giải thích câu lệnh NOT IN trong MySQL: Cú pháp, Rủi ro NULL, Hiệu năng & Các thực tiễn tốt nhất

1. Điều khoản NOT IN trong MySQL là gì? — Làm cho việc loại trừ dữ liệu hiệu quả hơn

Khi làm việc với cơ sở dữ liệu trong MySQL, có rất nhiều tình huống bất ngờ mà bạn cần truy xuất dữ liệu trong khi “loại trừ” các giá trị hoặc điều kiện cụ thể. Ví dụ, bạn có thể muốn hiển thị danh sách người dùng ngoại trừ những người đã hủy đăng ký, hoặc tổng hợp dữ liệu loại trừ các ID xuất hiện trong danh sách đen. Những tình huống này thường xảy ra trong môi trường kinh doanh và phát triển. Đây là nơi mà điều khoản NOT IN trở nên cực kỳ hữu ích.

Điều khoản NOT IN là một điều kiện SQL mạnh mẽ cho phép bạn dễ dàng trích xuất chỉ dữ liệu không khớp với các giá trị được chỉ định hoặc kết quả của một truy vấn con. Ngoài việc loại trừ đơn giản sử dụng danh sách, việc kết hợp nó với các truy vấn con động cho phép các mẫu loại trừ đa dạng.

Tuy nhiên, tùy thuộc vào cách sử dụng, NOT IN có một số lưu ý và bẫy tiềm ẩn. Đặc biệt, hành vi của nó khi liên quan đến giá trị NULL, vấn đề hiệu suất trong cơ sở dữ liệu lớn, và sự khác biệt so với NOT EXISTS đều là những điểm quan trọng cần hiểu ở mức thực tế.

Trong bài viết này, chúng tôi giải thích chi tiết về điều khoản NOT IN của MySQL—từ cơ bản đến sử dụng nâng cao—cùng với các biện pháp phòng ngừa và so sánh với các phương pháp loại trừ thay thế, sử dụng các ví dụ cụ thể. Dù bạn mới bắt đầu với SQL hay đã làm việc với nó thường xuyên, hướng dẫn này cung cấp những insight quý giá. Hãy đọc đến cuối và sử dụng kiến thức này để cải thiện kỹ năng SQL và tối ưu hóa quy trình làm việc của bạn.

2. Cú pháp cơ bản và ví dụ sử dụng của NOT IN

Điều khoản NOT IN trong MySQL được sử dụng khi bạn muốn truy xuất các bản ghi không khớp với bất kỳ giá trị nào trong số các giá trị được chỉ định. Cú pháp của nó đơn giản, nhưng trong các tình huống thực tế, nó chứng tỏ hữu ích trong nhiều trường hợp. Ở đây, chúng tôi giới thiệu cú pháp cơ bản và các ví dụ thực tế.

[Basic Syntax]

SELECT column_name FROM table_name WHERE column_name NOT IN (value1, value2, ...);

Loại trừ sử dụng danh sách đơn giản

Ví dụ, nếu bạn muốn truy xuất người dùng có tên không phải “Yamada” hoặc “Sato,” bạn sẽ viết câu lệnh SQL sau:

SELECT * FROM users WHERE name NOT IN ('Yamada', 'Sato');

Việc thực thi truy vấn này sẽ truy xuất tất cả bản ghi người dùng ngoại trừ những người có tên “Yamada” và “Sato.” Vì danh sách loại trừ chỉ yêu cầu các giá trị phân cách bằng dấu phẩy, nó dễ viết và hiểu.

Loại trừ động sử dụng truy vấn con

Điều khoản NOT IN cũng có thể sử dụng truy vấn con bên trong dấu ngoặc, không chỉ danh sách cố định. Điều này đặc biệt hữu ích khi bạn muốn loại trừ các ID người dùng đáp ứng các điều kiện cụ thể.

SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM blacklist WHERE is_active = 1);

Trong ví dụ này, các ID người dùng được đánh dấu là hoạt động trong bảng blacklist (is_active = 1) bị loại trừ, và các người dùng còn lại được truy xuất từ bảng users. Bằng cách kết hợp NOT IN với truy vấn con, bạn có thể linh hoạt thích ứng với các yêu cầu logic kinh doanh đa dạng.

Áp dụng nhiều điều kiện

Nếu bạn cần chỉ định các điều kiện loại trừ trên nhiều cột đồng thời, NOT IN chủ yếu được thiết kế cho sử dụng một cột. Tuy nhiên, bằng cách kết hợp nó với truy vấn con hoặc joins (JOIN), bạn có thể xử lý các điều kiện phức tạp hơn. Chúng tôi sẽ giải thích chi tiết điều này trong phần kỹ thuật nâng cao sau.

Như bạn thấy, điều khoản NOT IN cực kỳ hữu ích khi bạn muốn truy xuất tất cả bản ghi ngoại trừ những bản ghi được bao gồm trong danh sách được chỉ định hoặc kết quả truy vấn con. Hãy bắt đầu bằng cách hình dung dữ liệu bạn muốn trích xuất, và thực hành sử dụng cả danh sách loại trừ đơn giản và truy vấn con một cách hiệu quả.

3. Những lưu ý quan trọng khi có giá trị NULL

Khi sử dụng điều khoản NOT IN, một vấn đề thường bị bỏ qua là hành vi của nó khi liên quan đến giá trị NULL. Đây là một “bẫy” cổ điển có thể gây ra lỗi không chỉ cho người mới bắt đầu mà thậm chí cho cả người dùng SQL có kinh nghiệm.

Lý do là logic đánh giá của NOT IN khác với các phép so sánh thông thường — nó hoạt động khác khi các giá trị NULL được bao gồm.

Hành vi Khi NULL Được Bao Gồm

Giả sử chúng ta có các bảng sau:

-- users table
id | name
---+------
 1 | Sato
 2 | Yamada
 3 | Suzuki
 4 | Tanaka

-- blacklist table
user_id
--------
1
NULL

Bây giờ hãy xem xét việc thực thi câu lệnh SQL sau:

SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM blacklist);

Nhìn sơ qua, có thể nghĩ rằng tất cả người dùng ngoại trừ user_id = 1 (tức là id = 2, 3, 4) sẽ được trả về. Tuy nhiên, thực tế không có hàng nào được trả về.

Tại sao không có hàng nào được trả về?

Nguyên nhân nằm ở logic ba giá trị của SQL (TRUE / FALSE / UNKNOWN).
Khi NULL được bao gồm trong danh sách NOT IN, kết quả so sánh trở thành UNKNOWN, và MySQL không bao gồm những hàng đó trong tập kết quả.

Nói cách khác, vì không thể xác định chắc chắn rằng một giá trị không khớp với bất kỳ mục nào trong danh sách, điều kiện tổng thể sẽ đánh giá là false.

Các Kịch Bản Gặp Lỗi Thường Gặp

Vấn đề này thường xuất hiện khi sử dụng các subquery. Nếu có giá trị NULL trong danh sách đen hoặc danh sách hủy đăng ký, dữ liệu có thể không được truy xuất như mong đợi.

Các vấn đề như “không có dữ liệu được trả về” hoặc “bản ghi không được loại trừ đúng cách” thường bắt nguồn từ các giá trị NULL ẩn.

Các Biện Pháp Đối Phó và Giải Pháp

Để ngăn ngừa các vấn đề do giá trị NULL gây ra, bạn phải loại bỏ NULL khỏi danh sách NOT IN. Cụ thể, thêm điều kiện IS NOT NULL vào bên trong subquery.

SELECT * FROM users
WHERE id NOT IN (
  SELECT user_id FROM blacklist WHERE user_id IS NOT NULL
);

Với điều chỉnh này, ngay cả khi bảng blacklist chứa các giá trị NULL, truy vấn vẫn sẽ đúng cách lấy được những người dùng không nằm trong blacklist.

Các Điểm Chính

  • Nếu NULL tồn tại trong danh sách NOT IN, truy vấn có thể trả về không có hàng nào
  • Luôn kết hợp các subquery với IS NOT NULL khi sử dụng NOT IN
  • Nếu dữ liệu bất ngờ thiếu, hãy kiểm tra các giá trị NULL ẩn trước tiên

4. NOT IN vs NOT EXISTS — So Sánh Các Phương Pháp Thay Thế

Khi chỉ định các điều kiện loại trừ trong MySQL, NOT EXISTS là một lựa chọn phổ biến khác cho NOT IN. Mặc dù cả hai đều có thể đạt được kết quả tương tự, chúng khác nhau về hành vi, cách xử lý NULL và các đặc điểm hiệu suất. Trong phần này, chúng ta sẽ so sánh NOT INNOT EXISTS, đồng thời giải thích các ưu nhược điểm của chúng.

So Sánh Cú Pháp Cơ Bản

[Exclusion Using NOT IN]

SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM blacklist WHERE user_id IS NOT NULL);

[Exclusion Using NOT EXISTS]

SELECT * FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM blacklist b WHERE b.user_id = u.id
);

Cả hai truy vấn đều lấy những người dùng không được đăng ký trong blacklist.

Xử Lý Giá Trị NULL

NOT IN

  • Nếu NULL được bao gồm trong danh sách hoặc kết quả subquery, truy vấn có thể không hoạt động như mong đợi (có thể trả về không có hàng nào)
  • Cần một điều kiện IS NOT NULL rõ ràng để bảo vệ

NOT EXISTS

  • Hoạt động đúng ngay cả khi kết quả subquery chứa NULL
  • Thông thường an toàn hơn vì không bị ảnh hưởng bởi giá trị NULL

Sự Khác Biệt Về Hiệu Suất

Cách tiếp cận tối ưu phụ thuộc vào khối lượng dữ liệu và cấu trúc bảng, nhưng nói chung:

  • Đối với tập dữ liệu nhỏ hoặc danh sách cố định, NOT IN hoạt động đủ tốt
  • Đối với các subquery lớn hoặc điều kiện phức tạp, NOT EXISTS hoặc LEFT JOIN thường mang lại hiệu suất tốt hơn

Khi số lượng bản ghi trong blacklist tăng lên, NOT EXISTS thường trở nên hiệu quả hơn. Tùy thuộc vào phiên bản MySQL và chỉ mục, NOT EXISTS có thể rất nhanh khi có các chỉ mục phù hợp, vì nó thực hiện kiểm tra tồn tại cho mỗi hàng.

Hướng Dẫn Lựa Chọn

  • Nếu có khả năng xuất hiện giá trị NULL → Sử dụng NOT EXISTS
  • Nếu loại trừ một danh sách cố định hoặc các giá trị đơn giảnNOT IN là đủ
  • Nếu hiệu suất là yếu tố quan trọng → Kiểm tra kế hoạch thực thi bằng EXPLAIN và chọn phù hợp (cân nhắc JOIN hoặc NOT EXISTS)

Các Trường Hợp Mẫu

Ví dụ Gây Vấn Đề Khi Sử Dụng NOT IN

-- If blacklist.user_id contains NULL
SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM blacklist);
-- → May return zero rows

Ví dụ Loại Trừ An Toàn Khi Sử Dụng NOT EXISTS

SELECT * FROM users u
WHERE NOT EXISTS (
  SELECT 1 FROM blacklist b WHERE b.user_id = u.id
);
-- → Correct results regardless of NULL values

Tóm Tắt

  • NOT IN đơn giản nhưng dễ bị ảnh hưởng bởi giá trị NULL
  • NOT EXISTS mạnh mẽ trước NULL và được sử dụng rộng rãi trong môi trường sản xuất
  • Lựa chọn dựa trên đặc điểm dữ liệu và hiệu năng yêu cầu

5. Các Xem Xét Về Hiệu Năng

Khi làm việc với các bộ dữ liệu lớn trong SQL, hiệu năng truy vấn là vô cùng quan trọng. Tùy thuộc vào các điều kiện và khối lượng dữ liệu, việc sử dụng NOT IN hoặc NOT EXISTS có thể tạo ra sự khác biệt đáng kể về tốc độ thực thi. Trong phần này, chúng tôi tập trung vào tác động về hiệu năng của mệnh đề NOT IN, cùng với các mẹo tối ưu hoá và những lưu ý quan trọng.

Đặc Điểm Hiệu Năng của NOT IN

Mệnh đề NOT IN lấy các bản ghi không khớp với bất kỳ giá trị nào trong một danh sách hoặc kết quả truy vấn phụ được chỉ định. Nó hoạt động hiệu quả với các danh sách hoặc bảng nhỏ, nhưng có thể chậm lại trong các tình huống sau:

  • Khi truy vấn phụ trả về số lượng hàng lớn
  • Khi cột bị loại trừ không có chỉ mục
  • Khi có giá trị NULL trong kết quả truy vấn phụ

Đặc biệt, nếu truy vấn phụ chứa hàng chục nghìn hoặc hàng trăm nghìn hàng và không có chỉ mục nào được định nghĩa, MySQL có thể thực hiện so sánh toàn bộ, dẫn đến việc chậm lại đáng kể.

Tầm Quan Trọng Của Việc Đánh Chỉ Mục

Thêm một chỉ mục vào cột được dùng để loại trừ (ví dụ, user_id) cho phép MySQL thực hiện so sánh và lọc hiệu quả hơn. Các cột được sử dụng trong truy vấn phụ hoặc các phép join nên được đánh chỉ mục khi thích hợp.

CREATE INDEX idx_blacklist_user_id ON blacklist(user_id);

Bằng cách thêm một chỉ mục như vậy, hiệu năng của các truy vấn NOT INNOT EXISTS có thể được cải thiện đáng kể.

So Sánh Hiệu Năng: NOT IN vs NOT EXISTS

  • Danh sách nhỏ, cố định: NOT IN thường nhanh
  • Truy vấn phụ lớn: NOT EXISTS hoặc LEFT JOIN thường hiệu quả hơn

Vì kế hoạch thực thi của MySQL (kết quả EXPLAIN) thay đổi tùy theo phiên bản và thiết kế bảng, việc tối ưu hoá hiệu năng luôn cần phải thực hiện kiểm thử thực tế.

Kiểm Tra Kế Hoạch Thực Thi Bằng EXPLAIN

Để xác định truy vấn nào thực thi tốt hơn, sử dụng lệnh EXPLAIN của MySQL:

EXPLAIN SELECT * FROM users WHERE id NOT IN (SELECT user_id FROM blacklist WHERE user_id IS NOT NULL);

Điều này cho phép bạn xem các chỉ mục nào được sử dụng và liệu có bảng nào đang được quét toàn bộ hay không — thông tin này ảnh hưởng trực tiếp đến hiệu năng.

Chiến Lược Tối Ưu Cho Dữ Liệu Lớn

  • Lưu kết quả trung gian vào bảng tạm để giảm tải truy vấn phụ
  • Sử dụng xử lý theo lô hoặc bộ nhớ đệm nếu hiệu năng vẫn chưa đủ
  • Viết lại bằng LEFT JOIN ... IS NULL (trong một số trường hợp cách này tăng tốc)

Các Điểm Chính

  • NOT IN có thể chậm khi truy vấn phụ lớn hoặc thiếu chỉ mục
  • Thiết kế chỉ mục hợp lý và rà soát truy vấn có thể cải thiện hiệu năng đáng kể
  • Xem xét sử dụng NOT EXISTS hoặc LEFT JOIN, và luôn xác minh kết quả bằng EXPLAIN

Trong môi trường sản xuất, luôn chọn truy vấn phù hợp nhất dựa trên quy mô dữ liệu và tần suất sử dụng.

6. Các Trường Hợp Sử Dụng Thông Thường và Kỹ Thuật Nâng Cao

Mệnh đề NOT IN không chỉ giới hạn ở các loại trừ đơn giản. Với các kỹ thuật nâng cao, bạn có thể thực hiện việc trích xuất dữ liệu linh hoạt hơn. Ở đây chúng tôi giới thiệu các mẫu thường dùng và các kỹ thuật thực tiễn.

Loại Trừ Nhiều Cột (Loại Trừ Khóa Tổng Hợp)

Mặc dù NOT IN thường được dùng cho một cột duy nhất, nhưng có những trường hợp bạn cần loại trừ các kết hợp của nhiều cột. Trong những tình huống này, NOT EXISTS hoặc LEFT JOIN phù hợp hơn.

[Ví dụ: Loại trừ các kết hợp cụ thể của customer_id và product_id khỏi bảng orders]

SELECT * FROM orders o
WHERE NOT EXISTS (
  SELECT 1 FROM blacklist b
  WHERE b.customer_id = o.customer_id
    AND b.product_id = o.product_id
);

Điều này loại trừ tất cả các kết hợp “customer_id × product_id” đã được đăng ký trong danh sách đen.

Loại trừ khớp một phần (Sử dụng NOT LIKE)

NOT IN chỉ hoạt động với các khớp chính xác, hãy sử dụng NOT LIKE khi loại trừ các mẫu chuỗi cụ thể. Ví dụ, để loại trừ người dùng có địa chỉ email bắt đầu bằng “test@”:

SELECT * FROM users WHERE email NOT LIKE 'test@%';

Để loại trừ nhiều mẫu cùng một lúc, kết hợp các điều kiện bằng AND:

SELECT * FROM users
WHERE email NOT LIKE 'test@%'
  AND email NOT LIKE 'sample@%';

Xử lý danh sách loại trừ lớn

Liệt kê hàng trăm hoặc hàng nghìn giá trị trực tiếp trong NOT IN làm giảm khả năng đọc và có thể ảnh hưởng đến hiệu suất.

Trong những trường hợp như vậy, hãy sử dụng một bảng riêng hoặc truy vấn phụ để quản lý danh sách loại trừ một cách sạch sẽ hơn:

-- Example: Store exclusion list in blacklist table
SELECT * FROM users
WHERE id NOT IN (SELECT user_id FROM blacklist WHERE user_id IS NOT NULL);

Kết hợp với các hàm tổng hợp

Bạn cũng có thể sử dụng NOT IN với các truy vấn phụ chứa các điều kiện tổng hợp.

[Ví dụ: Lấy danh sách khách hàng không đặt hàng trong tháng này]

SELECT * FROM customers
WHERE id NOT IN (
  SELECT customer_id FROM orders
  WHERE order_date >= '2025-06-01'
    AND order_date < '2025-07-01'
);

Sử dụng JOIN Thay vì Truy vấn phụ

Trong một số trường hợp, bạn có thể đạt được kết quả tương tự bằng cách sử dụng LEFT JOIN kết hợp với IS NULL.

Hãy chọn phương pháp phù hợp nhất dựa trên hiệu suất và khả năng đọc.

SELECT u.*
FROM users u
LEFT JOIN blacklist b ON u.id = b.user_id
WHERE b.user_id IS NULL;

Cách tiếp cận này đặc biệt hữu ích khi hiệu suất của truy vấn phụ không chắc chắn hoặc khi các chỉ mục có hiệu quả.

Các điểm chính

  • Sử dụng NOT EXISTS hoặc JOIN để loại trừ đa cột
  • Kết hợp với NOT LIKE để loại trừ chuỗi một phần
  • Quản lý danh sách loại trừ lớn bằng bảng hoặc truy vấn phụ
  • JOIN + IS NULL cũng có thể cải thiện hiệu suất

7. Câu hỏi thường gặp (FAQ)

Dưới đây là một số câu hỏi thường gặp và các vấn đề phổ biến liên quan đến mệnh đề MySQL NOT IN. Các câu trả lời tập trung vào các vấn đề thực tiễn thường được tìm kiếm trong các tình huống thực tế.

Q1. Sự khác biệt giữa NOT ININ là gì?

A.
IN lấy dữ liệu khớp với bất kỳ giá trị nào trong danh sách được chỉ định, trong khi NOT IN chỉ lấy dữ liệu không khớp với bất kỳ giá trị nào trong danh sách. Cú pháp của chúng gần như giống nhau, nhưng nếu bạn muốn loại trừ một số giá trị, bạn nên sử dụng NOT IN.

Q2. Điều gì xảy ra nếu có giá trị NULL khi sử dụng NOT IN?

A.
Nếu các giá trị NULL được bao gồm trong danh sách hoặc truy vấn phụ, NOT IN có thể trả về không có dòng nào hoặc tạo ra kết quả không mong muốn. An toàn nhất là loại trừ NULL một cách rõ ràng bằng cách sử dụng IS NOT NULL.

Q3. Làm thế nào để chọn giữa NOT INNOT EXISTS?

A.

  • Nếu có khả năng xuất hiện giá trị NULL hoặc có truy vấn phụ , NOT EXISTS đáng tin cậy hơn.
  • Đối với danh sách cố định hoặc các loại trừ đơn giản , NOT IN hoạt động tốt.
  • Vì hiệu suất có thể thay đổi tùy thuộc vào kế hoạch thực thi và khối lượng dữ liệu, hãy chọn dựa trên kịch bản cụ thể của bạn.

Q4. Đôi khi các truy vấn sử dụng NOT IN chậm. Tôi có thể làm gì?

A.

  • Thêm chỉ mục vào cột được sử dụng trong điều kiện loại trừ
  • Giảm kích thước kết quả truy vấn phụ hoặc tổ chức dữ liệu vào bảng tạm
  • Xem xét viết lại truy vấn bằng cách sử dụng NOT EXISTS hoặc LEFT JOIN ... IS NULL
  • Sử dụng EXPLAIN để phân tích kế hoạch thực thi và xác định các nút thắt

Q5. Làm thế nào tôi có thể loại trừ dựa trên nhiều cột?

A.
NOT IN được thiết kế cho việc sử dụng với cột đơn, hãy sử dụng NOT EXISTS hoặc LEFT JOIN khi bạn cần loại trừ tổng hợp trên nhiều cột. Kết hợp các điều kiện cột nhiều trong truy vấn con.

Q6. Tôi nên cẩn thận điều gì khi truy vấn con trả về nhiều hàng?

A.
Khi một truy vấn con trả về số lượng hàng lớn, NOT IN có thể gặp phải sự suy giảm hiệu suất. Sử dụng lập chỉ mục, bảng tạm thời, hoặc cấu trúc lại truy vấn để giữ cho truy vấn con nhỏ nhất có thể.

Q7. Nếu tôi không nhận được kết quả mong đợi, tôi nên kiểm tra gì?

A.

  • Xác minh rằng không có giá trị NULL nào được bao gồm một cách không chủ ý
  • Chạy truy vấn con độc lập để xác nhận kết quả của nó
  • Kiểm tra lỗi trong các điều kiện WHERE hoặc logic JOIN
  • Xem xét hành vi cụ thể theo phiên bản MySQL và tài liệu chính thức nếu cần

8. Kết luận

Mệnh đề NOT IN của MySQL là một cấu trúc rất hữu ích để lấy dữ liệu một cách hiệu quả không đáp ứng các điều kiện cụ thể. Từ danh sách loại trừ đơn giản đến lọc linh hoạt với truy vấn con, nó có thể được áp dụng trong nhiều tình huống thực tế.

Tuy nhiên, có những lưu ý quan trọng trong sử dụng thực tế, chẳng hạn như xử lý giá trị NULL và suy giảm hiệu suất trong tập dữ liệu lớn. Các vấn đề như truy vấn kết quả bằng không bất ngờ do giá trị NULL hoặc thực thi chậm do truy vấn con lớn đòi hỏi sự chú ý từ cả người mới bắt đầu và lập trình viên có kinh nghiệm.

Bằng cách hiểu thêm các cách tiếp cận thay thế như NOT EXISTSLEFT JOIN ... IS NULL, bạn có thể viết các truy vấn SQL an toàn và hiệu quả hơn. Luôn chọn phương pháp phù hợp nhất dựa trên mục tiêu và quy mô dữ liệu của bạn.

Điểm chính cần ghi nhớ

  • NOT IN hiệu quả cho các điều kiện loại trừ đơn giản
  • Luôn bảo vệ chống lại giá trị NULL (hãy biến IS NOT NULL thành thói quen)
  • Nếu hiệu suất là mối quan tâm, hãy xem xét chiến lược lập chỉ mục hoặc sử dụng NOT EXISTS và các lựa chọn JOIN thay thế
  • Luôn xác minh hiệu quả bằng cách sử dụng kế hoạch thực thi (EXPLAIN)

Tránh các “bẫy” SQL và thực hành trích xuất dữ liệu thông minh bằng cách áp dụng các khái niệm được đề cập trong bài viết này vào công việc hàng ngày và việc học của bạn.