表函数中的条件UNION ALL

时间:2017-08-15 18:54:31

标签: sql sql-server tsql sql-server-2016 sql-execution-plan

所以用例如下 - 有一些参数,根据这些参数,我想从一个或另一个表中选择数据。

create table dbo.TEST1 (id int primary key, name nvarchar(128))
create table dbo.TEST2 (id int primary key, name nvarchar(128))

所以我创造了这样的功能:

create function [dbo].[f_TEST]
(
    @test bit
)
returns table
as
return (
    select id, name from TEST1 where @test = 1

    union all

    select id, name from TEST2 where @test = 0
)

当我使用常量运行它时,执行计划很棒 - 只扫描一个表

select * from dbo.f_TEST(1)

enter image description here

但是,当我使用变量时,计划不是那么好 - 两个表都被扫描

declare @test bit = 1

select * from dbo.f_TEST(@test)

enter image description here

那么是否有任何提示(或技巧)迫使SQL Server理解在某个查询中只应扫描一个表?

5 个答案:

答案 0 :(得分:13)

如果您的功能是内联TVP(如示例所示),那么您可以使用:

declare @test bit = 1
select * from dbo.f_TEST(@test) OPTION (RECOMPILE);

然后在这两种情况下,您将获得单个聚簇索引扫描。

<强> DBFiddle Demo

来自Option RECOMPILE

  

编译查询计划时,RECOMPILE查询提示使用查询中任何局部变量的当前值,如果查询位于存储过程中,则将当前值传递给任何参数。

答案 1 :(得分:4)

它的工作原理很好。当您更改参数值时,请查看相关表格上的“执行次数”。将被排除的表格出现在计划中,因为必须考虑这些表格,但这并不意味着它们将被扫描。

Number of Executions: 0

另外,请查看过滤器上的启动表达式:

enter image description here

答案 2 :(得分:2)

您可以尝试

select top (@test*100) percent id, name from TEST1 

union all

select top ((1-@test)*100) percent id, name from TEST2

答案 3 :(得分:1)

使用存储过程而不是表函数,但要注意参数嗅探。您可以在存储过程中使用动态SQL来生成使用表函数查找的相同结果。

本文将解释为什么你所做的工作方式。 https://docs.microsoft.com/en-us/sql/relational-databases/user-defined-functions/user-defined-functions

我确定您希望使用该功能执行更多操作,这就是您可能不想创建存储过程的原因。有一种方法可以在查询中使用sporc执行的结果。但是,这与您在此处记录的内容不同。

答案 4 :(得分:-2)

当您的功能用于表格时,OPTION(RECOMPILE)不会帮助您。例如,这将查询将扫描两个表

-- 3rd table to test against
create table dbo.TEST3 (id int primary key, test bit);
insert dbo.TEST3 values(1,1),(2,1),(3,0),(4,1);
GO

select TEST3.* 
from TEST3 
CROSS APPLY dbo.f_TEST(test3.test) 
OPTION (RECOMPILE);

但是没关系。我的时间很短(否则我会包含一个屏幕截图),但是如果您使用实际执行计划运行这三个查询,您会看到优化程序将这些视图视为具有相同的费用:

DECLARE @test int = 1

select * from dbo.f_TEST(1)
select * from dbo.f_TEST(@test)
select * from dbo.f_TEST(@test) OPTION (RECOMPILE)

第二个查询将显示它的价格是第一个和最后一个的两倍但是,当您将鼠标悬停在SELECT运算符上时,您会看到它,因为优化程序估计两个行而不是1(与其他两个一样)。

如果你进行了一些性能测试,你会看到在这种情况下,优化器可能是正确的。

您的代码更大的问题是每个表都保证表扫描,因为您在任一查询中都没有过滤器。如果可能,添加过滤器将使您能够以搜索发生而不是扫描的方式为这两个表编制索引。