null도 허용하는 고유 제약 조건을 어떻게 만듭니 까?


620

GUID로 채울 열에 고유 한 제약 조건을 갖고 싶습니다. 그러나 내 데이터에는이 열에 대한 null 값이 포함되어 있습니다. 여러 null 값을 허용하는 제약 조건을 어떻게 만듭니 까?

시나리오 예 는 다음과 같습니다 . 이 스키마를 고려하십시오.

CREATE TABLE People (
  Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
  Name NVARCHAR(250) NOT NULL,
  LibraryCardId UNIQUEIDENTIFIER NULL,
  CONSTRAINT UQ_People_LibraryCardId UNIQUE (LibraryCardId)
)

그런 다음 달성하려는 내용은이 코드를 참조하십시오.

-- This works fine:
INSERT INTO People (Name, LibraryCardId) 
 VALUES ('John Doe', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');

-- This also works fine, obviously:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Marie Doe', 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB');

-- This would *correctly* fail:
--INSERT INTO People (Name, LibraryCardId) 
--VALUES ('John Doe the Second', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');

-- This works fine this one first time:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Richard Roe', NULL);

-- THE PROBLEM: This fails even though I'd like to be able to do this:
INSERT INTO People (Name, LibraryCardId) 
VALUES ('Marcus Roe', NULL);

마지막 설명은 메시지와 함께 실패합니다.

UNIQUE KEY 제약 조건 'UQ_People_LibraryCardId'위반 'dbo.People'개체에 중복 키를 삽입 할 수 없습니다.

NULL실제 데이터에서 고유성을 확인하면서 여러 값을 허용하도록 스키마 및 / 또는 고유성 제약 조건을 어떻게 변경 합니까?


표준 호환성에 대한 연결 문제 투표 : connect.microsoft.com/SQLServer/Feedback/Details/299229
Vadzim


UNIQUE 제약 조건 및 NULL 허용 ? 상식입니다. 그것은 불가능하다
flik

13
@flik은 "상식"을 참조하지 않는 것이 좋습니다. 그것은 유효한 주장이 아닙니다. 특히 그것을 고려할 때 null가치는 없지만 가치가 없습니다. SQL 표준에 따라 null같지 않은 것으로 간주됩니다 null. 그렇다면 왜 다중 null이 고유성 위반이어야합니까?
프레데릭

답변:


144

SQL Server 2008 +

WHERE절을 사용하여 여러 NULL을 허용하는 고유 인덱스를 만들 수 있습니다 . 아래 답변을 참조하십시오 .

SQL Server 2008 이전

UNIQUE 제약 조건을 만들고 NULL을 허용 할 수 없습니다. 기본값 NEWID ()를 설정해야합니다.

UNIQUE 제약 조건을 만들기 전에 기존 값을 NEWID ()로 업데이트합니다. 여기서 NULL은 없습니다.


2
그리고 이것은 기존 행에 소급하여 값을 추가 할 것입니다.
스튜어트

1
기존 필드가 NULL 인 경우 기존 값을 NEWID ()로 설정하려면 UPDATE 문을 실행해야합니다.
Jose Basilio

54
SQL Server 2008 이상을 사용하는 경우 100 개가 넘는 투표로 아래 답변을 참조하십시오. 고유 제한 조건에 WHERE 절을 추가 할 수 있습니다.
대런 그리피스

1
이 문제는 ADO.NET DataTables에도 적용됩니다. 따라서이 방법을 사용하여 백업 필드에 null을 허용하더라도 DataTable을 사용하면 NULL을 고유 열에 처음 저장할 수 없습니다. 누군가 그 솔루션을 알고 있다면 여기에
dotNET

6
여러분은 아래로 스크롤하여 600 upvotes로 답변을 읽으십시오. 더 이상 100을 넘지 않습니다.
Luminous

1287

당신이 찾고있는 것은 실제로 ANSI 표준 SQL : 92, SQL : 1999 및 SQL : 2003의 일부입니다. 즉 UNIQUE 제약 조건은 NULL이 아닌 중복 값을 허용하지 않지만 여러 NULL 값을 허용해야합니다.

그러나 SQL Server의 Microsoft 세계에서는 단일 NULL이 허용되지만 여러 NULL은 허용되지 않습니다 ...

에서 SQL 서버 2008 , 당신은 제외 NULL을 그 조건 자에 따라 고유 필터링 된 인덱스를 정의 할 수 있습니다 :

CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;

이전 버전에서는 NOT NULL 술어를 사용하여 VIEWS를 사용하여 제한 조건을 적용 할 수 있습니다.


3
이것이 가장 좋은 방법 일 것입니다. 성능에 영향이 있는지 확실하지 않습니까? 누군가?
Simon_Weaver

3
SQL Server 2008 Express 버전에서이 작업을 정확하게 수행하려고하는데 다음과 같은 오류가 발생합니다. [SLS-CP]에서 고유 한 색인 된 UC_MailingId 만들기 .dbo.MasterFileEntry (MailingId) MailingId가 NULL이 아닌 곳에서 결과 : 메시지 156, 수준 15, 상태 1, 줄 3 키워드 'WHERE'근처의 구문이 잘못되었습니다. where 절을 제거하면 DDL이 제대로 실행되지만 물론 필요한 것을 수행하지 않습니다. 어떤 아이디어?
Kenneth Baltrinic

4
내가 실수하지 않으면, 고유 제한 조건을 해제 할 수있는 것처럼 고유 색인에서 외래 키를 작성할 수 없습니다. (내가 시도했을 때 적어도 SSMS가 나에게 불평했습니다.) 외래 키 관계의 소스가 항상 고유 (널이 아닌 경우) 인 nullable 열을 가질 수 있으면 좋을 것입니다.
Vaccano

8
정말 좋은 대답입니다. 답변으로 받아 들여진 사람에 의해 숨겨져 너무 나빴습니다. 이 솔루션은 거의 관심을 얻지 못했지만 지금은 구현에서 놀라운 것처럼 작동합니다.
산호도 Doe

2
SQL 2005 이하의 또 다른 대안은 "열 버스터 (Nullbuster)"기법으로 계산 열입니다. stackoverflow.com/a/191729/132461 다른 뷰를 사용하여 데이터베이스를 어수선하게 정리하는 대신 다른 열이 있습니다. ColumnA가 ANSI nullable UNIQUE가 될 열인 경우 이름이 ColumnA-Nullbuster입니다. ColumnA-Nullbuster에 UNIQUE Index (또는 비즈니스 의도를 표현하기위한 제약)를 적용하면 ColumnA에 고유성을 부여합니다
DanO

34

SQL Server 2008 이상

고유 색인을 필터링하십시오.

CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;

낮은 버전에서는 구체화 된 뷰가 여전히 필요하지 않습니다.

SQL Server 2005 및 이전 버전의 경우보기없이 수행 할 수 있습니다. 방금 내 테이블 중 하나에 요청하는 것처럼 독특한 제약 조건을 추가했습니다. column SamAccountName에서 고유성을 원 하지만 여러 NULL을 허용하려면 materialized view 대신 materialized column을 사용했습니다.

ALTER TABLE dbo.Party ADD SamAccountNameUnique
   AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
   UNIQUE (SamAccountNameUnique)

실제 원하는 고유 열이 NULL 일 때 전체 테이블에서 고유하게 보장되는 계산 열에 무언가를 넣어야합니다. 이 경우 PartyIDID 열이며 숫자가되는 것은 결코 일치하지 SamAccountName않으므로 나를 위해 일했습니다. 고유 한 방법을 시도해 볼 수 있습니다. 실제 데이터와 교차 할 가능성이 없도록 데이터의 도메인을 이해해야합니다. 다음과 같이 차별화 요소를 추가하는 것만 큼 간단 할 수 있습니다.

Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))

