ràng buộc duy nhất có điều kiện


92

Tôi gặp trường hợp cần thực thi một ràng buộc duy nhất trên một tập hợp các cột, nhưng chỉ cho một giá trị của một cột.

Vì vậy, ví dụ tôi có một bảng như Table (ID, Name, RecordStatus).

RecordStatus chỉ có thể có giá trị 1 hoặc 2 (hoạt động hoặc bị xóa) và tôi muốn tạo một ràng buộc duy nhất trên (ID, RecordStatus) chỉ khi RecordStatus = 1, vì tôi không quan tâm nếu có nhiều bản ghi bị xóa cùng TÔI.

Ngoài việc viết kích hoạt, tôi có thể làm điều đó không?

Tôi đang sử dụng SQL Server 2005.


1
Thiết kế này là một nỗi đau chung. Bạn đã xem xét việc thay đổi thiết kế để các bản ghi 'đã xóa' theo danh nghĩa bị xóa khỏi bảng và có lẽ được chuyển sang bảng 'lưu trữ' chưa?
onedaywhen

1
... bởi vì không có khả năng viết một ràng buộc DUY NHẤT để thực thi một khóa đơn giản nên được coi là 'mùi mã', IMO. Nếu bạn không thể thay đổi thiết kế (SQL DDL) vì nhiều bảng khác tham chiếu đến bảng này thì tôi cá rằng kết quả là SQL DML của bạn cũng bị ảnh hưởng tức là bạn phải nhớ thêm ... AND Table.RecordStatus = 1 ' hầu hết các điều kiện tìm kiếm và điều kiện tham gia liên quan đến bảng này và gặp phải các lỗi nhỏ khi nó chắc chắn bị bỏ qua đôi khi.
onedaywhen

Câu trả lời:


36

Thêm một ràng buộc kiểm tra như thế này. Sự khác biệt là, bạn sẽ trả về false nếu Trạng thái = 1 và Đếm> 0.

http://msdn.microsoft.com/en-us/library/ms188258.aspx

CREATE TABLE CheckConstraint
(
  Id TINYINT,
  Name VARCHAR(50),
  RecordStatus TINYINT
)
GO

CREATE FUNCTION CheckActiveCount(
  @Id INT
) RETURNS INT AS BEGIN

  DECLARE @ret INT;
  SELECT @ret = COUNT(*) FROM CheckConstraint WHERE Id = @Id AND RecordStatus = 1;
  RETURN @ret;

END;
GO

ALTER TABLE CheckConstraint
  ADD CONSTRAINT CheckActiveCountConstraint CHECK (NOT (dbo.CheckActiveCount(Id) > 1 AND RecordStatus = 1));

INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 2);
INSERT INTO CheckConstraint VALUES (1, 'No Problems', 1);

INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 2);
-- Msg 547, Level 16, State 0, Line 14
-- The INSERT statement conflicted with the CHECK constraint "CheckActiveCountConstraint". The conflict occurred in database "TestSchema", table "dbo.CheckConstraint".
INSERT INTO CheckConstraint VALUES (2, 'Oh no!', 1);

SELECT * FROM CheckConstraint;
-- Id   Name         RecordStatus
-- ---- ------------ ------------
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  2
-- 1    No Problems  1
-- 2    Oh no!       1
-- 2    Oh no!       2

ALTER TABLE CheckConstraint
  DROP CONSTRAINT CheckActiveCountConstraint;

DROP FUNCTION CheckActiveCount;
DROP TABLE CheckConstraint;

Tôi đã xem xét các ràng buộc kiểm tra mức bảng nhưng không thấy có cách nào để chuyển các giá trị đang được chèn hoặc cập nhật vào hàm, bạn có biết cách làm không?
np-hard

