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

Mat*_*hew 46 database-design sql-server azure-sql-database

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

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

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

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

在这种情况下,我有一个表格将许可信息与“公司名称”联系起来

编辑:具有相同“公司名称”的多行是错误数据,但我们目前无法删除或更新这些重复项。一种方法是让INSERTs 使用一个存储过程,该过程将因重复而失败......如果可以让 SQL 自行检查唯一性,那将是可取的。

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

Gor*_*off 36

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

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

create unique index t_col on t(col) where id > 1000;
Run Code Online (Sandbox Code Playgroud)

这将创建一个唯一索引,仅在行上,而不是在旧行上。这种特殊的公式将允许具有现有值的重复项。

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

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

  • 对于后者的工作,必须确保从列表中省略每个具有重复项的不同键值的 id,并且还必须确保故意从列表中省略的项目是否已从表中删除,具有相同键的项目将从列表中删除。 (3认同)
  • 这是否好取决于“旧”现有项目是否应该阻止创建具有相同价值的新项目。 (2认同)

A-K*_*A-K 25

是的,你可以这样做。

这是一个重复的表:

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
Run Code Online (Sandbox Code Playgroud)

让我们忽略现有的,并确保不会添加新的重复项:

-- 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
Run Code Online (Sandbox Code Playgroud)

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

-- 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'.
Run Code Online (Sandbox Code Playgroud)

  • 我喜欢这个答案如何将在唯一约束中以非标准方式处理 NULL 值的方式转变为有用的东西。狡猾的伎俩。 (5认同)
  • 除了他不能在表格中添加一列。 (4认同)
  • @Noach 在 SQL Server 中,可空列中的“UNIQUE”约束确保最多只有一个“NULL”值。SQL 标准(以及几乎所有其他 SQL DBMS)说它应该允许任意数量的 `NULL` 值(即约束应该忽略空值)。 (2认同)

ype*_*eᵀᴹ 17

过滤的唯一索引是一个绝妙的主意,但它有一个小缺点 - 无论您使用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.和 IDs 4,6,8,9,排除列表应该只有 3 个这些 IDs。

第二种方法的另一个缺点是繁琐的条件(繁琐程度首先取决于有多少重复项),因为 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) ; 
Run Code Online (Sandbox Code Playgroud)

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

SQL-Fiddle-2