列を一意にしたいのですが、別の列が特定の値である場合のみです。
次の表を検討してください。
CREATE TABLE [SampleTable]
(
[Id] INTEGER NOT NULL IDENTITY(1,1)
,CONSTRAINT [PK_SampleTable]
PRIMARY KEY ([Id])
,[Code] NVARCHAR(255) NOT NULL
,[Deleted] BIT NOT NULL DEFAULT 0
,[CreatedOn] DATETIME NOT NULL DEFAULT GETDATE()
);
その目的は、[Deleted]
列を1に設定することにより、アイテムを「削除」できるようにすることです。
必要なのは、[Code]
列を一意として強制することですが、削除されていない行のみです。
データベースレベルでデータの正確さを強制することは、一般的に私の好みです。ただし、過去にこのパターンを頻繁に使用しましたが、この種の制約をデータベースレベルで適用できるかどうかは定かではありませんでした。締め切りのプレッシャーは彼らが何であるかであり、私は気にすることはありません。したがって、私は常にそれらをアプリケーションレベルで強制しました。
それを行う方法がある場合でも、それが何であるかを本当に知りたいです。
明確にするために、次のデータをサポートできるようにする必要があるため、結合された一意制約を使用することはできません。
[ID] [Code] [Deleted] [CreatedOn]
=====================================================================
1 'ABC' 1 Ages ago
2 'ABC' 1 A while ago
3 'ABC' 1 Quite recently, actually
4 'ABC' 0 Just a moment ago!
私の目的では、同じコードを持つ3つの異なる「削除済み」エントリが有効であるため、結合された一意制約は機能しません。
「アプリケーションレベルでこれを強制する」ポリシーにより、サードパーティのアプリケーションからのデータを統合することに関して、私は最近お尻に食い込んだので、この質問をします。データベースが不正な統合データを完全に拒否していたとしたら、それは良かったでしょう。統合が発生する前に修正することは、データが誤って発生した後にデータをクレンジングするよりもはるかに簡単だからです。
SQL Server 2008 R2を使用しています。ただし、この機能を取得する必要がある場合はアップグレードしてもかまいません。とにかくアップグレードするつもりです。
アーロンはこれにかなりすぐに答えました。フィルター処理された一意のインデックスを使用する必要がありました。
次のコードは、ソリューションを示しています。
IF EXISTS (SELECT * FROM SYS.TABLES WHERE [name] = 'SampleTable')
BEGIN
PRINT 'Dropping Table [SampleTable]';
DROP TABLE [SampleTable];
END;
GO
PRINT 'Creating Table [SampleTable]';
CREATE TABLE [SampleTable]
(
[Id] INTEGER NOT NULL IDENTITY(1,1)
,CONSTRAINT [PK_SampleTable]
PRIMARY KEY ([Id])
,[Code] NVARCHAR(255) NOT NULL
,[Deleted] BIT NOT NULL DEFAULT 0
,[CreatedOn] DATETIME NOT NULL DEFAULT GETDATE()
);
CREATE UNIQUE INDEX
[UNQ_SampleTable_Code]
ON
[SampleTable]([Code])
WHERE
([Deleted] = 0);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 1);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 1);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 1);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 1);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 0);
INSERT INTO [SampleTable] ([Code],[Deleted]) VALUES ('ABC', 0);
UPDATE [SampleTable] SET [Deleted] = 0 WHERE [Id] = 1;
SELECT * FROM [SampleTable];
6番目(最後)の挿入と更新の両方が、フィルター処理されたインデックスが原因で失敗します。
将来はこれを有効活用します。アーロン、ありがとう!
行のサブセットのみに適用する一意の制約がある場合、一意のフィルター処理されたインデックスを使用してこれを強制できます。この場合に効果があると思われるインデックスは次のとおりです。
CREATE UNIQUE INDEX [UNQ_SampleTable_Code]
ON dbo.[SampleTable]([Code])
WHERE ([Deleted] = 0);
これにより、Code
が0の行にはDeleted
の個別の値が1つだけ存在し、Deleted
が1の場合は重複が存在することが保証されます。通常、これは一部のクエリは、アクティブな行のみに関心があるため(ソフト削除ではなく)、これがクエリをカバーしない場合は、INCLUDE句に列を追加することを検討することができます(SQL Serverはクラスター化されたスキャンを選択する場合があります)インデックス、または別のインデックス(ルックアップのコストが高すぎると思われる場合)。