Được rồi, tôi đã đăng một kịch bản mẫu sẽ giúp bạn chứng minh những gì tôi đang nói. Tôi đã thử nghiệm nó và nó hoạt động. Nếu bạn nhìn vào hai dòng bình luận, bạn sẽ thấy thông báo tôi nhận được. Xin lưu ý, trong quá trình triển khai của tôi, tôi chỉ đảm bảo rằng bạn không thể thêm mục thứ hai có cùng Id đang hoạt động nếu đã có một mục đang hoạt động. Bạn có thể sửa đổi logic sao cho nếu có một logic đang hoạt động, bạn không thể thêm bất kỳ mục nào có cùng id. Với mô hình này, khả năng là vô tận.
D. Patrick

Tôi muốn cùng một logic trong một trình kích hoạt. "một truy vấn trong một hàm vô hướng ... có thể tạo ra các vấn đề lớn nếu ràng buộc CHECK của bạn dựa trên một truy vấn và nếu nhiều hơn một hàng bị ảnh hưởng bởi bất kỳ cập nhật nào. Điều gì xảy ra là ràng buộc được kiểm tra một lần cho mỗi hàng trước khi câu lệnh hoàn thành . Điều đó có nghĩa là tính nguyên tử của câu lệnh bị hỏng và hàm sẽ hiển thị với cơ sở dữ liệu ở trạng thái không nhất quán. Kết quả là không đáng tin cậy và không chính xác. " Xem: blog.conchango.com/davidportas/archive/2007/02/19/…
onedaywhen

Điều đó chỉ đúng một phần vào ngày mai. Cơ sở dữ liệu hoạt động nhất quán và có thể dự đoán được. Ràng buộc kiểm tra sẽ thực hiện sau khi hàng được thêm vào bảng và trước khi giao dịch được cam kết bởi dbms và bạn có thể tin tưởng vào điều đó. Blog đó đang nói về một vấn đề khá độc đáo, nơi bạn cần thực hiện ràng buộc đối với một tập hợp các chèn thay vì chỉ một chèn một lúc. ashish đang yêu cầu một ràng buộc đối với từng phần chèn một và ràng buộc này sẽ hoạt động chính xác, dễ đoán và nhất quán. Tôi xin lỗi nếu điều này nghe có vẻ ngắn gọn; Tôi đã hết ký tự.
D. Patrick

3
Điều này hoạt động tốt cho các mục chèn nhưng dường như không hoạt động cho các bản cập nhật. EG Thêm cái này vào sau khi cái khác hoạt động ở đây khi tôi không mong đợi. CHÈN VÀO GIÁ TRỊ CheckConstraint (1, 'Không có vấn đềA', 2); cập nhật CheckConstraint set Recordstatus = 1 where name = 'No ProblemsA'
dwidel 24-08-09

147

Kìa, chỉ mục được lọc . Từ tài liệu (tôi nhấn mạnh):

Chỉ mục được lọc là một chỉ mục không phân tán được tối ưu hóa, đặc biệt phù hợp để bao hàm các truy vấn chọn từ một tập hợp con dữ liệu được xác định rõ ràng. Nó sử dụng một vị từ bộ lọc để lập chỉ mục một phần của các hàng trong bảng. Chỉ mục được lọc được thiết kế tốt có thể cải thiện hiệu suất truy vấn cũng như giảm chi phí lưu trữ và bảo trì chỉ mục so với chỉ mục toàn bảng.

Và đây là một ví dụ kết hợp một chỉ mục duy nhất với một vị từ bộ lọc:

create unique index MyIndex
on MyTable(ID)
where RecordStatus = 1;

Điều này về cơ bản thực thi tính duy nhất của IDthời điểm RecordStatus1.

Sau khi tạo chỉ mục đó, vi phạm tính duy nhất sẽ gây ra lỗi:

Msg 2601, Mức 14, Trạng thái 1, Dòng 13
Không thể chèn hàng khóa trùng lặp trong đối tượng 'dbo.MyTable' với chỉ mục duy nhất 'MyIndex'. Giá trị khóa trùng lặp là (9999).

Lưu ý: chỉ mục được lọc đã được giới thiệu trong SQL Server 2008. Đối với các phiên bản SQL Server trước, vui lòng xem câu trả lời này .


