SQL Server 优化器确实包含删除冗余连接的逻辑,但存在一些限制,并且连接必须可证明是冗余的。总而言之,连接可以产生四种效果:
- 它可以添加额外的列(来自连接表)
- 它可以添加额外的行(连接的表可能会多次匹配源行)
- 它可以删除行(连接的表可能没有匹配)
- 它可以引入
NULL
s(对于 aRIGHT
或FULL JOIN
)
要成功删除冗余连接,查询(或视图)必须考虑所有四种可能性。正确完成此操作后,效果可能会令人惊讶。例如:
USE AdventureWorks2012;
GO
CREATE VIEW dbo.ComplexView
AS
SELECT
pc.ProductCategoryID, pc.Name AS CatName,
ps.ProductSubcategoryID, ps.Name AS SubCatName,
p.ProductID, p.Name AS ProductName,
p.Color, p.ListPrice, p.ReorderPoint,
pm.Name AS ModelName, pm.ModifiedDate
FROM Production.ProductCategory AS pc
FULL JOIN Production.ProductSubcategory AS ps ON
ps.ProductCategoryID = pc.ProductCategoryID
FULL JOIN Production.Product AS p ON
p.ProductSubcategoryID = ps.ProductSubcategoryID
FULL JOIN Production.ProductModel AS pm ON
pm.ProductModelID = p.ProductModelID
优化器可以成功简化以下查询:
SELECT
c.ProductID,
c.ProductName
FROM dbo.ComplexView AS c
WHERE
c.ProductName LIKE N'G%';
至:
Rob Farley 在最初的 MVP Deep Dives 书中深入地描述了这些想法,并且有一段他在 SQLBits上就该主题进行演讲的录音。
主要限制是外键关系必须基于单个键以有助于简化过程,并且针对这种视图的查询的编译时间可能会变得很长,特别是随着连接数量的增加。编写一个包含 100 个表且所有语义都完全正确的视图可能是一个相当大的挑战。我倾向于寻找替代解决方案,也许使用动态 SQL。
也就是说,非规范化表的特殊特性可能意味着视图非常易于组装,只需要强制执行的FOREIGN KEYs
不可NULL
引用列和适当UNIQUE
的约束即可使该解决方案按您希望的方式工作,而无需 100 个物理连接运算符的开销在计划中。
例子
使用十个表而不是一百个:
-- Referenced tables
CREATE TABLE dbo.Ref01 (col01 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref02 (col02 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref03 (col03 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref04 (col04 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref05 (col05 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref06 (col06 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref07 (col07 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref08 (col08 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref09 (col09 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
CREATE TABLE dbo.Ref10 (col10 tinyint PRIMARY KEY, item varchar(50) NOT NULL UNIQUE);
父表定义(带有页面压缩):
CREATE TABLE dbo.Normalized
(
pk integer IDENTITY NOT NULL,
col01 tinyint NOT NULL REFERENCES dbo.Ref01,
col02 tinyint NOT NULL REFERENCES dbo.Ref02,
col03 tinyint NOT NULL REFERENCES dbo.Ref03,
col04 tinyint NOT NULL REFERENCES dbo.Ref04,
col05 tinyint NOT NULL REFERENCES dbo.Ref05,
col06 tinyint NOT NULL REFERENCES dbo.Ref06,
col07 tinyint NOT NULL REFERENCES dbo.Ref07,
col08 tinyint NOT NULL REFERENCES dbo.Ref08,
col09 tinyint NOT NULL REFERENCES dbo.Ref09,
col10 tinyint NOT NULL REFERENCES dbo.Ref10,
CONSTRAINT PK_Normalized
PRIMARY KEY CLUSTERED (pk)
WITH (DATA_COMPRESSION = PAGE)
);
风景:
CREATE VIEW dbo.Denormalized
WITH SCHEMABINDING AS
SELECT
item01 = r01.item,
item02 = r02.item,
item03 = r03.item,
item04 = r04.item,
item05 = r05.item,
item06 = r06.item,
item07 = r07.item,
item08 = r08.item,
item09 = r09.item,
item10 = r10.item
FROM dbo.Normalized AS n
JOIN dbo.Ref01 AS r01 ON r01.col01 = n.col01
JOIN dbo.Ref02 AS r02 ON r02.col02 = n.col02
JOIN dbo.Ref03 AS r03 ON r03.col03 = n.col03
JOIN dbo.Ref04 AS r04 ON r04.col04 = n.col04
JOIN dbo.Ref05 AS r05 ON r05.col05 = n.col05
JOIN dbo.Ref06 AS r06 ON r06.col06 = n.col06
JOIN dbo.Ref07 AS r07 ON r07.col07 = n.col07
JOIN dbo.Ref08 AS r08 ON r08.col08 = n.col08
JOIN dbo.Ref09 AS r09 ON r09.col09 = n.col09
JOIN dbo.Ref10 AS r10 ON r10.col10 = n.col10;
修改统计信息以使优化器认为该表非常大:
UPDATE STATISTICS dbo.Normalized WITH ROWCOUNT = 100000000, PAGECOUNT = 5000000;
用户查询示例:
SELECT
d.item06,
d.item07
FROM dbo.Denormalized AS d
WHERE
d.item08 = 'Banana'
AND d.item01 = 'Green';
给我们这个执行计划:
规范化表的扫描看起来很糟糕,但是存储引擎在扫描期间应用了两个 Bloom-filter 位图(因此无法匹配的行甚至不会出现在查询处理器中)。这可能足以在您的情况下提供可接受的性能,并且肯定比扫描具有溢出列的原始表更好。
如果您能够在某个阶段升级到 SQL Server 2012 Enterprise,您还有另一个选择:在规范化表上创建列存储索引:
CREATE NONCLUSTERED COLUMNSTORE INDEX cs
ON dbo.Normalized (col01,col02,col03,col04,col05,col06,col07,col08,col09,col10);
执行计划是:
这对您来说可能看起来更糟,但是列存储提供了出色的压缩,并且整个执行计划在批处理模式下运行,并为所有贡献的列提供了过滤器。如果服务器有足够的线程和内存可用,这个替代方案真的可以实现。
最终,考虑到表的数量以及执行计划不佳或需要过多编译时间的可能性,我不确定这种规范化是正确的方法。我可能会首先更正非规范化表的架构(正确的数据类型等),可能会应用数据压缩......通常的事情。
如果数据确实属于星型模式,那么它可能需要更多的设计工作,而不仅仅是将重复的数据元素拆分到单独的表中。