无法在计算列上创建过滤索引


18

在我的上一个问题中,在向表中添加新的计算列时禁用锁升级是个好主意吗?,我正在创建一个计算列:

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;

计算所得的列是PERSISTED,并且根据computed_column_definition(Transact-SQL)

坚持

指定数据库引擎将物理地将计算出的值存储在表中,并在更新计算出的列所依赖的任何其他列时更新这些值。将计算列标记为PERSISTED允许在确定的但不精确的计算列上创建索引。有关更多信息,请参见计算列上的索引。任何用作分区表分区列的计算列都必须显式标记为PERSISTED。当指定PERSISTED时,computed_column_expression必须是确定性的。

但是,当我尝试在列上创建索引时,出现以下错误:

CREATE INDEX FIX_tblBGiftVoucherItem_incl
ON dbo.tblBGiftVoucherItem (strItemNo) 
INCLUDE (strTier3)
WHERE isUsGift = 1;

无法在表'dbo.tblBGiftVoucherItem'上创建过滤索引'FIX_tblBGiftVoucherItem_incl',因为过滤表达式中的'isUsGift'列是计算列。重写过滤器表达式,使其不包括此列。

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

要么

有替代解决方案吗?


3
您可以在上面创建过滤索引WHERE (sintMarketID = 2 AND strType = 'CARD' AND strTier1 LIKE 'GG%')
ypercubeᵀᴹ

Answers:


21

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

自2009年以来已经有一个Connect Item开放,所以请继续投票。也许微软会修复这一天。

亚伦·伯特兰(Aaron Bertrand)的文章涵盖了过滤索引的其他许多问题。


21

尽管您不能在持久化列上创建过滤索引,但是您可以使用一个相当简单的解决方法。

作为测试,我创建了一个带有IDENTITY列的简单表,并基于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

然后,我基于该表创建了一个架构绑定视图,并在计算列上使用了过滤器:

CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID
    , SomeData 
    , TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);

接下来,我在绑定模式的视图上创建了聚集索引,其作用是持久存储视图中存储的值,包括计算列的值:

CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

在表中插入一些测试数据:

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;

在视图上创建一个统计项和一个索引:

CREATE STATISTICS ST_PersistedViewTest_View
ON dbo.PersistedViewTest_View(TestComputedColumn)
WITH FULLSCAN;

CREATE INDEX IX_PersistedViewTest_View_TestComputedColumn
ON dbo.PersistedViewTest_View(TestComputedColumn);

如果查询优化器确定这样做对有意义SELECT的表,对具有持久化列的表执行语句现在可以自动使用持久化视图:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

上面查询的实际执行计划显示查询优化器选择使用持久化视图返回结果:

在此处输入图片说明

您可能已经注意到WHERE上面子句中的显式转换。此显式CONVERT(INT, 26)允许查询优化器正确使用统计信息对象来估计查询将返回的行数。如果使用编写查询WHERE pv.TestComputedColumn = 26,则查询优化器可能无法正确估计行数,因为实际上将26视为a TINY INT;这可能会导致SQL Server不使用持久化视图。隐式转换可能会非常痛苦,并且必须始终使用正确的数据类型进行比较和联接,这是值得的。

当然,使用模式绑定所产生的所有标准“陷阱”都适用于上述情况。这可能会阻止在所有情况下使用此替代方法。例如,如果不先从视图中删除模式绑定,就无法再修改基表。为此,您需要从视图中删除聚簇索引。

如果您没有SQL Server Enterprise Edition,则查询优化器将不会自动将持久化视图用于未使用WITH (NOEXPAND)提示直接引用该视图的查询。为了实现在非企业版版本中使用持久化视图的好处,您需要将上面的查询重新编写为:

SELECT pv.PersistedViewTest_ID
    , pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)

感谢Ian Ringrose指出了上述企业版限制,并感谢Paul White给出了(NOEXPAND)提示。

Paul的答案提供了一些有关持久化视图的查询优化器的有趣细节。


解决方法显示在视图上同时创建了聚集索引和非聚集索引。是否出于某种原因必须在聚簇索引上使用非聚簇索引?或者,非聚集索引是否更有效?如果在查询中使用了聚集索引,统计信息将显示什么?
鲍勃·布赖恩

有趣的问题@BobBryan-需要聚簇索引以使视图得以持久,尽管实际上并不需要是唯一索引。我本可以在其他列上创建视图的聚簇索引,例如the TestComputedColumn。但是,由于聚簇索引包含表/视图的所有数据,因此我决定使用单调递增的数字作为聚簇键可能会更好。注意,我实际上没有测试过这种假设,实际上对于repro的某些变化它可能是不正确的。
Max Vernon