해도 PartyID숫자가 아닌 언젠가되었다과 일치 수 SamAccountName, 지금은 문제가되지 않습니다.

계산 된 열을 포함하는 인덱스가 있으면 각 식 결과가 테이블의 다른 데이터와 함께 디스크에 저장되므로 DOES는 추가 디스크 공간을 사용합니다.

색인을 원하지 않으면 PERSISTED열 표현식 정의의 끝에 키워드 를 추가하여 표현식을 디스크에 미리 계산하여 CPU를 절약 할 수 있습니다 .

SQL Server 2008 이상에서는 가능하면 필터링 된 솔루션을 사용하십시오!

논쟁

일부 데이터베이스 전문가는 이것을 "서로 게이트 NULL"의 경우로 볼 수 있는데, 이는 분명히 문제가 있습니다 (주로 무언가가 실제 값 인지 또는 누락 된 데이터에 대한 대리 값 인지 결정하려고하는 문제로 인해 문제가 있을 수 있음). NULL이 아닌 대리 값이 미친 것처럼 곱해집니다).

그러나 나는이 사건이 다르다고 생각합니다. 내가 추가하는 계산 열은 아무것도 결정하는 데 사용되지 않습니다. 자체 의미가 없으며 올바르게 정의 된 다른 열에서 별도로 발견되지 않은 정보는 인코딩하지 않습니다. 절대로 선택하거나 사용해서는 안됩니다.

