跨表的 SQL Server 唯一索引

Posted

技术标签:

【中文标题】跨表的 SQL Server 唯一索引【英文标题】:SQL Server Unique Index across tables 【发布时间】:2014-02-06 08:38:46 【问题描述】:

可以跨表创建唯一索引,基本上使用视图和唯一索引。

我有一个问题。

给定两个(或三个)表。

Company
- Id
- Name

Brand
- Id
- CompanyId
- Name
- Code

Product
- Id
- BrandId
- Name
- Code

我想确保以下组合的唯一性:

Company / Brand.Code

Company / Brand.Product/Code

是独一无二的。

CREATE VIEW TestView
WITH SCHEMABINDING
AS
    SELECT b.CompanyId, b.Code
    FROM dbo.Brand b

    UNION ALL

    SELECT b.CompanyId, p.Code
    FROM dbo.Product p
         INNER JOIN dbo.Brand b ON p.BrandId = b.BrandId

视图创建成功。

CREATE UNIQUE CLUSTERED INDEX UIX_UniquePrefixCode
    ON TestView(CompanyId, Code)

由于UNION 而失败

我该如何解决这种情况?

基本上,Brand/Product 的代码不能在公司内复制。

注意事项:

我得到的错误是:

消息 10116,级别 16,状态 1,行 3 无法在视图上创建索引 'XXXX.dbo.TestView' 因为它包含一个或多个 UNION、INTERSECT、 或除了运算符。考虑为 作为 UNION、INTERSECT 或 EXCEPT 输入的每个查询 原始视图的运算符。

注意事项 2:

当我使用子查询时,我收到以下错误:

消息 10109,级别 16,状态 1,行 3 无法在视图上创建索引 “XXXX.dbo.TestView”,因为它引用了派生表“a” (由 FROM 子句中的 SELECT 语句定义)。考虑删除 引用派生表或不索引视图。

**注3:**

所以考虑到品牌:

来自@spaghettidba 的回答。

INSERT INTO Brand
(
    Id,
    CompanyId,
    Name,
    Code
)
VALUES 
(1, 1, 'Brand 1', 100 ),
(2, 2, 'Brand 2', 200 ),
(3, 3, 'Brand 3', 300 ),
(4, 1, 'Brand 4', 400 ),
(5, 3, 'Brand 5', 500 )

INSERT INTO Product
(
    Id,
    BrandId,
    Name,
    Code
)
VALUES
(1001, 1, 'Product 1001', 1 ),
(1002, 1, 'Product 1002', 2 ),
(1003, 3, 'Product 1003', 3 ),
(1004, 3, 'Product 1004', 301 ),
(1005, 4, 'Product 1005', 5 )

如果我们将结果展开,我们的期望是,Brand Code + CompanyProduct Code + Company 是唯一的。

Company / Brand|Product Code
1 / 100 <-- Brand
1 / 400 <-- Brand
1 / 1   <-- Product
1 / 2   <-- Product
1 / 5   <-- Product

2 / 200 <-- Brand

3 / 300 <-- Brand
3 / 500 <-- Brand
3 / 3   <-- Product
3 / 301 <-- Brand

没有重复。如果我们有一个品牌和产品具有相同的代码。

INSERT INTO Brand
(
    Id,
    CompanyId,
    Name,
    Code
)
VALUES 
(6, 1, 'Brand 6', 999)

INSERT INTO Product
(
    Id,
    BrandId,
    Name,
    Code
)
VALUES
(1006, 2, 'Product 1006', 999)

产品属于不同的公司,所以我们得到

Company / Brand|Product Code
1 / 999 <-- Brand
2 / 999 <-- Product

这是独一无二的。

但如果您有 2 个品牌和 1 个产品。

INSERT INTO Brand
(
    Id,
    CompanyId,
    Name,
    Code
)
VALUES 
(7, 1, 'Brand 7', 777)
(8, 1, 'Brand 8', 888)

INSERT INTO Product
(
    Id,
    BrandId,
    Name,
    Code
)
VALUES
(1007, 8, 'Product 1008', 777)

这会产生

Company / Brand|Product Code
1 / 777 <-- Brand
1 / 888 <-- Brand
1 / 777 <-- Product

这是不允许的。

希望这是有道理的。

注意事项 4:

@spaghettidba 的回答解决了跨表问题,第二个问题是品牌表本身的重复问题。

我已经设法通过在品牌表上创建一个单独的索引来解决这个问题:

CREATE UNIQUE NONCLUSTERED INDEX UIX_UniquePrefixCode23
    ON Brand(CompanyId, Code)
    WHERE Code IS NOT NULL;

【问题讨论】:

你说的是索引视图/实体化视图 @hrishi 在 SQL Server 中,物化视图称为索引视图。 【参考方案1】:

早在 2011 年,我就曾在博客中介绍过类似的解决方案。您可以在此处找到该帖子: http://spaghettidba.com/2011/08/03/enforcing-complex-constraints-with-indexed-views/

基本上,您必须创建一个恰好包含两行的表,并且您将在 CROSS JOIN 中使用该表来复制违反您的业务规则的行。

在您的情况下,由于您表达业务规则的方式,索引视图的编码有点困难。事实上,正如您已经看到的那样,不允许通过索引视图检查 UNIONed 表的唯一性。

但是,约束可以用不同的方式表示:由于 companyId 是由品牌隐含的,您可以避免使用 UNION,只需在产品和品牌之间使用 JOIN,并通过在代码本身上添加 JOIN 谓词来检查唯一性.

您没有提供一些示例数据,希望您不要介意我为您提供:

CREATE TABLE Company (
    Id int PRIMARY KEY,
    Name varchar(50)
)

CREATE TABLE Brand (
    Id int PRIMARY KEY,
    CompanyId int,
    Name varchar(50),
    Code int
)

CREATE TABLE Product (
    Id int PRIMARY KEY,
    BrandId int,
    Name varchar(50),
    Code int
)
GO

INSERT INTO Brand
(
    Id,
    CompanyId,
    Name,
    Code
)
VALUES (1, 1, 'Brand 1', 100 ),
(2, 2, 'Brand 2', 200 ),
(3, 3, 'Brand 3', 300 ),
(4, 1, 'Brand 4', 400 ),
(5, 3, 'Brand 5', 500 )



INSERT INTO Product
(
    Id,
    BrandId,
    Name,
    Code
)
VALUES
(1001, 1, 'Product 1001', 1 ),
(1002, 1, 'Product 1002', 2 ),
(1003, 3, 'Product 1003', 3 ),
(1004, 3, 'Product 1004', 301 ),
(1005, 4, 'Product 1005', 5 )

据我所知,目前还没有违反业务规则的行。

现在我们需要索引视图和两行表:

CREATE TABLE tworows (
    n int
)

INSERT INTO tworows values (1),(2)
GO

这是索引视图:

CREATE VIEW TestView
WITH SCHEMABINDING
AS
SELECT 1 AS one
FROM dbo.Brand b
INNER JOIN dbo.Product p
    ON p.BrandId = b.Id
    AND p.code = b.code
CROSS JOIN dbo.tworows AS t
GO

CREATE UNIQUE CLUSTERED INDEX IX_TestView ON dbo.TestView(one)

此更新应该违反业务规则:

UPDATE product SET code = 300 WHERE code = 301

事实上你得到一个错误:

Msg 2601, Level 14, State 1, Line 1
Cannot insert duplicate key row in object 'dbo.TestView' with unique index 'IX_TestView'. The duplicate key value is (1).
The statement has been terminated.

希望这会有所帮助。

【讨论】:

Looks like we both had this idea in 2011!我已经在实践中使用过几次了。有时有点痛苦,因为 SQL Server 在语句末尾检查所有约束而不是事务,因此有时需要以正确的顺序进行更改以避免违规。如果涉及 ORM,尤其是痛苦。 我认为这只能保证一种产品及其相关品牌的独特性。但是我可以通过删除连接条件p.BrandId = b.Id 来了解如何解决它。你同意吗? @usr 我不确定,恕我直言,这些要求令人困惑。也许菲尔可以回答这个问题。 @spaghettidba - 我更新了一些笔记。在我明天上班之前我无法尝试你的解决方案,但我很想尝试一下!!!!非常感谢! 嗯@spaghettidba 它非常适合防止重复产品,但它允许重复的品牌代码:( 试图弄清楚如何防止两者。

以上是关于跨表的 SQL Server 唯一索引的主要内容,如果未能解决你的问题,请参考以下文章

SQL server 2005如何设置一个或几个字段唯一约束?

译SQL Server索引进阶第八篇:唯一索引

sql server 索引阐述系列四 表的B-Tree组织

SQL server 数据库之“索引”详解

sqlserver2008 啥是唯一键? 怎样设置唯一键

SQL SERVER中索引类型包括的三种类型分别是哪三种?