Menu

Ràng buộc UNIQUE trong SQLite: một cột, nhiều cột và NULL

Cách ràng buộc UNIQUE hoạt động trong SQLite: cấp cột, cấp bảng, khóa kết hợp nhiều cột, điểm lạ với NULL và cách xử lý khi ràng buộc bị vi phạm.

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.

UNIQUE nghĩa là "không cho phép trùng lặp"

Ràng buộc UNIQUE báo cho SQLite rằng giá trị trong một cột (hoặc một nhóm cột) không được lặp lại giữa các hàng. Đó là cách bạn nói "hai người dùng không thể dùng chung một email" hoặc "một mã sản phẩm chỉ xuất hiện tối đa một lần".

Lệnh insert thứ ba thất bại với lỗi UNIQUE constraint failed: users.email. SQLite kiểm tra ràng buộc ở mỗi lần ghi và từ chối mọi thứ sẽ tạo ra giá trị trùng lặp. Hai hàng đầu được lưu; hàng thứ ba không bao giờ được ghi vào.

Phía sau, UNIQUE được cài đặt bằng một unique index, cùng cấu trúc dữ liệu SQLite dùng để tra cứu nhanh, nên việc kiểm tra rất nhẹ và cột tự động được đánh index.

Cú pháp cấp cột và cấp bảng

Bạn có thể viết UNIQUE theo hai cách. Viết ngay cạnh một cột, hoặc viết thành một mệnh đề riêng ở cuối định nghĩa bảng:

Với một cột, hai cách là tương đương: chọn cách nào dễ đọc hơn. Dạng cấp bảng trở nên bắt buộc ngay khi bạn cần tính duy nhất trên nhiều hơn một cột.

UNIQUE kết hợp: nhiều cột cùng lúc

Đôi khi một cột riêng lẻ không duy nhất, nhưng một tổ hợp các cột thì phải duy nhất. Một người dùng có thể đăng ký nhiều khóa học, và một khóa học có thể có nhiều người dùng, nhưng cùng một cặp (user_id, course_id) không được xuất hiện hai lần:

Ràng buộc áp dụng cho cặp giá trị, không cho riêng từng cột. Người dùng 1 có thể đăng ký nhiều khóa học, khóa học 100 có thể có nhiều người dùng, nhưng mỗi tổ hợp chỉ một lần.

Đây là mẫu cơ bản cho các bảng nối trong quan hệ nhiều-nhiều.

UNIQUE so với PRIMARY KEY

Hai thứ này nghe giống nhau và có liên quan, nhưng không phải là một:

  • Một bảng có tối đa một PRIMARY KEY. Nó có thể có nhiều ràng buộc UNIQUE.
  • PRIMARY KEY là định danh của hàng: thứ mà khóa ngoại trỏ tới, thứ mà rowid làm bí danh.
  • UNIQUE chỉ đơn giản là "giá trị này (hoặc tổ hợp này) không lặp lại".
  • Trong bảng thông thường, cột UNIQUE có thể chứa giá trị NULL; PRIMARY KEY thì không (trừ một ngoại lệ lịch sử mà ta sẽ bỏ qua).

Một cấu trúc thường gặp:

id là thứ các phần còn lại của cơ sở dữ liệu tham chiếu tới. email và username là duy nhất vì ứng dụng yêu cầu như vậy, không phải vì chúng là định danh. Nếu người dùng đổi email, id vẫn giữ nguyên: đó chính là lý do tách chúng ra.

Điểm lạ với NULL

Điều này làm gần như ai cũng vấp ở lần đầu. Một cột UNIQUE trong SQLite chấp nhận bao nhiêu giá trị NULL cũng được:

Ba NULL, không vấn đề gì. Hai 'ada@example.com' thì xung đột.

Lý do: SQL coi NULL là "không xác định", và hai giá trị không xác định không được coi là bằng nhau, nên phép kiểm tra tính duy nhất không thể kết luận chúng trùng nhau. Nếu bạn cần tối đa một NULL, cách gọn nhất là NOT NULL UNIQUE. Nếu NULL hợp lệ nhưng chỉ được một lần cho mỗi tổ hợp với cột khác, hãy dùng partial index (được trình bày sau trong chương về index).

Xử lý xung đột: ON CONFLICT

Mặc định, vi phạm UNIQUE sẽ hủy câu lệnh. Nhưng đôi khi bạn muốn hành vi khác: thay thế hàng hiện có, bỏ qua hàng mới, hoặc cập nhật một số cột. SQLite cho bạn hai cách để làm điều đó.

Cách thứ nhất được gắn ngay vào ràng buộc bằng ON CONFLICT:

Lần thứ hai theme được chèn, hàng hiện có bị xóa và hàng mới thế chỗ. Các lựa chọn khác là IGNORE (bỏ qua âm thầm), ABORT (mặc định), FAIL và ROLLBACK.

Cách thứ hai áp dụng theo từng câu lệnh, với cú pháp upsert, thường linh hoạt hơn vì có thể cập nhật các cột cụ thể:

Lệnh insert đầu tiên tạo hàng. Hai lệnh tiếp theo chạm vào ràng buộc UNIQUE và chuyển sang nhánh DO UPDATE, tăng count. Đây là mẫu upsert INSERT ... ON CONFLICT, và có một trang riêng về nó ở phần sau.

