Mar*_*lli 19 index sql-server filtered-index sql-server-2014 computed-column
在我之前的一个问题中,在向表中添加新的计算列时禁用锁升级是个好主意吗?,我正在创建一个计算列:
ALTER TABLE dbo.tblBGiftVoucherItem
ADD isUsGift AS CAST
(
ISNULL(
CASE WHEN sintMarketID = 2
AND strType = 'CARD'
AND strTier1 LIKE 'GG%'
THEN 1
ELSE 0
END
, 0)
AS BIT
) PERSISTED;
Run Code Online (Sandbox Code Playgroud)
计算列是PERSISTED
,并且根据计算列定义(Transact-SQL):
坚持
指定数据库引擎将计算值物理存储在表中,并在计算列所依赖的任何其他列更新时更新值。将计算列标记为 PERSISTED 允许在确定性但不精确的计算列上创建索引。有关更多信息,请参阅计算列上的索引。任何用作分区表的分区列的计算列都必须显式标记为 PERSISTED。当指定 PERSISTED 时,computed_column_expression 必须是确定性的。
但是当我尝试在我的列上创建索引时,我收到以下错误:
CREATE INDEX FIX_tblBGiftVoucherItem_incl
ON dbo.tblBGiftVoucherItem (strItemNo)
INCLUDE (strTier3)
WHERE isUsGift = 1;
Run Code Online (Sandbox Code Playgroud)
无法在表 'dbo.tblBGiftVoucherItem' 上创建过滤索引 'FIX_tblBGiftVoucherItem_incl',因为过滤器表达式中的列 'isUsGift' 是计算列。重写过滤器表达式,使其不包含此列。
如何在计算列上创建过滤索引?
或者
有替代的解决方案吗?
Han*_*non 21
尽管您无法在持久化列上创建筛选索引,但您可以使用一种相当简单的解决方法。
作为测试,我创建了一个简单的表,其中包含一个IDENTITY
列和一个基于标识列的持久计算列:
USE tempdb;
CREATE TABLE dbo.PersistedViewTest
(
PersistedViewTest_ID INT NOT NULL
CONSTRAINT PK_PersistedViewTest
PRIMARY KEY CLUSTERED
IDENTITY(1,1)
, SomeData VARCHAR(2000) NOT NULL
, TestComputedColumn AS (PersistedViewTest_ID - 1) PERSISTED
);
GO
Run Code Online (Sandbox Code Playgroud)
然后,我创建了一个基于表的模式绑定视图,在计算列上有一个过滤器:
CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID
, SomeData
, TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
Run Code Online (Sandbox Code Playgroud)
接下来,我在模式绑定视图上创建了一个聚集索引,它的作用是持久化存储在视图中的值,包括计算列的值:
CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO
Run Code Online (Sandbox Code Playgroud)
在表中插入一些测试数据:
INSERT INTO dbo.PersistedViewTest (SomeData)
SELECT o.name + o1.name + o2.name
FROM sys.objects o
CROSS JOIN sys.objects o1
CROSS JOIN sys.objects o2;
Run Code Online (Sandbox Code Playgroud)
在视图上创建一个统计项和一个索引:
CREATE STATISTICS ST_PersistedViewTest_View
ON dbo.PersistedViewTest_View(TestComputedColumn)
WITH FULLSCAN;
CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);
Run Code Online (Sandbox Code Playgroud)
如果查询优化器确定这样做有意义,则对SELECT
具有持久化列的表执行语句现在可以自动使用持久化视图:
SELECT pv.PersistedViewTest_ID
, pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
Run Code Online (Sandbox Code Playgroud)
上述查询的实际执行计划显示查询优化器选择使用持久化视图返回结果:
您可能已经注意到WHERE
上面子句中的显式转换。这个显式CONVERT(INT, 26)
允许查询优化器正确使用统计对象来估计查询将返回的行数。如果我们使用 编写查询WHERE pv.TestComputedColumn = 26
,查询优化器可能无法正确估计行数,因为 26 实际上被认为是TINY INT
;这可能会导致 SQL Server 不使用持久化视图。隐式转换可能非常痛苦,并且始终使用正确的数据类型进行比较和连接是值得的。
当然,使用模式绑定产生的所有标准“陷阱”都适用于上述场景;这可能会阻止在所有情况下使用此解决方法。例如,如果不首先从视图中删除模式绑定,将不再可能修改基表。为此,您需要从视图中删除聚集索引。
如果您没有 SQL Server 企业版,查询优化器将不会自动将持久视图用于未使用WITH (NOEXPAND)
提示直接引用视图的查询。要实现在非企业版版本中使用持久化视图的好处,您需要将上面的查询重新编写为:
SELECT pv.PersistedViewTest_ID
, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
Run Code Online (Sandbox Code Playgroud)
感谢Ian Ringrose指出上述企业版的限制,感谢Paul White的(NOEXPAND)
提示。
Paul 的这个答案有一些关于与持久视图相关的查询优化器的有趣细节。