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 '26 / 03/18

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である場合、UPDATEステートメントを実行して既存の値をNEWID()に設定する必要があります
Jose Basilio

54
SQL Server 2008以降を使用している場合は、100を超える投票で以下の回答を参照してください。一意の制約にWHERE句を追加できます。
ダレングリフィス

1
この非常に問題はADO.NET DataTablesにも影響します。したがって、このメソッドを使用してバッキングフィールドでnullを許可できる場合でも、DataTableでは、最初に一意の列にnullを格納できません。誰かがその解決策を知っている場合は、こちらに投稿してください
dotNET

6
みんな、必ず下にスクロールして600の賛成票で答えを読んでください。それはちょうど100上に、もはやません
ルミナス

1288

あなたが探しているのは、確かにANSI標準SQL:92、SQL:1999およびSQL:2003の一部です。つまり、UNIQUE制約は、重複する非NULL値を許可せず、複数のNULL値を受け入れる必要があります。

ただし、MicrosoftのSQL Serverの世界では、単一のNULLは許可されていますが、複数のNULLは許可されていません...

SQL Server 2008の、あなたは述語その除外ヌルに基づいて独自のフィルターのインデックスを定義することができます。

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エディションでこれを正確に実行しようとすると、次のようなエラーが発生します。CREATE UNIQUE NONCLUSTERED INDEX UC_MailingId ON [SLS-CP] .dbo.MasterFileEntry(MailingId)WHERE MailingId IS NOT NULLレベル15、状態1、行3キーワード「WHERE」付近の構文が正しくありません。where句を削除すると、DDLは問題なく動作しますが、もちろん、必要なことを実行できません。何か案は?
Kenneth Baltrinic、2010

4
私が間違っていない限り、一意の制約をオフにすることができるように、一意のインデックスから外部キーを作成することはできません。(私が試したとき、少なくともSSMSは私に不満を言いました。)常に一意である(nullでない場合)null可能列を外部キー関係のソースにできると便利です。
Vaccano

8
本当に素晴らしい答えです。残念ながら、回答として受け入れられた人によって隠されていました。このソリューションはほとんど気になりませんでしたが、今の私の実装では不思議に機能します。
Coral Doe

2
SQL 2005以下のもう1つの代替手段は、計算列(別名「Nullbuster」)です。stackoverflow.com/a/191729/132461これにより、データベースを別のビューで乱雑にするのを防ぐことができます。代わりに別の列があります。通常、ColumnAがANSI nullable UNIQUEにしたい場合は、ColumnA-Nullbusterという名前です。ColumnA-Nullbusterに一意のインデックス(またはビジネスの意図を表す制約)を設定すると、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以前の場合、ビューなしで実行できます。私が求めているような一意の制約をテーブルの1つに追加しました。columnの一意性SamAccountNameが必要だが、複数のNULLを許可したいので、マテリアライズドビューではなくマテリアライズド列を使用しました。

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の場合、テーブル全体で一意であることが保証される計算列に何かを置くだけです。この場合、PartyIDはID列であり、数値であることは決してと一致しないSamAccountNameため、私にとってはうまくいきました。実際のデータと交差する可能性がないように、独自の方法を試すことができます。必ずデータのドメインを理解してください。これは、次のように微分文字を前に付けるだけの簡単なものです。

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

たとえ PartyID非数値いつかになったとと一致することができSamAccountName、今では問題ではありません。

計算された列を含むインデックスが存在すると、各式の結果がテーブル内の他のデータと共にディスクに保存されることに注意してください。これにより、追加のディスク領域が必要になります。

インデックスが必要ない場合でも、キーワードを追加して式をディスクに事前計算することで、CPUを節約できます。 PERSISTED、列式定義の最後にをます。

SQL Server 2008以降では、可能であれば、代わりにフィルターされたソリューションを使用してください。

論争

一部のデータベースプロフェッショナルは、これを「代理NULL」の場合と見なすことに注意してください。これは間違いなく問題があります(主に、何かが実際の値であるか、欠落しているデータの代理値であるかを判別する際の問題が原因です)。です。問題がある場合もあります。 NULL以外のサロゲート値の数が狂ったように増加しています)。

ただし、このケースは異なると思います。追加する計算列は、何かを決定するために使用されることはありません。それ自体には意味がなく、適切に定義された他の列で個別に見つからない情報はエンコードされません。選択または使用しないでください。

だから、私の話では、これは代理NULLではなく、私はそれに固執しています!実際には、UNIQUEインデックスをだましてNULLを無視する以外の目的で非NULL値を必要としないため、このユースケースでは、通常のサロゲートNULLの作成で発生する問題はありません。

そうは言っても、代わりにインデックス付きビューを使用しても問題はありませんが、を使用する必要があるなど、いくつかの問題が発生しますSCHEMABINDING。ベーステーブルに新しい列を追加してください(少なくとも、インデックスを削除してから、ビューを削除するか、ビューがスキーマにバインドされないように変更する必要があります)。SQL Server(2005)(以降のバージョン)(2000)でインデックス付きビューを作成するための要件の完全な(長い)リストを参照してください。

更新

列が数値の場合、を使用する一意の制約Coalesceによって衝突が発生しないようにすることが難しい場合があります。その場合、いくつかのオプションがあります。1つは、負の数を使用して、「代理NULL」を負の範囲にのみ置き、「実際の値」を正の範囲にのみ入れることです。または、次のパターンを使用することもできます。テーブルIssueIssueIDPRIMARY 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

使用している人々のためのMicrosoft SQL Serverのマネージャーをしてユニークが、NULL可能インデックスを作成したいあなたは、あなたが正常にあなたの新しいインデックスのためのあなたのインデックスプロパティで、その後同じように、あなたの一意のインデックスを作成することができます左側のパネルから「フィルタ」を選択し、入力します。フィルター(where句です)。次のように表示されます。

([YourColumnName] IS NOT NULL)

これはMSSQL 2012で動作します


Microsoft SQL Server Management Studioでフィルター処理されたインデックスを作成する方法は次のとおりで、完全に機能します。msdn.microsoft.com
Jan

9

以下の一意のインデックスを適用した場合:

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

null以外の更新と挿入はすべて次のエラーで失敗しました:

次のSETオプションの設定が正しくないため、UPDATEは失敗しました: '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 2009

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年以降、Microsoft Connectにはこのためのチケットがあります。そこで提案されているように、今日ここでの最良のオプションは、別の回答または計算された列で述べられているようにフィルターされたインデックスを使用することです。

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

このコードは、textBoxを使用して登録フォームを作成し、挿入を使用し、textBoxが空の場合、送信ボタンをクリックします。

CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.