无法在计算列上创建筛选索引

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' 是计算列。重写过滤器表达式,使其不包含此列。

如何在计算列上创建过滤索引?

或者

有替代的解决方案吗?

Mar*_*son 22

不幸的是,从 SQL Server 2014 开始,无法创建Filtered Index过滤器在计算列上的位置(无论它是否持久化)。

自 2009 年以来一直有一个连接项目开放,所以请继续投票。也许微软有一天会解决这个问题。

Aaron Bertrand 有一篇文章涵盖了许多其他关于过滤索引的问题。


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 的这个答案有一些关于与持久视图相关的查询优化器的有趣细节。


Jul*_*eur 5

FromCreate Index及其where子句,这是不可能的:

在哪里

通过指定要包含在索引中的行来创建过滤索引。过滤索引必须是表上的非聚集索引。为筛选索引中的数据行创建筛选统计信息。

过滤谓词使用简单的比较逻辑,不能引用计算列、UDT 列、空间数据类型列或hierarchyID 数据类型列。比较运算符不允许使用 NULL 文字进行比较。请改用 IS NULL 和 IS NOT NULL 运算符。

来源:MSDN