SQL 我可以有一个“条件唯一”吗?表上的约束?

发布于 2024-09-10 15:47:04 字数 455 浏览 3 评论 0原文

在我的职业生涯中,这个问题已经出现过好几次了,但我当地的同行似乎都无法回答这个问题。假设我有一个表,其中有一个“描述”字段,该字段是候选键,但有时用户会在过程中途停止。因此,对于 25% 的记录,该值可能为 null,但对于所有非 NULL 的记录,它必须是唯一的。

另一个例子可能是一个表,它必须维护一条记录的多个“版本”,并且一个位值指示哪一个是“活动”版本。因此,“候选密钥”始终会被填充,但可能存在三个相同的版本(活动位为 0),并且只有一个版本是活动的(活动位为 1)。

我有替代方法来解决这些问题(在第一种情况下,在存储过程或业务层中强制执行规则代码,在第二种情况下,使用触发器填充存档表,并在需要历史记录时对表进行 UNION) 。我不需要替代方案(除非有明显更好的解决方案),我只是想知道是否有任何 SQL 风格可以通过这种方式表达“条件唯一性”。我正在使用 MS SQL,所以如果有办法做到这一点,那就太好了。我主要只是在学术上对这个问题感兴趣。

I've had this come up a couple times in my career, and none of my local peers seems to be able to answer it. Say I have a table that has a "Description" field which is a candidate key, except that sometimes a user will stop halfway through the process. So for maybe 25% of the records this value is null, but for all that are not NULL, it must be unique.

Another example might be a table which must maintain multiple "versions" of a record, and a bit value indicates which one is the "active" one. So the "candidate key" is always populated, but there may be three versions that are identical (with 0 in the active bit) and only one that is active (1 in the active bit).

I have alternate methods to solve these problems (in the first case, enforce the rule code, either in the stored procedure or business layer, and in the second, populate an archive table with a trigger and UNION the tables when I need a history). I don't want alternatives (unless there are demonstrably better solutions), I'm just wondering if any flavor of SQL can express "conditional uniqueness" in this way. I'm using MS SQL, so if there's a way to do it in that, great. I'm mostly just academically interested in the problem.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

欢烬 2024-09-17 15:47:04

如果您使用的是 SQL Server 2008,索引过滤器可能是您的解决方案:

http:// msdn.microsoft.com/en-us/library/ms188783.aspx

这就是我使用多个 NULL 值强制执行唯一索引的方法

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL

If you are using SQL Server 2008 a Index filter would maybe your solution:

http://msdn.microsoft.com/en-us/library/ms188783.aspx

This is how I enforce a Unique Index with multiple NULL values

CREATE UNIQUE INDEX [IDX_Blah] ON [tblBlah] ([MyCol]) WHERE [MyCol] IS NOT NULL
尘曦 2024-09-17 15:47:04

对于尚未完成的描述,我不会将其与最终的描述放在同一个表中。最终表将在描述上具有唯一索引或主键。

在活动/非活动的情况下,我可能会像使用“存档”或“历史”表一样拥有单独的表,但至少在 MS SQL Server 中执行此操作的另一种可能方法是通过使用索引视图:

CREATE TABLE Test_Conditionally_Unique
(
    my_id   INT NOT NULL,
    active  BIT NOT NULL DEFAULT 0
)
GO
CREATE VIEW dbo.Test_Conditionally_Unique_View
WITH SCHEMABINDING
AS
    SELECT
        my_id
    FROM
        dbo.Test_Conditionally_Unique
    WHERE
        active = 1
GO
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
GO

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)    -- This insert will fail

您也可以对 NULL/Valued 描述使用相同的方法。

In the case of descriptions which are not yet completed, I wouldn't have those in the same table as the finalized descriptions. The final table would then have a unique index or primary key on the description.

In the case of the active/inactive, again I might have separate tables as you did with an "archive" or "history" table, but another possible way to do it in MS SQL Server at least is through the use of an indexed view:

CREATE TABLE Test_Conditionally_Unique
(
    my_id   INT NOT NULL,
    active  BIT NOT NULL DEFAULT 0
)
GO
CREATE VIEW dbo.Test_Conditionally_Unique_View
WITH SCHEMABINDING
AS
    SELECT
        my_id
    FROM
        dbo.Test_Conditionally_Unique
    WHERE
        active = 1
GO
CREATE UNIQUE CLUSTERED INDEX IDX1 ON Test_Conditionally_Unique_View (my_id)
GO

INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (1, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 0)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)
INSERT INTO dbo.Test_Conditionally_Unique (my_id, active)
VALUES (2, 1)    -- This insert will fail

You could use this same method for the NULL/Valued descriptions as well.

靖瑶 2024-09-17 15:47:04

感谢您的评论,这个答案的最初版本是错误的。

下面是一个使用计算列的技巧,它可以在 SQL Server 中有效地允许可空的唯一约束:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50),
    uniqueName as case 
        when name is null then cast(id as varchar(51)) 
        else name + '_' end,
    unique(uniqueName)
    )

insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!

name 为空时,它基本上使用 id+ '_' 是为了避免名称可能是数字的情况,例如 1,它可能与 id 冲突。

Thanks for the comments, the initial version of this answer was wrong.

Here's a trick using a computed column that effectively allows a nullable unique constraint in SQL Server:

create table NullAndUnique 
    (
    id int identity, 
    name varchar(50),
    uniqueName as case 
        when name is null then cast(id as varchar(51)) 
        else name + '_' end,
    unique(uniqueName)
    )

insert into NullAndUnique default values
insert into NullAndUnique default values -- Works
insert into NullAndUnique default values -- not accidentally :)
insert into NullAndUnique (name) values ('Joel')
insert into NullAndUnique (name) values ('Joel') -- Boom!

It basically uses the id when the name is null. The + '_' is to avoid cases where name might be numeric, like 1, which could collide with the id.

困倦 2024-09-17 15:47:04

我不完全了解您的预期用途或表格,但您可以尝试使用一对一关系。将这个“有时”唯一的列拆分到一个新表中,在新表中的该列上创建 UNIQUE 索引,并使用原始表 PK FK 返回原始表。仅当“唯一”数据应该存在时,此新表中才会有一行。

旧表:

TableA
ID    pk
Col1  sometimes unique
Col...

新表:

TableA
ID
Col...

TableB
ID   PK, FK to TableA.ID
Col1 unique index

I'm not entirely aware of your intended use or your tables, but you could try using a one to one relationship. Split out this "sometimes" unique column into a new table, create the UNIQUE index on that column in the new table and FK back to the original table using the original tables PK. Only have a row in this new table when the "unique" data is supposed to exist.

OLD tables:

TableA
ID    pk
Col1  sometimes unique
Col...

NEW tables:

TableA
ID
Col...

TableB
ID   PK, FK to TableA.ID
Col1 unique index
不如归去 2024-09-17 15:47:04

甲骨文确实如此。 Oracle 中索引中的 B 树不会对全空键进行索引,Oracle 使用 B 树索引来强制执行唯一约束。

假设希望基于 ACTIVE_FLAG 设置为 1 来版本 ID_COLUMN:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
  (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
   CASE active_flag WHEN 0 THEN NULL ELSE id_column   END);

Oracle does. A fully null key is not indexed by a Btree in index in Oracle, and Oracle uses Btree indexes to enforce unique constraints.

Assuming one wished to version ID_COLUMN based on the ACTIVE_FLAG being set to 1:

CREATE UNIQUE INDEX idx_versioning_id ON mytable 
  (CASE active_flag WHEN 0 THEN NULL ELSE active_flag END,
   CASE active_flag WHEN 0 THEN NULL ELSE id_column   END);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文