打开查询以在视图中运行

时间:2011-01-19 23:38:23

标签: sql-server

我有一个巨大的视图,许多查询使用UNION ALL连接在一起,每个查询的第一列都是常量。

e.g。

CREATE VIEW M AS (
    SELECT 'A' ID, Value FROM A
    UNION ALL
    SELECT 'B' ID, Value FROM B
    ...
)

现实中的查询更复杂,但这里的目的只是为了切换要运行的查询:

SELECT * FROM M WHERE ID = 'A' 

执行计划显示ID上不匹配的查询永远不会运行。

我认为这是一个非常好的(功能?),我可以用它来通过相同的视图组合查询不同但相似的东西。

然而,如果查询这样的CTE,我最终会得到一个更好的执行计划:

WITH M AS (
    SELECT 'A' ID, Value FROM A
    UNION ALL
    SELECT 'B' ID, Value FROM B
    ...
)
SELECT * FROM M WHERE ID = 'A' 

以下是实际查询的部分示例:

SELECT CONVERT(char(4), 'T   ') EntityTypeID, SystemID, TaskId EntityID 
FROM [dbo].[Task]

UNION ALL

SELECT CONVERT(char(4), 'T   ') EntityTypeID, s.SystemID, [dbo].[Task].TaskId EntityID
FROM [dbo].[Task] 
INNER JOIN [dbo].[System] s ON s.MasterSystemID = [dbo].[Task].SystemID
INNER JOIN SystemEntitySettings ON SystemEntitySettings.SystemID = s.SystemID
    AND SystemEntitySettings.EntityTypeID = 'T   '
    AND SystemEntitySettings.IsSystemPrivate = 0

如果我运行上面的T-SQL WHERE EntityTypeiD <> 'T',它会完全忽略第一个查询,但会对第二个查询执行某些操作(永远不会返回任何实际行)。

我遇到的问题,或者更确切地说,我的问题是,为什么它不能完全从视图中消除查询,当它在CTE案例中这样做时?

修改

到目前为止,我已经观察到了一些有趣的事情,我不排除参数化的处理,但是我也可以通过指定查询提示(显然任何会做)或者将第二个连接重写为a来实现desiered效果。 IN谓词,因为它只是一个过滤器。

INNER JOIN SystemEntitySettings ON SystemEntitySettings.SystemID = s.SystemID
    AND SystemEntitySettings.EntityTypeID = 'T   '
    AND SystemEntitySettings.IsSystemPrivate = 0

... ...变为

 WHERE s.SystemID IN (
      SELECT SystemID 
      FROM dbo.SystemEntitySettings 
      WHERE EntityTypeID = 'T   ' AND IsSystemPrivate = 0
 )

但是,以下查询具有相同的问题。它看起来好像与JOIN操作有关。 (注意在此查询中发生[Group]的附加JOIN)

SELECT CONVERT(char(4), 'CF  ') EntityTypeID, s.SystemID, [dbo].[CareerForum].GroupID EntityID
FROM [dbo].[CareerForum] 
INNER JOIN [dbo].[Group] ON [dbo].[Group].GroupID = [dbo].[CareerForum].GroupID
INNER JOIN [dbo].[System] s ON s.MasterSystemID = [dbo].[Group].SystemID
WHERE s.SystemID IN (SELECT SystemID FROM dbo.SystemEntitySettings WHERE EntityTypeID = 'CF  ' AND IsSystemPrivate = 0)

重现的

以下脚本可用于重现该问题。请注意,如果使用查询提示运行查询,或者使用cte(desiered result)运行视图,执行计划如何完全不同。

CREATE DATABASE test_jan_20

USE test_jan_20

create table source (
    x int not null primary key,
)

insert into source values (1)
insert into source values (2)
insert into source values (3)
insert into source values (4)
insert into source values (5)
insert into source values (6)

create table other (
    y int not null primary key,
)

insert into other values (1)
insert into other values (2)
insert into other values (3)
insert into other values (4)
insert into other values (5)
insert into other values (6)

