过滤后的唯一索引是一个绝妙的主意,但它有一个次要的缺点-无论使用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 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提琴2