【问题标题】:Unique constraint on two fields, and their opposite两个字段的唯一约束,以及它们的相反
【发布时间】:2014-04-16 08:03:49
【问题描述】:

我有一个数据结构,我必须在其中存储成对的元素。每对都恰好有 2 个值,因此我们使用了一个表,其中包含字段(leftvalue,rightvalue....)。 这些对应该是唯一的,并且如果更改了键,则它们被认为是相同的。

Example: (Fruit, Apple) is the same as (Apple, Fruit).

如果有可能以一种有效的方式,我会在字段上设置一个数据库约束,但不以任何成本为代价 - 性能更重要。

我们目前正在使用MSSQL server 2008,但可以进行更新。

有没有一种有效的方法来实现这一点?

【问题讨论】:

  • 值得一提的是,任何约束都会对插入物产生成本效益。虽然您的检索查询可能会更快,这取决于约束,所以它甚至可能...
  • 如果左值和右值是可互换的(如果(left,right)(right,left) 是“相等的”,这通常是暗示的),你能不能对表应用约束,使得left 是总是<right?
  • 性能是指插入性能吗?在不影响插入性能的情况下索引或约束数据是不可能的,总是有一些成本。
  • 主要目标是实现读取性能,但插入不应受到太大影响。 Damien 的解决方案看起来是个好主意。
  • @Robert 读取性能永远不会受到约束的影响。如果有的话,它只会变得更快。

标签: sql sql-server sql-server-2008 unique


【解决方案1】:

两种解决方案,都是为了将​​问题转化为更简单的问题。如果强制消费者改变是可以接受的,我通常更喜欢T1 解决方案:

create table dbo.T1 (
    Lft int not null,
    Rgt int not null,
    constraint CK_T1 CHECK (Lft < Rgt),
    constraint UQ_T1 UNIQUE (Lft,Rgt)
)
go
create table dbo.T2 (
    Lft int not null,
    Rgt int not null
)
go
create view dbo.T2_DRI
with schemabinding
as
    select
        CASE WHEN Lft<Rgt THEN Lft ELSE Rgt END as Lft,
        CASE WHEN Lft<Rgt THEN Rgt ELSE Lft END as Rgt
    from dbo.T2
go
create unique clustered index IX_T2_DRI on dbo.T2_DRI(Lft,Rgt)
go

在这两种情况下,T1T2 都不能在 Lft,Rgt 对中包含重复值。

【讨论】:

  • T1 解决方案听起来不错,我会再等一下其他答案,但看起来我会接受的答案:)
  • @Robert 如果您能够更改您的逻辑以便它们始终以正确的顺序输入(即 Apple、Fruit 而不是 Fruit、Apple)然后执行此操作。如果你不能考虑插入一个存储过程,那么你可以这样做。如果你不能这样做,那么使用其他更“hacky”的解决方案之一!
  • @Robert,如果LeftRight 被颠倒,或者这对的“惯用手”实际上无关紧要,是否不需要存储?
  • 在当前情况下,“惯用手”是无关紧要的。有一个案例,它会很困难,但现在不是。
  • @Robert,在这种情况下,我的回答对这个极早的回答 +1 没有任何帮助。
【解决方案2】:

如果您总是按顺序存储值但将方向存储在另一列中,

CREATE TABLE [Pairs]
(
    [A] NVarChar(MAX) NOT NULL,
    [B] NVarChar(MAX) NOT NULL,
    [DirectionAB] Bit NOT NULL,
    CONSTRAINT [PK_Pairs] PRIMARY KEY ([A],[B]) 
)

您可以使用一个聚簇索引准确地实现您想要的,并优化您的查找。

所以当我插入 'Apple', 'Fruit' 时,我会这样做,

INSERT [Pairs] VALUES ('Apple', 'Friut', 1);

很好很容易。然后我插入'Fruit', 'Apple'

INSERT [Pairs] VALUES ('Apple', 'Fruit', 0); -- 0 becuase order is reversed.

插入失败,因为这是主键违规。为了进一步说明,'Coconuts', 'Bananas' 对将存储为

INSERT [Pairs] VALUES ('Bananas', 'Coconuts', 0);

为了提高查找性能,我会添加索引

CREATE NONCLUSTERED INDEX [IX_Pairs_Reverse] ON [Pairs] ([B], [A]);

如果您无法控制对表的插入,则可能需要确保正确插入 [A][B]

CONSTRAINT [CK_Pairs_ALessThanB] CHECK ([A] < [B])

但这可能会对性能造成不必要的影响,具体取决于插入的控制程度。

【讨论】:

    【解决方案3】:

    一种方法是创建一个计算列,将这两个值结合起来并对其施加唯一约束:

    create table #test (
        a varchar(10) not null, 
        b varchar(10) not null, 
        both as case when a > b then a + ':' + b else b + ':' + a end persisted unique nonclustered
        )
    

    所以

    insert #test
    select 'apple', 'fruit'
    insert #test
    select 'fruit', 'apple'
    

    给予

    (1 row(s) affected)
    Msg 2627, Level 14, State 1, Line 3
    Violation of UNIQUE KEY constraint 'UQ__#test_____55252CB631EC6D26'. Cannot insert duplicate key in object 'dbo.#test'.
    The statement has been terminated.
    

    【讨论】:

    • 很好,但您还必须确保您的分隔符不存在于您的值域中,或者其他事件以某种方式被转义。这将不允许我们同时插入 (apple:z,fruit) 和 (apple,z:fruit) 对,尽管所有四个值都是不同的。
    • 当然,这不是最好的。如果你知道底层数据类型,使用填充和诸如此类的东西,它可能会起作用,但是是的......公平地说,如果你做得正确,你会强制执行排序!
    【解决方案4】:
    猜你喜欢
    • 1970-01-01
    • 2013-01-19
    • 1970-01-01
    • 1970-01-01
    • 2015-12-17
    • 2021-08-28
    • 2018-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多