视图上的过滤器应用于视图中排除的过滤器

Posted

技术标签:

【中文标题】视图上的过滤器应用于视图中排除的过滤器【英文标题】:Filter on a view is applied to filters excluded in a view 【发布时间】:2021-05-13 18:47:56 【问题描述】:

我有一个可以在列中包含 JSON 数据的表。我使用 ISJSON() 函数向表中添加了一个计算列,以标记任何不包含有效 JSON 数据的行

CREATE TABLE tbl1 (Id INT IDENTITY(1,1) NOT NULL, Content NVARCHAR(MAX), IsJsonRecord AS ISJSON(Content))
GO
INSERT INTO tbl1 (Content) VALUES ('a'), ('"name":"asd"')
GO

现在我有一个视图,可以将 JSON 数据解析为更易读的格式,例如

CREATE VIEW vw1
AS
SELECT Id,
    JSON_VALUE(Content, '$."name"') AS Name
FROM tbl1
WHERE IsJsonRecord > 0

当我从视图中选择时,WHERE 子句按预期工作。

SELECT *
FROM vw1

当我使用额外的 where 子句查询视图时,由于 JSON 数据格式不正确(如下所示)而出现错误。

SELECT *
FROM vw1
WHERE [Name] LIKE '%a%'

查询 WHERE 子句似乎应用于不符合视图中已指定的 WHERE 子句的行。

这是预期的行为吗?

我知道该视图已“优化掉”,但我希望查询优化器先将过滤器应用到不同的字段,然后再应用需要对数据进行操作的函数的过滤器。我认为逻辑在某些情况下可能会带来性能优势。

我不太确定如何在视图中容纳 WHERE 子句。我的实际情况比示例复杂得多,我不确定是否可以使用CASE 语句而不是JSON_VALUE 语句来测试视图中的每一列。

有什么建议吗?

【问题讨论】:

看起来很奇怪。如果您将 json_value(...) 替换为 cross apply openjson(...) with (...) 构造,错误就会消失,但我一开始就没想到会出现错误。 【参考方案1】:

就目前而言,不,您不能告诉编译器按什么顺序进行过滤,尽管您可以采取一些技巧,通常会迫使它这样做。

问题是您的查询是这样有效编译的:

SELECT *
FROM (
    SELECT Id,
        JSON_VALUE(Content, '$."name"') AS Name
    FROM tbl1
    WHERE IsJsonRecord > 0
) vw1
WHERE [Name] LIKE '%a%'

然后将其优化为:

SELECT Id,
    JSON_VALUE(Content, '$."name"') AS Name
FROM tbl1
WHERE IsJsonRecord > 0 AND [Name] LIKE '%a%'

此时,编译器将决定首先评估哪个部分。不管什么原因,它都选择先做LIKE,这可能是因为索引很好。

您有多种解决方案:

使用CASE,保证(某些情况除外)按顺序评估
SELECT Id,
    JSON_VALUE(Content, '$."name"') AS Name
FROM tbl1
WHERE CASE WHEN IsJsonRecord > 0 THEN CASE WHEN [Name] LIKE '%a%' THEN 1 END END = 1

在持久列上添加索引,以该列作为前导键,或过滤索引IsJsonRecord > 0。 这不能保证总是有效,但通常可以。确保包括所有必要的列。

上面的一个变体是在视图中添加一个聚集索引。这样做有很多限制,但它可以很好地工作。确保在查询中添加WITH (NOEXPAND) 提示。

更有保证的选择是在视图中添加TOP。 这迫使编译器确保IsJsonRecord > 0 在逻辑上首先被评估,这几乎总是意味着它将首先在物理上执行。

SELECT *
FROM (
    SELECT TOP (9223372036854775807) *
    FROM vw1
) vw1
WHERE [Name] LIKE '%a%'

【讨论】:

感谢您的出色回应,我将不得不进行实验。我发现的另一种方法如下。问题是我的观点被用于其他事情,所以我需要确保这一切都不会破坏这些情况。 ALTER VIEW [dbo].[vw1] AS SELECT Id, CASE WHEN IsJsonRecord = 0 THEN NULL ELSE JSON_VALUE(Content, '$."name"') END AS Name FROM tbl1 WHERE IsJsonRecord > 0 GO

以上是关于视图上的过滤器应用于视图中排除的过滤器的主要内容,如果未能解决你的问题,请参考以下文章

bigQuery 和 GA-Premium 集成:从 GA 中的未过滤视图导出数据时,如何在 bigQuery 中使用 IP 过滤器(以排除内部流量)

使用自定义过滤器将事件从主视图发送到视图

如何使 pixi js 掩码过滤器响应移动视图上的触摸,就像它在桌面视图中移动指针一样?

如何在 Drupal 视图中设置适用于两种不同内容类型的过滤器?

Sharepoint 2010列表视图阈值和项级别权限

从特定操作中排除过滤器