Lưu ý rằng SQL Server yêu cầu ansi_paddingcác chỉ mục được lọc, vì vậy hãy đảm bảo rằng tùy chọn này được bật bằng cách thực thi SET ANSI_PADDING ONtrước khi tạo chỉ mục được lọc.
naXa

10

Bạn có thể di chuyển các bản ghi đã xóa sang một bảng thiếu ràng buộc và có thể sử dụng dạng xem với UNION của hai bảng để giữ nguyên hình thức của một bảng.


2
Đó thực sự là một Carl thông minh. Đây không phải là một câu trả lời cho câu hỏi, nhưng đó là một giải pháp tốt. Nếu bảng có nhiều hàng, điều đó cũng có thể tăng tốc độ tìm kiếm bản ghi hiện hoạt vì bạn có thể xem bảng bản ghi hiện hoạt. Nó cũng sẽ tăng tốc độ ràng buộc bởi vì ràng buộc duy nhất sử dụng một chỉ mục trái ngược với ràng buộc kiểm tra mà tôi đã viết dưới đây phải thực hiện một số lượng. Tôi thích nó.
D. Patrick

3

Bạn có thể làm điều này một cách thực sự khó hiểu ...

Tạo một khung nhìn hỗn hợp trên bảng của bạn.

TẠO CHẾ ĐỘ XEM Dù CHỌN * TỪ Bảng WHERE RecordStatus = 1

Bây giờ, hãy tạo một ràng buộc duy nhất trên dạng xem với các trường bạn muốn.

Tuy nhiên, một lưu ý về các khung nhìn hỗn hợp, nếu bạn thay đổi các bảng bên dưới, bạn sẽ phải tạo lại khung nhìn. Có rất nhiều gotchas vì điều đó.


Đây là một gợi ý khá hay, và không phải là "hacky" đâu. Đây là thông tin thêm về thay thế chỉ mục được lọc này .
Scott Whitlock,

Đó là một ý tưởng tồi. Câu hỏi không phải là nó.
FabianoLothor

Tôi đã sử dụng chế độ xem schemabound một lần và chưa bao giờ lặp lại sai lầm. Họ có thể là một nỗi đau hoàng gia để làm việc cùng. Không phải là bạn phải tạo lại dạng xem nếu bạn thay đổi bảng bên dưới - bạn có thể phải làm điều đó cho tất cả các dạng xem, ít nhất là trong máy chủ SQL. Đó là bạn không thể thay đổi bảng mà không bỏ chế độ xem trước, điều này bạn có thể không làm được nếu không bỏ tham chiếu đến nó trước. Ồ, cộng với việc lưu trữ có thể có vấn đề - do dung lượng hoặc do chi phí thêm vào để chèn và cập nhật.
MattW

1

Bởi vì, bạn sẽ cho phép các bản sao, một ràng buộc duy nhất sẽ không hoạt động. Bạn có thể tạo ràng buộc kiểm tra cho cột RecordStatus và một thủ tục được lưu trữ cho INSERT để kiểm tra các bản ghi hoạt động hiện có trước khi chèn các ID trùng lặp.


1

Nếu bạn không thể sử dụng NULL làm RecordStatus như Bill đã đề xuất, bạn có thể kết hợp ý tưởng của anh ấy với một chỉ mục dựa trên chức năng. Tạo một hàm trả về NULL nếu RecordStatus không phải là một trong những giá trị bạn muốn xem xét trong ràng buộc của mình (và RecordStatus nếu không) và tạo một chỉ mục trên đó.

Điều đó sẽ có lợi thế là bạn không phải kiểm tra rõ ràng các hàng khác trong bảng trong ràng buộc của mình, điều này có thể gây ra các vấn đề về hiệu suất cho bạn.

Tôi nên nói rằng tôi hoàn toàn không biết máy chủ SQL, nhưng tôi đã sử dụng thành công cách tiếp cận này trong Oracle.


ý tưởng tốt, nhưng không có chức năng dựa lập chỉ mục trong SQL Server cảm ơn cho câu trả lời mặc dù
np-cứng
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.