我可以添加一个忽略现有违规的唯一约束吗?


40

我有一个表,当前在列中有重复的值。

我无法删除这些错误的重复项,但我想防止添加其他非唯一值。

我可以创建一个UNIQUE不检查现有合规性的商品吗?

我尝试使用,NOCHECK但未成功。

在这种情况下,我有一个表将许可信息与“ CompanyName”相关联

编辑:具有相同的“ CompanyName”具有多个行是错误的数据,但此时我们不能删除或更新这些重复项。一种方法是让INSERTs使用存储过程,该存储过程将导致重复操作失败...如果可以让SQL自己检查唯一性,那将是更好的选择。

该数据通过公司名称查询。对于少数现有重复项,这将意味着将返回并显示多行...虽然这是错误的,但在我们的用例中是可以接受的。目的是防止将来发生这种情况。从评论看来,我似乎必须在存储过程中执行此逻辑。


您是否可以更改表格(多添加一列)?
ypercubeᵀᴹ

不幸的是,@ ypercube没有。
马修·

Answers:


33

答案是“是”。您可以使用过滤索引来执行此操作(请参阅此处以获取文档)。

例如,您可以执行以下操作:

create unique index t_col on t(col) where id > 1000;

这将仅在行上而不是旧行上创建唯一索引。这种特定的表述将允许具有现有值的重复项。

如果只有少量重复项,则可以执行以下操作:

create unique index t_col on t(col) where id not in (<list of ids for duplicate values here>);

2
这是否好取决于“旧的”现有项目是否应阻止创建具有相同价值的新项目。
超级猫

1
@supercat。。。我给出了一种替代性的公式,用于在除现有重复值之外的所有内容上构建索引。
Gordon Linoff 2013年

1
为了使后者起作用,必须确保从列表中删除每个重复的不同键值的ID,并且还必须确保是否故意从列表中删除了从列表中删除的项。 ,则具有相同键的项将从列表中删除。
2013年

@supercat。。。我同意。保持索引一致以进行更新和删除更具挑战性,因为您无法在触发器中重新创建索引。无论如何,OP给我的印象是,数据(至少是重复项)并不会经常更改,即使有的话也不会更改。
Gordon Linoff 2013年

为什么不排除值列表而不是ID列表?这样,您就不必从排除的ID列表中排除每个重复值一个ID
JMD Coalesce

23

是的,你可以这样做。

这是一个重复的表:

CREATE TABLE dbo.Party
  (
    ID INT NOT NULL
           IDENTITY ,
    CONSTRAINT PK_Party PRIMARY KEY ( ID ) ,
    Name VARCHAR(30) NOT NULL
  ) ;
GO

INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' ),
        ( 'Luke Skywalker' ),
        ( 'Luke Skywalker' ),
        ( 'Harry Potter' ) ;
GO

让我们忽略现有的副本,并确保不能添加新的副本:

-- Add a new column to mark grandfathered duplicates.
ALTER TABLE dbo.Party ADD IgnoreThisDuplicate INT NULL ;
GO

-- The *first* instance will be left NULL.
-- *Secondary* instances will be set to their ID (a unique value).
UPDATE  dbo.Party
SET     IgnoreThisDuplicate = ID
FROM    dbo.Party AS my
WHERE   EXISTS ( SELECT *
                 FROM   dbo.Party AS other
                 WHERE  other.Name = my.Name
                        AND other.ID < my.ID ) ;
GO

-- This constraint is not strictly necessary.
-- It prevents granting further exemptions beyond the ones we made above.
ALTER TABLE dbo.Party WITH NOCHECK
ADD CONSTRAINT CHK_Party_NoNewExemptions 
CHECK(IgnoreThisDuplicate IS NULL);
GO

SELECT * FROM dbo.Party;
GO

-- **THIS** is our pseudo-unique constraint.
-- It works because the grandfathered duplicates have a unique value (== their ID).
-- Non-grandfathered records just have NULL, which is not unique.
CREATE UNIQUE INDEX UNQ_Party_UniqueNewNames ON dbo.Party(Name, IgnoreThisDuplicate);
GO

让我们测试这个解决方案:

-- cannot add a name that exists
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Frodo Baggins' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

-- cannot add a name that exists and has an ignored duplicate
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Luke Skywalker' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.


-- can add a new name 
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

-- but only once
INSERT  INTO dbo.Party
        ( Name )
VALUES  ( 'Hamlet' );

Cannot insert duplicate key row in object 'dbo.Party' with unique index 'UNQ_Party_UniqueNewNames'.

4
除了他不能在表中添加列。
亚伦·伯特兰

3
我喜欢这个答案如何将如何以非标准方式在唯一约束中将NULL值处理成有用的东西。狡猾的把戏。
ypercubeᵀᴹ

@ypercubeᵀᴹ,您能解释一下在唯一约束中NULL处理的非标准之处吗?它与您的预期有何不同?谢谢!
Noach

1
@Noach在SQL Server中,UNIQUE可为空的列中的约束可确保最多只有一个NULL值。SQL标准(以及几乎所有其他SQL DBMS)说,它应允许任意数量的NULL值(即约束应忽略空值)。
ypercubeᵀᴹ

@ypercubeᵀᴹ因此,要在其他DBMS上实现此功能,我们只需要使用DEFAULT 0而不是NULL。正确?
Noach

16

过滤后的唯一索引是一个绝妙的主意,但它有一个次要的缺点-无论使用WHERE identity_column > <current value>条件还是WHERE identity_column NOT IN (<list of ids for duplicate values here>)

使用第一种方法,将来您仍然可以插入重复数据,即现有(现在)数据的重复。例如,如果您现在具有(甚至只有一行)行CompanyName = 'Software Inc.',则索引不会禁止再插入一行具有相同公司名称的行。如果您尝试两次,它只会禁止它。

通过第二种方法,有一个改进,上述方法不起作用(很好。)但是,您仍然可以插入更多重复项或现有重复项。例如,如果您现在使用(具有两行或更多行)CompanyName = 'DoubleData Co.',则索引不会禁止再插入一行具有相同公司名称的行。如果您尝试两次,它只会禁止它。

(更新)如果对于每个重复的名称,您在排除列表中都保留一个ID,则可以更正此错误。如果像上面的示例一样,有4行具有重复的CompanyName = DoubleData Co.ID和ID 4,6,8,9,则排除列表应仅包含3个ID。

使用第二种方法时,另一个缺点是麻烦的条件(多少麻烦首先取决于有多少重复项),因为SQL-Server似乎NOT INWHERE过滤索引的一部分中不支持运算符。参见SQL-Fiddle。相反的WHERE (CompanyID NOT IN (3,7,4,6,8,9)),你必须有这样的事WHERE (CompanyID <> 3 AND CompanyID <> 7 AND CompanyID <> 4 AND CompanyID <> 6 AND CompanyID <> 8 AND CompanyID <> 9)我不知道,如果有这样的条件下效率的影响,如果你有上百个重名的。


另一个解决方案(类似于@Alex Kuznetsov的解决方案)是添加另一列,用等级号填充它,并添加包括该列的唯一索引:

ALTER TABLE Company
  ADD Rn TINYINT DEFAULT 1;

UPDATE x
SET Rn = Rnk
FROM
  ( SELECT 
      CompanyID,
      Rn,
      Rnk = ROW_NUMBER() OVER (PARTITION BY CompanyName 
                               ORDER BY CompanyID)
    FROM Company 
  ) x ;

CREATE UNIQUE INDEX CompanyName_UQ 
  ON Company (CompanyName, Rn) ; 

然后,由于DEFAULT 1属性和唯一索引,插入具有重复名称的行将失败。这仍然不是100%万无一失的(而Alex是)。如果RnINSERT语句中显式设置了或Rn恶意更新了值,重复项仍会遗漏。

SQL提琴2



-2

我正在寻找相同的内容-创建一个不受信任的唯一索引,以便忽略现有的不良数据,但是新记录不能与任何已存在的记录重复。

在阅读此线程时,我想到一个更好的解决方案是编写一个触发器,该触发器将针对父表检查[插入]重复项,如果在这些表之间存在任何重复项,则为ROLLBACK TRAN。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.