create view dummy AS (

    SELECT 'A' id, x, NULL y 
    FROM SOURCE 
    WHERE x BETWEEN 1 AND 2

    UNION ALL

    SELECT 'B' id, x, NULL y
    FROM SOURCE 
    WHERE x BETWEEN 3 AND 4

    UNION ALL

    SELECT 'B' id, source.x, y
    FROM SOURCE 
    INNER JOIN other ON y = source.x
    INNER JOIN source s2 ON s2.x = y - 1 --i need this join for the issue to occur in the execution plan
    WHERE source.x BETWEEN 5 AND 6

)
GO

--this one fails to remove the JOIN, not OK
SELECT * FROM dummy WHERE id = 'c' 

--this is OK
SELECT * FROM dummy WHERE id = 'c' OPTION (HASH JOIN) --NOTE: any query hint seems to do the trick

--this is OK
;
WITH a AS (

    SELECT 'A' id, x, NULL y 
    FROM SOURCE 
    WHERE x BETWEEN 1 AND 2

    UNION ALL

    SELECT 'B' id, x, NULL y
    FROM SOURCE 
    WHERE x BETWEEN 3 AND 4

    UNION ALL

    SELECT 'B' id, source.x, y
    FROM SOURCE 
    INNER JOIN other ON y = source.x
    INNER JOIN source s2 ON s2.x = y - 1 --i need this join for the issue to occur in the execution plan
    WHERE source.x BETWEEN 5 AND 6

)
SELECT * FROM a WHERE id = 'c' 

3 个答案:

答案 0 :(得分:2)

在您的测试案例中,这就是正在发生的事情。

对于具有视图和查询提示或CTE的查询,查询优化器正在使用“矛盾检测”。您可以在执行计划属性中看到OPTIMIZATION LEVELTRIVIAL。制作的琐碎计划与this article第8点所示的计划完全相同。

对于没有查询提示的视图的查询,这将自动参数化。这可以防止矛盾检测在as covered here中被踢。

答案 1 :(得分:1)

  

执行计划显示了   从不与ID匹配的查询   运行

这是正确的,因为你提供了一个常量'A',所以计划是针对特定的字符串'A'构建的,它会切断一部分。

  

我遇到的问题,或者更确切地说,我的问题   问题是,为什么它不能   彻底消除查询   在CTE的情况下,当它这样做的时候?

我以为你刚才说它确实如此?我想你是以参数化的方式使用它,无论是在SP,函数还是参数化查询中。这导致创建一个必须能够采用各种参数的计划 - 因此切掉一部分是不可能的。

要实现您的目标,您必须生成动态SQL,以便向查询优化器提供constant值的查询。无论您使用View还是内联表值函数,都是如此。

编辑:添加可重复的

这两种形式似乎也有效

select * from (SELECT * FROM dummy) y WHERE id = 'c' 

with a as (Select * from dummy) SELECT * FROM a WHERE id = 'c' 

答案 2 :(得分:1)

上次更新时,查询也会进行优化。

如果您提供c(或任何烦恼缺失值)作为过滤器,您将拥有此计划:

  |--Compute Scalar(DEFINE:([Union1019]=[Expr1018], [Union1020]=[ee].[dbo].[source].[x], [Union1021]=[ee].[dbo].[other].[y]))
       |--Compute Scalar(DEFINE:([Expr1018]='B'))
            |--Nested Loops(Inner Join, OUTER REFERENCES:([Expr1022]))
                 |--Constant Scan
                 |--Clustered Index Seek(OBJECT:([ee].[dbo].[source].[PK__source__3BD019E5171A1207] AS [s2]), SEEK:([s2].[x]=[Expr1022]) ORDERED FORWARD)

,恒定扫描扩展如下:

<RelOp AvgRowSize="19" EstimateCPU="1.57E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimateRows="0" LogicalOp="Constant Scan" NodeId="3" Parallel="false" PhysicalOp="Constant Scan" EstimatedTotalSubtreeCost="1.57E-07">
  <OutputList>
    <ColumnReference Database="[ee]" Schema="[dbo]" Table="[source]" Column="x" />
    <ColumnReference Database="[ee]" Schema="[dbo]" Table="[other]" Column="y" />
    <ColumnReference Column="Expr1022" />
  </OutputList>
  <ConstantScan />
</RelOp>

在其他世界中,永远不会触及source sother o,这不会产生任何实际输出,因此Nested Loops没有输入,因此没有实际的搜索执行。

如果用b替换参数,您将看到一个更复杂的计划,对所有三个表进行实际JOIN操作。