根据另一列值强制一列唯一

Dan*_*ler 6 sql-server constraint t-sql sql-server-2008-r2

我想让一列唯一,但前提是不同的列是特定值。

考虑下表:

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()
);
Run Code Online (Sandbox Code Playgroud)

目的是可以通过将[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!
Run Code Online (Sandbox Code Playgroud)

组合的唯一约束将不起作用,因为就我而言,具有相同代码的三个不同的“已删除”条目是有效的。

我问这个问题是因为最近在集成来自第三方应用程序的数据时,“在应用程序级别强制执行”策略让我感到很恼火。如果数据库直接拒绝错误的集成数据会很好,因为在集成发生之前修复它比在错误发生后清理数据容易得多。

我正在使用 SQL Server 2008 R2。但我不介意升级,如果我必须获得这个功能,无论如何我一直想升级。

Aaron 很快就回答了这个问题。我需要使用过滤的唯一索引。

以下代码演示了解决方案。

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];
Run Code Online (Sandbox Code Playgroud)

由于过滤索引,第六次(最终)插入和更新都失败了。

以后我会好好利用这个的。谢谢阿龙!

Aar*_*and 12

当您有一个唯一约束并且只想应用于行的子集时,您可以使用唯一的、过滤的索引来强制执行此操作。在这种情况下,似乎对您有用的索引是:

CREATE UNIQUE INDEX [UNQ_SampleTable_Code]
  ON dbo.[SampleTable]([Code])
  WHERE   ([Deleted] = 0);
Run Code Online (Sandbox Code Playgroud)

这确保了Code对于Deleted为 0 的行只能存在一个不同的值,但是对于为 1 的行可以存在重复项Deleted。通常这也有助于某些查询的性能,因为您通常只对活动行(而不是软删除),但如果这不包括查询,您可能需要考虑将列添加到 INCLUDE 子句(如果认为查找成本过高,SQL Server 可能会选择扫描聚集索引或其他索引)。