我无法删除这些错误的重复项,但我想防止添加其他非唯一值。
我可以创建一个不检查现有合规性的
UNIQUE
吗?我尝试使用
NOCHECK
但未成功。在这种情况下,我有一个表与许可信息相关联更改为“ CompanyName”
编辑:具有相同“ CompanyName”的多行是错误的数据,但是我们目前无法删除或更新这些重复项。一种方法是让
INSERT
使用存储过程,该存储过程将导致重复操作失败...如果可以让SQL自己检查唯一性,那将是更好的选择。此数据是按公司名称查询。对于少量的现有重复项,这将意味着返回并显示多行...虽然这是错误的,但在我们的用例中是可以接受的。目的是防止将来发生这种情况。从评论看来,我似乎必须在存储过程中执行此逻辑。
#1 楼
答案是“是”。您可以使用过滤索引来执行此操作(请参见此处以获取文档)。例如,您可以执行以下操作: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>);
评论
这是否好取决于“旧”现有项目是否应阻止创建具有相同价值的新项目。
–超级猫
13年4月4日在18:34
@supercat。 。 。我给出了一种替代的方法,用于在除现有重复值之外的所有内容上构建索引。
–戈登·利诺夫(Gordon Linoff)
13年4月4日在18:35
为了使后者起作用,必须确保从列表中删除一个具有重复的不同键值的ID,并且还必须确保是否故意从列表中删除了该项目。 ,则具有相同键的项将从列表中删除。
–超级猫
13年4月4日在18:44
@supercat。 。 。我同意。保持索引一致以进行更新和删除更具挑战性,因为您无法在触发器中重新创建索引。无论如何,我从OP中得出的印象是,数据(或至少是重复项)并不会经常更改,甚至根本不会更改。
–戈登·利诺夫(Gordon Linoff)
13年4月4日在18:53
为什么不排除值列表而不是ID列表?然后,您不必从排除的ID列表中为每个重复的值排除一个ID
– JMD Coalesce
17年11月29日在19:14
#2 楼
是的,你可以这样做。这是一个包含重复项的表:
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'.
评论
除了他不能在表中添加列。
–亚伦·伯特兰(Aaron Bertrand)
13年6月4日在21:09
我喜欢这个答案如何将如何以独特的约束以非标准的方式对待NULL值变成有用的东西。狡猾的把戏。
–超立方体ᵀᴹ
13年4月4日在21:33
@ypercubeᵀᴹ,您能解释一下在唯一约束中NULL处理的非标准之处吗?它与您的预期有何不同?谢谢!
– Noach
17年11月29日在16:46
@Noach在SQL Server中,可为空列中的UNIQUE约束可确保最多只有一个NULL值。 SQL标准(以及几乎所有其他SQL DBMS)说,它应允许任意数量的NULL值(即约束应忽略空值)。
–超立方体ᵀᴹ
17年11月29日在18:47
@ypercubeᵀᴹ因此,要在其他DBMS上实现此功能,我们只需要使用DEFAULT 0而不是NULL。正确?
– Noach
17年11月29日在21:45
#3 楼
过滤后的唯一索引是一个绝妙的主意,但是它有一个次要的缺点-无论使用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为4,6,8,9
,则排除列表应仅包含这些ID中的3个。采用第二种方法的另一个缺点是麻烦的条件(多少繁琐的操作首先取决于有多少个重复项),因为SQL-Server在过滤索引的
NOT IN
部分中似乎不支持WHERE
运算符。请参见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是)。如果在Rn
语句中显式设置了INSERT
,或者恶意更新了Rn
值,重复项仍会遗漏。 SQL-Fiddle-2
#4 楼
另一种选择是编写一个标量函数,该函数检查表中是否已经存在一个值,然后从检查约束中调用该函数。这将对性能造成可怕的影响。
评论
我强烈建议不要考虑使用检查约束中的标量函数。
–亚伦·伯特兰(Aaron Bertrand)
13年6月6日,下午3:28
除了Aaron指出的问题外,答案没有说明如何添加此检查约束,因此它会忽略现有的重复项。
–超立方体ᵀᴹ
2015年10月1日,13:24
#5 楼
我正在寻找相同的对象-创建一个不受信任的唯一索引,以便忽略现有的不良数据,但是新记录不能与已存在的任何内容重复。在阅读此线程时,它涉及到我认为更好的解决方案是编写一个触发器,该触发器将针对父表检查[插入]是否存在重复,如果在这些表之间存在任何重复,则使用ROLLBACK TRAN。
评论
您是否可以更改表格(多添加一列)?@ypercube不幸的是没有。