跨表的SQL Server唯一索引

Phi*_*ill 12 sql sql-server unique-index

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

我有一个问题.

给出两个(或三个)表.

Company
- Id
- Name

Brand
- Id
- CompanyId
- Name
- Code

Product
- Id
- BrandId
- Name
- Code
Run Code Online (Sandbox Code Playgroud)

我想确保组合的独特性:

Company / Brand.Code
Run Code Online (Sandbox Code Playgroud)

Company / Brand.Product/Code
Run Code Online (Sandbox Code Playgroud)

很独特.

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

视图的创建是成功的.

CREATE UNIQUE CLUSTERED INDEX UIX_UniquePrefixCode
    ON TestView(CompanyId, Code)
Run Code Online (Sandbox Code Playgroud)

因为这个失败了 UNION

我该如何解决这个问题呢?

基本上,两者的代码都Brand/Product不能在公司内重复.

笔记:

我得到的错误是:

消息10116,级别16,状态1,行3无法在视图'XXXX.dbo.TestView'上创建索引,因为它包含一个或多个UNION,INTERSECT或EXCEPT运算符.考虑为每个查询创建一个单独的索引视图,该视图是原始视图的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 )
Run Code Online (Sandbox Code Playgroud)

如果我们扩大结果,期望是,Brand Code + Company或者Product 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
Run Code Online (Sandbox Code Playgroud)

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

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

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

Company / Brand|Product Code
1 / 999 <-- Brand
2 / 999 <-- Product
Run Code Online (Sandbox Code Playgroud)

这是独一无二的.

但如果你有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)
Run Code Online (Sandbox Code Playgroud)

这会产生

Company / Brand|Product Code
1 / 777 <-- Brand
1 / 888 <-- Brand
1 / 777 <-- Product
Run Code Online (Sandbox Code Playgroud)

这是不允许的.

希望有道理.

注4:

@ spaghettidba的答案解决了跨表问题,第二个问题在Brand表本身中是重复的.

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

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

spa*_*dba 7

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

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

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

CREATE TABLE tworows (
    n int
)

INSERT INTO tworows values (1),(2)
GO
Run Code Online (Sandbox Code Playgroud)

这是索引视图:

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

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

UPDATE product SET code = 300 WHERE code = 301
Run Code Online (Sandbox Code Playgroud)

实际上你得到一个错误:

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

希望这可以帮助.