Cách sửa lỗi MySQL ERROR 1242 (21000): Subquery returns more than 1 row

intermediate🗄️ MySQL2026-07-18| MySQL 5.7, MySQL 8.0+, MariaDB (Ubuntu, CentOS, Windows, macOS)

Error Message

ERROR 1242 (21000): Subquery returns more than 1 row
#mysql#cơ sở dữ liệu#tối ưu hóa sql#khắc phục sự cố

Thông báo lỗi

Có khả năng bạn đang chạy một báo cáo phức tạp hoặc cập nhật các bản ghi thì MySQL đột ngột dừng lại. Terminal hoặc nhật ký ứng dụng (logs) sẽ hiển thị thông báo cụ thể này:

ERROR 1242 (21000): Subquery returns more than 1 row

Tại sao lỗi này xảy ra

Về cốt lõi, đây là sự không khớp về logic. MySQL mong đợi một scalar value (giá trị đơn)—một mẩu dữ liệu duy nhất như con số 5 hoặc chuỗi 'Active'—nhưng subquery của bạn lại trả về một danh sách. Điều này thường xảy ra trong ba tình huống cụ thể:

  • Bạn đã sử dụng một toán tử so sánh (=, <, >) vốn chỉ hoạt động với một giá trị duy nhất.
  • Một subquery được đặt bên trong danh sách SELECT để tạo ra một cột đơn lẻ.
  • Subquery đóng vai trò là đối số cho một hàm không hỗ trợ mảng (array) hoặc tập hợp (set).

Nếu logic cơ sở dữ liệu của bạn giả định một người dùng chỉ có một gói đăng ký (subscription) đang hoạt động, nhưng dữ liệu thực tế lại chứa ba gói, truy vấn sẽ bị lỗi ngay lập tức.

Các bước khắc phục

Cách 1: Sử dụng mệnh đề LIMIT 1

Khi bạn chỉ cần bản ghi gần đây nhất hoặc một giá trị đại diện duy nhất, LIMIT 1 là giải pháp trực tiếp nhất. Nó buộc subquery phải dừng lại sau khi tìm thấy kết quả khớp đầu tiên, đáp ứng yêu cầu về giá trị đơn (scalar).

Ví dụ về một truy vấn bị lỗi:

SELECT name, 
       (SELECT order_date FROM orders WHERE orders.user_id = users.id) as last_order
FROM users;

Nếu người dùng #402 đã đặt 12 đơn hàng, subquery sẽ trả về 12 ngày và truy vấn sẽ bị lỗi. Cách khắc phục:

SELECT name, 
       (SELECT order_date FROM orders 
        WHERE orders.user_id = users.id 
        ORDER BY order_date DESC LIMIT 1) as last_order
FROM users;

Cách 2: Chuyển từ = sang IN

Toán tử bằng (=) rất nghiêm ngặt; nó yêu cầu một kết quả khớp duy nhất. Nếu subquery của bạn có thể trả về một tập hợp các ID, hãy thay đổi dấu bằng thành toán tử IN. IN được thiết kế đặc biệt để xử lý các danh sách giá trị.

Ví dụ về một truy vấn bị lỗi:

SELECT * FROM products 
WHERE category_id = (SELECT id FROM categories WHERE name = 'Electronics');

Nếu cửa hàng của bạn có các danh mục 'Electronics' cho cả 'Retail' (Bán lẻ) và 'Wholesale' (Bán buôn) (ID 5 và 12), subquery sẽ trả về cả hai. Cách khắc phục:

SELECT * FROM products 
WHERE category_id IN (SELECT id FROM categories WHERE name = 'Electronics');

Cách 3: Sử dụng các hàm tổng hợp (Aggregate Functions)

Đôi khi bạn không muốn một hàng ngẫu nhiên; bạn muốn một bản tóm tắt. Các hàm tổng hợp như MAX(), MIN(), hoặc SUM() sẽ nén nhiều hàng thành một giá trị đơn (scalar) một cách tự nhiên. Điều này lý tưởng cho dữ liệu tài chính hoặc dữ liệu theo trình tự thời gian.