Ràng buộc UNIQUE so với unique index

CREATE UNIQUE INDEX làm cùng việc với ràng buộc UNIQUE. Thực tế, ràng buộc UNIQUE tự tạo một unique index phía sau: gần như cùng một cơ chế, chỉ khác cách gọi.

Khi nào dùng cách nào:

  • Ràng buộc khi tính duy nhất là một phần trong định nghĩa của bảng. Nó được ghi rõ ngay cạnh các cột.
  • Unique index khi bạn muốn một partial index (có mệnh đề WHERE), cần một cái tên cụ thể, hoặc muốn thêm vào một bảng có sẵn mà không phải viết lại bảng. ALTER TABLE của SQLite không thêm được ràng buộc, nhưng luôn có thể thêm index.

Hành vi khi ghi là giống hệt nhau. Lựa chọn chủ yếu là bạn muốn quy tắc nằm ở đâu trong schema.

Thêm UNIQUE vào bảng có sẵn

ALTER TABLE của SQLite được giới hạn có chủ đích: không có ALTER TABLE ... ADD CONSTRAINT. Hai cách làm thực tế:

Cách 2, khi bạn thực sự muốn mệnh đề UNIQUE nằm sẵn trong định nghĩa bảng, là quy trình viết lại bảng: tạo bảng mới có ràng buộc, sao chép dữ liệu sang, xóa bảng cũ, rồi đổi tên. Trang tiếp theo sẽ nói về nó.

Lưu ý: nếu bạn thêm tính duy nhất cho một cột đã có giá trị trùng lặp, lệnh CREATE UNIQUE INDEX sẽ thất bại. Hãy dọn các hàng trùng lặp trước, rồi mới thêm index.

Khi UNIQUE thất bại: đọc thông báo lỗi

Thông báo lỗi cho bạn biết chính xác ràng buộc nào bị vi phạm:

Error: UNIQUE constraint failed: users.email
Error: UNIQUE constraint failed: enrollments.user_id, enrollments.course_id

Dạng thứ nhất là ràng buộc một cột trên users.email. Dạng thứ hai là ràng buộc kết hợp: cả hai cột được liệt kê vì tổ hợp đó đã tồn tại. Khi gặp lỗi này:

  1. Tìm hàng đang giữ giá trị xung đột (SELECT ... WHERE email = '...').
  2. Quyết định bạn muốn cập nhật hàng đó, bỏ qua lệnh insert hay dùng giá trị khác.
  3. Nếu việc trùng lặp là dự kiến và bạn muốn gộp chúng, hãy chuyển sang INSERT ... ON CONFLICT DO UPDATE.

Lỗi được báo rõ ràng vì phần lớn thời gian bạn thực sự muốn biết: trùng lặp âm thầm còn tệ hơn một lần ghi thất bại.

Tiếp theo: xóa và sửa bảng

Không thể thêm ràng buộc UNIQUE vào bảng có sẵn bằng một lệnh ALTER TABLE đơn giản. Giới hạn đó là lý do SQLite có một quy trình riêng cho việc thay đổi schema, đó là viết lại bảng, và đó là chủ đề của trang tiếp theo, cùng với những điều cơ bản về cách xóa bảng gọn gàng.

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

Làm sao thêm ràng buộc UNIQUE trong SQLite?

Thêm UNIQUE vào định nghĩa cột (email TEXT UNIQUE) hoặc viết mệnh đề cấp bảng UNIQUE(col1, col2) khi cần tính duy nhất trên nhiều cột. SQLite áp dụng ràng buộc này bằng cách tự tạo một unique index phía sau và từ chối mọi lệnh INSERT hoặc UPDATE tạo ra giá trị trùng lặp.

UNIQUE và PRIMARY KEY trong SQLite khác nhau thế nào?

Một bảng chỉ có thể có một PRIMARY KEY nhưng có thể có nhiều ràng buộc UNIQUE. PRIMARY KEY còn ngầm định NOT NULL (trong bảng strict và với INTEGER PRIMARY KEY), còn cột UNIQUE có thể chứa nhiều giá trị NULL. Dùng khóa chính làm định danh của hàng, và dùng UNIQUE cho các cột khác không được trùng lặp.

Vì sao SQLite cho phép nhiều NULL trong một cột UNIQUE?

Vì SQL coi NULL là 'không xác định', và hai giá trị không xác định không được coi là bằng nhau. Vì vậy cột UNIQUE chấp nhận bao nhiêu hàng NULL cũng được, chỉ các giá trị khác NULL mới phải khác nhau. Nếu bạn cần tối đa một NULL, hãy thêm NOT NULL hoặc dùng partial unique index.

Làm sao sửa lỗi 'UNIQUE constraint failed'?

Lỗi này có nghĩa là một lệnh INSERT hoặc UPDATE sẽ tạo ra giá trị trùng lặp trong một cột UNIQUE (hoặc PRIMARY KEY). Bạn có thể đổi giá trị đang chèn, xóa hàng hiện có trước, hoặc dùng INSERT ... ON CONFLICT (một upsert) để chỉ cho SQLite biết phải làm gì khi xung đột xảy ra.

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