그래서 내 이야기는 이것이 대리 NULL이 아니며, 나는 그것을 고집하고 있다는 것입니다! NULL UNIQUE을 무시 하도록 인덱스를 속이는 것 이외의 다른 목적으로 NULL이 아닌 값을 실제로 원하지 않기 때문에 우리 의 유스 케이스에는 정상적인 대리 NULL 생성에서 발생하는 문제가 없습니다.

그러나 인덱싱 된 뷰를 사용하는 데 아무런 문제가 없지만 사용 요구 사항과 같은 몇 가지 문제가 발생합니다 SCHEMABINDING. 기본 테이블에 새 열을 추가하는 것이 재미 있습니다. 최소한 인덱스를 삭제 한 다음 뷰를 삭제하거나 스키마 바인딩되지 않도록 뷰를 변경해야합니다. SQL Server (2005) (및 이후 버전) (2000) 에서 인덱싱 된 뷰를 만드는 데 필요한 전체 (긴) 요구 사항 목록을 참조하십시오 .

최신 정보

열이 숫자 인 경우 고유 제한 조건을 사용하여 Coalesce충돌이 발생하지 않도록해야 할 수도 있습니다 . 이 경우 몇 가지 옵션이 있습니다. 음수 범위에만 "surrogate NULLs"를, 양수 범위에만 "실제 값"을 배치하기 위해 음수를 사용할 수 있습니다. 대안 적으로, 다음 패턴이 사용될 수있다. 표 Issue(에 IssueID있는 PRIMARY KEY)에는가있을 수도 있고 없을 수도 TicketID있지만 존재하는 경우 고유해야합니다.

ALTER TABLE dbo.Issue ADD TicketUnique
   AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
   UNIQUE (TicketID, TicketUnique);

IssueID 1에 티켓 123이 있으면 UNIQUE제약 조건은 값 (123, NULL)입니다. IssueID 2에 티켓이 없으면 티켓이 켜져 있습니다 (NULL, 2). 어떤 생각은이 제약 조건을 테이블의 행에 대해 복제 할 수 없으며 여러 NULL을 허용한다는 것을 보여줍니다.


16

사용하는 사람들을 위해 마이크로 소프트 SQL 서버 관리자 는 일반적으로 다음 새 인덱스에 대한 색인 속성에서, 왼쪽 패널에서 "필터"를 선택하는 것처럼 고유하지만 Null 허용 인덱스를 만들려면 다음을 입력 한 후, 당신의 고유 인덱스를 만들 수 있습니다 필터 (where 절) 다음과 같이 읽어야합니다.

([YourColumnName] IS NOT NULL)

이것은 MSSQL 2012와 함께 작동합니다


Microsoft SQL Server Management Studio에서 필터링 된 인덱스를 만드는 방법은 여기에 설명되어 있으며 완벽하게 작동합니다. msdn.microsoft.com/en-us/library/cc280372.aspx
Jan

9

아래의 고유 색인을 적용했을 때 :

CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull
ON employee(badgeid)
WHERE badgeid IS NOT NULL;

null이 아닌 모든 업데이트 및 삽입이 아래 오류로 실패했습니다.

다음 SET 옵션의 설정이 잘못되어 업데이트에 실패했습니다 : 'ARITHABORT'.

MSDN 에서 이것을 찾았습니다.

계산 열 또는 인덱싱 된 뷰에서 인덱스를 만들거나 변경할 때는 SET ARITHABORT가 ON이어야합니다. SET ARITHABORT가 OFF이면 계산 열 또는 인덱스 뷰에 인덱스가있는 테이블의 CREATE, UPDATE, INSERT 및 DELETE 문이 실패합니다.

이것이 올바르게 작동하도록하려면이 작업을 수행하십시오.

[데이터베이스]-> 속성-> 옵션-> 기타 옵션-> 기타 ---- 산술 중단 활성화-> true를 마우스 오른쪽 버튼으로 클릭하십시오.

코드를 사용 하여이 옵션을 설정할 수 있다고 생각합니다.

ALTER DATABASE "DBNAME" SET ARITHABORT ON

하지만 나는 이것을 테스트하지 않았다


6

비열 만 선택하는보기를 작성하고보기에서 NULL작성하십시오 UNIQUE INDEX.

CREATE VIEW myview
AS
SELECT  *
FROM    mytable
WHERE   mycolumn IS NOT NULL

CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)

수행해야한다는 점 주 INSERT의와 UPDATE대신 테이블의 뷰의.

INSTEAD OF트리거 로 할 수 있습니다.

CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
        INSERT
        INTO    myview
        SELECT  *
        FROM    inserted
END

뷰에 삽입하려면 dal을 변경해야합니까?
스튜어트

1
INSTEAD OF INSERT 트리거를 작성할 수 있습니다.
Quassnoi

6

