2009-05-14 42 views
72

Tôi có một tình huống mà tôi cần phải 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.ràng buộc duy nhất có điều kiện

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

RecordStatus chỉ có thể có giá trị 1 hoặc 2 (hoạt động hoặc đã 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 mục bị xóa bản ghi có cùng ID.

Ngoài việc viết trình 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 phổ biến. Bạn đã xem xét việc thay đổi thiết kế sao cho các bản ghi 'xóa' không chính thức bị xóa khỏi bảng và có thể được chuyển sang bảng 'lưu trữ'? – onedaywhen

+0

... vì không có khả năng viết một ràng buộc UNIQUE để thực thi một khóa đơn giản nên được coi là một 'mã mùi', 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 sẽ cược rằng DML SQL của bạn cũng bị kết quả, nghĩa là bạn phải nhớ thêm ... AND Table.RecordStatus = 1 ' cho hầu hết các điều kiện tìm kiếm và tham gia các điều kiện liên quan đến bảng này và gặp phải các lỗi tinh vi khi nó chắc chắn bị bỏ qua vào dịp này. – onedaywhen

Trả lời

33

Thêm 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 Status = 1 và Count> 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; 
+0

tôi nhìn vào kiểm tra mức bảng kiểm tra nhưng không nhìn có cách nào để vượt qua các giá trị được chèn vào hoặc cập nhật cho chức năng, bạn có biết làm thế nào để? –

+0

OK, tôi đã đăng một tập lệnh mẫu sẽ giúp bạn chứng minh những gì tôi đang nói đến. Tôi đã thử nghiệm nó và nó hoạt động. Nếu bạn nhìn vào hai dòng nhận xét, bạn sẽ thấy thông báo tôi nhận được. Nota bene, trong quá trình thực hiện 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 hoạt động. Bạn có thể sửa đổi logic sao cho nếu có một 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. –

+0

Tôi thích cùng một logic trong 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à nguyên tử tuyên bố bị phá vỡ và chức năng sẽ được tiếp xúc với cơ sở dữ liệu trong trạng thái không phù hợp. Kết quả là không thể tin được và không chính xác. " Xem: http://blogs.conchango.com/davidportas/archive/2007/02/19/Trouble-with-CHECK-Constraints.aspx – onedaywhen

1

Bởi vì, bạn sẽ cho phép trùng lặp, 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 lưu sẵn cho INSERT để kiểm tra các bản ghi hiện hoạt trước khi chèn các ID trùng lặp.

9

Bạn có thể di chuyển các bản ghi đã xóa sang bảng thiếu ràng buộc và có thể sử dụng chế độ xem với UNION của hai bảng để duy trì sự xuất hiện của một bảng.

+2

Điều đó thực sự khá thông minh Carl. Nó không phải là 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 hoạt động vì bạn có thể xem bảng bản ghi đang hoạt động. 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 bên dưới mà phải thực thi một số đếm. Tôi thích nó. –

3

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

Tạo một cái nhìn schemabound trên bàn của bạn.

CREATE VIEW Dù SELECT * FROM Bảng ĐÂU RecordStatus = 1

Bây giờ, tạo một hạn chế duy nhất trên quan điểm với các trường bạn muốn.

Một lưu ý về chế độ xem mặc định, nếu bạn thay đổi các bảng bên dưới, bạn sẽ phải tạo lại chế độ xem. Rất nhiều gotchas vì điều đó.

+0

Đây là một gợi ý khá hay và không phải là "hacky". Dưới đây là thông tin thêm về [thay thế chỉ mục được lọc] này (http://noelmckinney.com/2010/10/filtered-index-alternative/). –

+0

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

+0

Tôi đã sử dụng chế độ xem sơ đồ một lần và chưa bao giờ lặp lại lỗi. Họ có thể là một nỗi đau hoàng gia để làm việc với. Nó không phải là bạn phải tạo lại khung nhìn nếu bạn thay đổi bảng bên dưới - bạn có khả năng phải làm điều đó cho tất cả các khung nhìn, ít nhất là trong máy chủ SQL. Đó là bạn không thể thay đổi bảng mà không làm giảm chế độ xem mà bạn có thể không thực hiện được mà không cần phải tham chiếu lần đầu vào nó. Oh, cộng với việc lưu trữ có thể có vấn đề - hoặc vì không gian, hoặc vì chi phí nó thêm vào để chèn và cập nhật. – MattW

1

Nếu bạn không thể sử dụng NULL làm RecordStatus như đề xuất của Bill, bạn có thể kết hợp ý tưởng của mình với chỉ mục dựa trên hàm. Tạo một hàm trả về NULL nếu RecordStatus không phải là một trong các giá trị mà bạn muốn xem xét trong ràng buộc của bạn (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 có ràng buộc của bạn, điều này có thể khiến bạn gặp phải vấn đề về hiệu suất.

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

+0

ý tưởng hay, nhưng không có chức năng nào được lập chỉ mục trong máy chủ sql cảm ơn câu trả lời mặc dù –

104

Behold, the filtered index. Từ tài liệu (nhấn mạnh mỏ):

Chỉ mục được lọc là một chỉ mục không được tối ưu hóa đặc biệt phù hợp với các truy vấn bao gồm từ tập con dữ liệu được xác định rõ. 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 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í bảo trì và lưu 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ỉ số duy nhất với một vị bộ lọc:

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

này về cơ bản thực thi độc đáo của ID khi RecordStatus1.

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

+0

Lưu ý rằng SQL Server yêu cầu 'ansi_padding' cho chỉ mụ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 ON' trước tạo chỉ mục được lọc. – naXa

Các vấn đề liên quan