조건부 고유 제약


92

열 집합에 대해 고유 한 제약 조건을 적용해야하지만 열의 한 값에만 적용해야하는 상황이 있습니다.

예를 들어 Table (ID, Name, RecordStatus)와 같은 테이블이 있습니다.

RecordStatus는 값 1 또는 2 (활성 또는 삭제됨) 만 가질 수 있으며 동일한 레코드가 여러 개 삭제 되어도 상관 없으므로 RecordStatus = 1 인 경우에만 (ID, RecordStatus)에 대한 고유 제약 조건을 만들고 싶습니다. 신분증.

트리거 작성 외에도 그렇게 할 수 있습니까?

SQL Server 2005를 사용하고 있습니다.


1
이 디자인은 일반적인 고통입니다. 개념적으로 '삭제 된'레코드가 테이블에서 물리적으로 삭제되고 '아카이브'테이블로 이동되도록 디자인 변경을 고려해 보셨습니까?
onedayWhen

1
... 단순 키를 적용하기 위해 UNIQUE 제약 조건을 작성할 수없는 것은 '코드 냄새', IMO로 간주되어야하기 때문입니다. 다른 많은 테이블이이 테이블을 참조하기 때문에 디자인 (SQL DDL)을 변경할 수없는 경우 SQL DML도 결과적으로 문제가 발생합니다. 즉, ... AND Table.RecordStatus = 1 '을 추가해야합니다. 이 테이블을 포함하는 대부분의 검색 조건 및 조인 조건에 적용되며 때때로 생략 될 경우 미묘한 버그가 발생합니다.
onedayWhen

답변:


36

이와 같은 검사 제약을 추가하십시오. 차이점은 Status = 1이고 Count> 0이면 false를 반환한다는 것입니다.

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;

테이블 수준 검사 제약 조건을 살펴 보았지만 함수에 삽입되거나 업데이트되는 값을 전달하는 방법이없는 것 같습니다. 방법을 알고 있습니까?
np-hard

알겠습니다. 제가 말하는 내용을 증명하는 데 도움이되는 샘플 스크립트를 게시했습니다. 나는 그것을 테스트했고 작동합니다. 주석 처리 된 두 줄을 보면 내가받은 메시지가 표시됩니다. 참고로, 내 구현에서 이미 하나의 활성 항목이있는 경우 활성 상태 인 동일한 ID를 가진 두 번째 항목을 추가 할 수 없음을 확인합니다. 활성 로직이있는 경우 동일한 ID를 가진 항목을 추가 할 수 없도록 로직을 수정할 수 있습니다. 이 패턴으로 가능성은 거의 무한합니다.
D. Patrick

트리거에서 동일한 논리를 선호합니다. "스칼라 함수의 쿼리는 ... CHECK 제약 조건이 쿼리에 의존하고 두 개 이상의 행이 업데이트의 영향을받는 경우 큰 문제를 일으킬 수 있습니다. 어떤 일이 발생하면 문이 완료되기 전에 제약 조건이 각 행에 대해 한 번씩 확인됩니다. . 즉, 명령문 원 자성이 손상되고 함수가 일관성없는 상태로 데이터베이스에 노출됩니다. 결과는 예측할 수없고 부정확합니다. " 참조 : blogs.conchango.com/davidportas/archive/2007/02/19/…
onedaywhen

언젠가는 부분적으로 만 사실입니다. 데이터베이스는 일관되고 예측 가능하게 작동합니다. 체크 제약 조건은 행이 테이블에 추가 된 후 트랜잭션이 dbms에 의해 커밋되기 전에 실행되며이를 신뢰할 수 있습니다. 그 블로그는 한 번에 하나의 삽입이 아닌 일련의 삽입에 대해 제약 조건을 실행해야하는 매우 독특한 문제에 대해 이야기하고있었습니다. ashish는 한 번에 하나의 삽입물에 대한 제약 조건을 요구하며이 제약 조건은 정확하고 예측 가능하며 일관되게 작동합니다. 간결하게 들리면 유감입니다. 캐릭터가 부족했습니다.
D. Patrick

3
이것은 삽입물에는 잘 작동하지만 업데이트에는 작동하지 않는 것 같습니다. EG 다른 삽입물 다음에 이것을 추가하면 내가 예상하지 못했던 곳에서 작동합니다. INSERT INTO CheckConstraint VALUES (1, '문제 없음 A', 2); 갱신 CheckConstraint는 Recordstatus = 1 곳 이름 = '없음 ProblemsA'설정
dwidel

148

보라, 필터링 된 인덱스 . 문서에서 (강조 내) :