디자이너도 할 수 있습니다

색인> 속성 을 마우스 오른쪽 버튼으로 클릭 하면이 창이 나타납니다.

포착


디자이너에게 접근 할 수있는 아주 좋은 대안
Francisco

방금 발견 한 것처럼 테이블에 데이터가 있으면 더 이상 디자이너를 사용할 수 없습니다. 필터를 무시하는 것으로 보이며 시도 된 테이블 업데이트에 "중복 키를 사용할 수 없습니다"라는 메시지가 표시됩니다.
MortimerCat

4

클러스터형 인덱싱 된 뷰에서 고유 한 제약 조건을 만들 수 있습니다

다음과 같이보기를 만들 수 있습니다.

CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS
SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable
WHERE YourUniqueColumnWithNullValues IS NOT NULL;

다음과 같은 고유 제약 조건 :

CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE 
  ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)

2

" INSTEAD OF"트리거를 고려하여 직접 확인 하시겠습니까? 열에 클러스터되지 않은 (고유하지 않은) 인덱스를 사용하여 조회를 활성화합니다.


1

앞에서 언급했듯이 SQL Server는 ANSI 표준을 구현하지 않습니다 UNIQUE CONSTRAINT. 가 마이크로 소프트 연결에 티켓을 2007 년으로이하고 제안하기 때문에 이것에 대한 여기 에 명시된 바와 같이 오늘 같은 최고의 옵션은 필터링 된 인덱스를 사용할 수있는 다른 답변 또는 계산 열, 예를 들면 :

CREATE TABLE [Orders] (
  [OrderId] INT IDENTITY(1,1) NOT NULL,
  [TrackingId] varchar(11) NULL,
  ...
  [ComputedUniqueTrackingId] AS (
      CASE WHEN [TrackingId] IS NULL
      THEN '#' + cast([OrderId] as varchar(12))
      ELSE [TrackingId_Unique] END
  ),
  CONSTRAINT [UQ_TrackingId] UNIQUE ([ComputedUniqueTrackingId])
)

1

INSTEAD OF 트리거를 생성하여 특정 조건과 오류가 충족되는지 확인할 수 있습니다. 큰 테이블에서는 인덱스를 만드는 데 많은 비용이들 수 있습니다.

예를 들면 다음과 같습니다.

CREATE TRIGGER PONY.trg_pony_unique_name ON PONY.tbl_pony
 INSTEAD OF INSERT, UPDATE
 AS
BEGIN
 IF EXISTS(
    SELECT TOP (1) 1 
    FROM inserted i
    GROUP BY i.pony_name
    HAVING COUNT(1) > 1     
    ) 
     OR EXISTS(
    SELECT TOP (1) 1 
    FROM PONY.tbl_pony t
    INNER JOIN inserted i
    ON i.pony_name = t.pony_name
    )
    THROW 911911, 'A pony must have a name as unique as s/he is. --PAS', 16;
 ELSE
    INSERT INTO PONY.tbl_pony (pony_name, stable_id, pet_human_id)
    SELECT pony_name, stable_id, pet_human_id
    FROM inserted
 END

-1

당신은이 작업을 수행 할 수없는 UNIQUE제약 조건,하지만 당신은 트리거에서이 작업을 수행 할 수 있습니다.

    CREATE TRIGGER [dbo].[OnInsertMyTableTrigger]
   ON  [dbo].[MyTable]
   INSTEAD OF INSERT
AS 
BEGIN
    SET NOCOUNT ON;

    DECLARE @Column1 INT;
    DECLARE @Column2 INT; -- allow nulls on this column

    SELECT @Column1=Column1, @Column2=Column2 FROM inserted;

    -- Check if an existing record already exists, if not allow the insert.
    IF NOT EXISTS(SELECT * FROM dbo.MyTable WHERE Column1=@Column1 AND Column2=@Column2 @Column2 IS NOT NULL)
    BEGIN
        INSERT INTO dbo.MyTable (Column1, Column2)
            SELECT @Column2, @Column2;
    END
    ELSE
    BEGIN
        RAISERROR('The unique constraint applies on Column1 %d, AND Column2 %d, unless Column2 is NULL.', 16, 1, @Column1, @Column2);
        ROLLBACK TRANSACTION;   
    END

END

-1
CREATE UNIQUE NONCLUSTERED INDEX [UIX_COLUMN_NAME]
ON [dbo].[Employee]([Username] ASC) WHERE ([Username] IS NOT NULL) 
WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF, 
DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF, 
MAXDOP = 0) ON [PRIMARY];

-1

이 코드는 텍스트 상자로 등록 양식을 만들고 삽입을 사용하고 텍스트 상자가 비어 있으면 제출 버튼을 클릭하십시오.

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.