请注意,非聚集索引不是覆盖索引,因此,从视图或基础表过滤,联接或返回列的任何查询都需要对基表或基表执行键查找操作风景。对于实际情况,我的答案范围有限,可能会考虑到更好的性能。
Max Vernon

4

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

哪里

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

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

资料来源:MSDN


3
  • 您需要一个未计算的列来放置过滤后的索引。
  • 您需要计算该列中的值。

在计算列之前,每当更改或插入行时,我们都会使用触发器来计算列值。

(触发器也可以用于从第二张表中插入/删除项目的PK,然后在查询中使用它。)


3

这是尝试改善Max Vernon的解决方法。在他的解决方案中,他建议在视图和统计对象上使用2个索引。

第一个索引是聚簇的,这实际上是必需的,因为与表上的非聚簇索引不同,如果尝试在不首先具有聚簇索引的情况下尝试在视图上创建非聚簇索引,则会产生错误。

第二索引是非聚集索引,用作查询后面的索引。在他的回答的评论部分,我问如果使用聚集索引而不是非聚集索引会发生什么。

下面的分析试图回答这个问题。

我正在使用与他完全相同的代码,除了没有在视图上创建非聚集索引。

我也没有创建统计对象。如果您遵循此方法并使用SQL Server Management Studio(SSMS)输入以下代码,则应注意,您可能会看到一些红色的波浪线-看起来像错误。这些(可能)不是错误,但是涉及智能感知问题。

您可以禁用智能感知,也可以忽略错误并运行命令。它们应该正确完成。

-- Create the test table that uses a computed column.
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

-- Insert some test data into the table.
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;
GO

针对表运行以下查询后,将产生以下执行计划(无视图/索引视图):

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

在此处输入图片说明

这提供了一个比较基准。请注意,查询完成后,将创建一个统计对象(_WA_Sys_00000003_1FCDBCEB)。创建聚簇表索引时,将创建PK_PersistedViewTest统计对象。

接下来,创建过滤视图和该视图上的聚簇索引:

-- Create filtered view on the computed column.
CREATE VIEW dbo.PersistedViewTest_View
WITH SCHEMABINDING
AS
SELECT PersistedViewTest_ID, SomeData, TestComputedColumn
FROM dbo.PersistedViewTest
WHERE TestComputedColumn < CONVERT(INT, 27);
GO

-- Create unique clustered index to persist the values, including the computed column.
CREATE UNIQUE CLUSTERED INDEX IX_PersistedViewTest
ON dbo.PersistedViewTest_View(PersistedViewTest_ID);
GO

现在,让我们尝试再次运行查询,但是这次针对视图:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

现在,新的执行计划为:

在此处输入图片说明

如果要相信新计划,则在该视图上添加视图和聚簇索引之后,统计信息似乎表明执行查询所需的时间现在已加倍。此外,请注意,运行查询后,没有创建新的统计对象来支持新索引,这与表上的查询不同。

查询计划仍然建议创建非聚集索引将对改善查询性能非常有帮助。那么,这是否意味着必须先在视图中添加非聚集索引,然后才能获得所需的性能改进?还有最后一件事可以尝试。修改查询以使用“ WITH NOEXPAND”选项:

SELECT pv.PersistedViewTest_ID, pv.TestComputedColumn
FROM dbo.PersistedViewTest_View pv WITH (NOEXPAND)
WHERE pv.TestComputedColumn = CONVERT(INT, 26)
GO

这将导致以下查询计划:

在此处输入图片说明

该执行计划看起来与Max Vernon答案中给出的非聚集索引生成的执行计划非常相似。但是,这是通过减少一个(非聚集)索引和减少一个统计对象来完成的。

事实证明,必须将NOEXPAND选项与SQL Server的快速版本和标准版本一起使用,才能正确使用索引视图。Paul White的精彩文章阐述了使用NOEXPAND选项的好处。他还建议将此选项与企业版一起使用,以确保优化程序使用视图索引提供的唯一性保证。

上面的分析是使用SQL Sever 2014的快速版进行的。我也使用SQL Server 2016的开发人员版进行了尝试。开发版似乎不需要NOEXPAND选项即可获得性能提升,但仍建议使用。

不到5个月前,微软免费提供开发人员版本。该许可证仅将其用于开发,这意味着该数据库不能在生产环境中使用。因此,如果您一直在尝试测试内存优化表,加密,R等,那么您将不再拥有无许可证的借口。几天前,我已成功将其与SQL Server 2014 Express一起安装在计算机上,没有任何问题。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.