필터링 된 인덱스는 잘 정의 된 데이터 하위 집합에서 선택하는 쿼리를 처리하는 데 특히 적합한 최적화 된 비 클러스터형 인덱스입니다. 필터 술어를 사용하여 테이블 행의 일부를 인덱싱합니다. 잘 설계된 필터링 된 인덱스는 전체 테이블 인덱스에 비해 쿼리 성능을 향상시킬뿐만 아니라 인덱스 유지 관리 및 저장 비용을 줄일 수 있습니다.

다음은 고유 인덱스를 필터 조건 자와 결합하는 예입니다.

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

이것은 본질적으로의 고유성을 적용 ID할 때 RecordStatus입니다 1.

해당 인덱스를 만든 후 고유성 위반으로 인해 오류가 발생합니다.

메시지 2601, 수준 14, 상태 1, 줄 13
고유 인덱스가 'MyIndex'인 개체 'dbo.MyTable'에 중복 키 행을 삽입 할 수 없습니다. 중복 키 값은 (9999)입니다.

참고 : 필터링 된 인덱스는 SQL Server 2008에서 도입되었습니다. 이전 버전의 SQL Server에 대해서는 이 답변을 참조하십시오 .


SQL Server에는 ansi_padding필터링 된 인덱스 가 필요 하므로 필터링 된 인덱스 SET ANSI_PADDING ON를 만들기 전에 실행하여이 옵션이 켜져 있는지 확인하십시오 .
naXa 2017

10

삭제 된 레코드를 제약 조건이없는 테이블로 이동하고 단일 테이블의 모양을 유지하기 위해 두 테이블의 UNION이있는 뷰를 사용할 수 있습니다.


2
사실 꽤 영리한 칼입니다. 질문에 대한 답은 아니지만 좋은 해결책입니다. 테이블에 행이 많은 경우 활성 레코드 테이블을 볼 수 있으므로 활성 레코드를 찾는 속도도 빨라질 수 있습니다. 또한 고유 제약 조건은 카운트를 실행해야하는 아래에 작성한 검사 제약 조건과 반대로 인덱스를 사용하기 때문에 제약 조건의 속도를 높일 수 있습니다. 나는 그것을 좋아한다.
D. Patrick

3

정말 해키 방식으로 할 수 있습니다 ...

테이블에 스키마 바운드 뷰를 만듭니다.

CREATE VIEW 무엇이든 SELECT * FROM Table WHERE RecordStatus = 1

이제 원하는 필드로 뷰에 고유 한 제약 조건을 만듭니다.

스키마 바운드 뷰에 대한 한 가지 참고 사항은 기본 테이블을 변경하는 경우 뷰를 다시 만들어야합니다. 그 때문에 많은 문제가 있습니다.


이것은 "해키"가 아닌 꽤 좋은 제안입니다. 다음은이 필터링 된 인덱스 대안 에 대한 자세한 정보 입니다.
Scott Whitlock 2011

그것은 나쁜 생각입니다. 문제는 아닙니다.
FabianoLothor

스키마 바운드 뷰를 한 번 사용했으며 실수를 반복 한 적이 없습니다. 그들은 함께 일하기에 왕실의 고통이 될 수 있습니다. 기본 테이블을 변경하면 뷰를 다시 만들어야하는 것이 아닙니다. 최소한 SQL 서버에서는 모든 뷰에 대해 잠재적으로이를 수행해야합니다. 뷰를 먼저 삭제하지 않고는 테이블을 변경할 수 없으며, 먼저 참조를 삭제하지 않고는 할 수 없습니다. 아, 추가로 저장 공간이 문제가 될 수 있습니다. 공간이나 삽입 및 업데이트에 추가되는 비용 때문입니다.
MattW

1

중복을 허용 할 것이기 때문에 고유 제약 조건이 작동하지 않습니다. 중복 ID를 삽입하기 전에 기존 활성 레코드를 확인하는 INSERT에 대한 RecordStatus 열 및 저장 프로 시저에 대한 검사 제약 조건을 만들 수 있습니다.


1

Bill이 제안한대로 RecordStatus로 NULL을 사용할 수없는 경우 그의 아이디어를 함수 기반 인덱스와 결합 할 수 있습니다. RecordStatus가 제약 조건에서 고려하려는 값 (그렇지 않으면 RecordStatus)이 아닌 경우 NULL을 반환하는 함수를 만들고 그 위에 인덱스를 만듭니다.

그러면 제약 조건에서 테이블의 다른 행을 명시 적으로 검사 할 필요가 없어 성능 문제가 발생할 수 있다는 이점이 있습니다.

SQL 서버를 전혀 모른다고 말해야하지만 Oracle에서이 접근 방식을 성공적으로 사용했습니다.


좋은 생각이지만 SQL 서버에 인덱싱 된 함수 기반은 ​​없습니다. 답을 주셔서 감사합니다
np-hard
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.