Ví dụ về một truy vấn bị lỗi:

UPDATE employees 
SET salary = (SELECT amount FROM bonuses WHERE employee_id = 101)
WHERE id = 101;

Nếu nhân viên 101 nhận được ba khoản tiền thưởng khác nhau trong tháng này, lệnh cập nhật sẽ thất bại. Cách khắc phục (sử dụng SUM):

UPDATE employees 
SET salary = salary + (SELECT SUM(amount) FROM bonuses WHERE employee_id = 101)
WHERE id = 101;

Cách 4: Sử dụng EXISTS để kiểm tra sự tồn tại

Nếu bạn chỉ đang kiểm tra xem một bản ghi liên quan có tồn tại hay không, hãy tránh hoàn toàn việc so sánh subquery. Thay vào đó, hãy sử dụng EXISTS. Nó có hiệu suất tốt hơn vì sẽ ngừng tìm kiếm ngay khi tìm thấy một kết quả khớp duy nhất. Nó cũng bỏ qua các lỗi đếm hàng vì nó trả về giá trị boolean (true/false) thay vì dữ liệu.

Ví dụ về một truy vấn bị lỗi:

SELECT * FROM articles 
WHERE id = (SELECT article_id FROM comments WHERE status = 'pending');

Cách khắc phục:

SELECT * FROM articles a 
WHERE EXISTS (SELECT 1 FROM comments c WHERE c.article_id = a.id AND c.status = 'pending');

Cách 5: Kiểm tra dữ liệu trùng lặp

Đôi khi mã SQL hoàn hảo, nhưng dữ liệu lại lộn xộn. Nếu một bảng đáng lẽ phải là duy nhất (như user_profiles) lại chứa hai hàng cho cùng một user_id, Lỗi 1242 sẽ xuất hiện. Đây thường là dấu hiệu của việc thiếu ràng buộc (constraint) trong cơ sở dữ liệu.

Tìm kiếm các bản ghi trùng lặp bằng mẫu sau:

SELECT user_id, COUNT(*) 
FROM profiles 
GROUP BY user_id 
HAVING COUNT(*) > 1;

Sau khi dọn dẹp các bản ghi trùng lặp, hãy áp dụng ràng buộc UNIQUE cho cột đó để ngăn lỗi quay lại trong tương lai.

Xác minh

Hãy kiểm tra cách khắc phục của bạn trong một môi trường an toàn như MySQL Workbench hoặc TablePlus trước khi triển khai. Kiểm tra ba điểm sau:

  • Xác nhận rằng thông báo lỗi đã biến mất.
  • Xác minh số lượng hàng. Sử dụng IN đôi khi có thể trả về nhiều hàng hơn dự định nếu subquery quá rộng.
  • Kiểm tra kỹ logic ORDER BY nếu bạn sử dụng LIMIT 1. Đảm bảo bạn đang lấy được bản ghi "mới nhất" hoặc "rẻ nhất" thay vì chỉ là một bản ghi ngẫu nhiên.

Mẹo chuyên nghiệp để phòng ngừa

  • Mặc định sử dụng LIMIT 1: Trong các scalar subquery, việc thêm LIMIT 1 đóng vai trò như một lưới an toàn chống lại sự tăng trưởng dữ liệu không mong muốn.
  • Ưu tiên sử dụng JOIN: Bất cứ khi nào có thể, hãy sử dụng một LEFT JOIN hoặc INNER JOIN. Chúng thường nhanh hơn và dễ gỡ lỗi hơn nhiều so với các subquery lồng nhau.
  • Thực thi tính duy nhất: Sử dụng các chỉ mục UNIQUE trên các cột đại diện cho mối quan hệ một-một. Điều này ngăn chặn lỗi ngay tại nguồn—điểm nhập dữ liệu.
  • Kiểm tra khả năng mở rộng: Một truy vấn hoạt động với 5 hàng dữ liệu thử nghiệm có thể bị lỗi với 50.000 hàng dữ liệu thực tế. Luôn kiểm tra với các tập dữ liệu thực tế.

Related Error Notes