Mam sytuację, w której muszę wymusić unikalne ograniczenie na zestawie kolumn, ale tylko dla jednej wartości kolumny.
Na przykład mam tabelę taką jak Table (ID, Name, RecordStatus).
RecordStatus może mieć tylko wartość 1 lub 2 (aktywny lub usunięty) i chcę utworzyć unikalne ograniczenie dla (ID, RecordStatus) tylko wtedy, gdy RecordStatus = 1, ponieważ nie obchodzi mnie, czy istnieje wiele usuniętych rekordów z tym samym ID.
Czy oprócz pisania wyzwalaczy mogę to zrobić?
Używam SQL Server 2005.
sql
sql-server
sql-server-2005
np-trudne
źródło
źródło
Odpowiedzi:
Dodaj takie ograniczenie sprawdzające. Różnica polega na tym, że zwrócisz wartość false, jeśli Status = 1 i 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;
źródło
Oto przefiltrowany indeks . Z dokumentacji (wyróżnienie moje):
A oto przykład łączący unikalny indeks z predykatem filtru:
create unique index MyIndex on MyTable(ID) where RecordStatus = 1;
To zasadniczo wymusza wyjątkowość tego,
ID
kiedyRecordStatus
jest1
.Po utworzeniu tego indeksu, naruszenie unikalności podniesie wynik:
Uwaga: filtrowany indeks został wprowadzony w SQL Server 2008. W przypadku wcześniejszych wersji SQL Server, zobacz tę odpowiedź .
źródło
ansi_padding
filtrowanych indeksów, więc upewnij się, że ta opcja jest włączona, wykonującSET ANSI_PADDING ON
przed utworzeniem filtrowanego indeksu.Możesz przenieść usunięte rekordy do tabeli, w której brakuje ograniczenia, i być może użyć widoku z UNION dwóch tabel, aby zachować wygląd pojedynczej tabeli.
źródło
Możesz to zrobić w naprawdę hakerski sposób ...
Utwórz schematyczny widok na swoim stole.
UTWÓRZ WIDOK Cokolwiek SELECT * FROM Table WHERE RecordStatus = 1
Teraz utwórz unikalne ograniczenie widoku z wybranymi polami.
Jedna uwaga na temat widoków schematu, jeśli zmienisz tabele bazowe, będziesz musiał ponownie utworzyć widok. Mnóstwo pułapek z tego powodu.
źródło
Ponieważ zamierzasz zezwolić na duplikaty, unikalne ograniczenie nie zadziała. Możesz utworzyć ograniczenie sprawdzające dla kolumny RecordStatus i procedurę składowaną dla INSERT, która sprawdza istniejące aktywne rekordy przed wstawieniem zduplikowanych identyfikatorów.
źródło
Jeśli nie możesz użyć NULL jako RecordStatus, jak sugerował Bill, możesz połączyć jego pomysł z indeksem opartym na funkcjach. Utwórz funkcję, która zwraca wartość NULL, jeśli RecordStatus nie jest jedną z wartości, które chcesz uwzględnić w ograniczeniu (a w przeciwnym razie RecordStatus), i utwórz indeks dla tego.
Będzie to miało tę zaletę, że nie musisz jawnie sprawdzać innych wierszy w tabeli w swoim ograniczeniu, co może powodować problemy z wydajnością.
Powinienem powiedzieć, że w ogóle nie znam serwera SQL, ale z powodzeniem zastosowałem to podejście w Oracle.
źródło