Sql 条件UNION ALL in表函数

Sql 条件UNION ALL in表函数,sql,sql-server,tsql,sql-server-2016,sql-execution-plan,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

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

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)

但是,当我使用变量时,计划并不是很好——两个表都会被扫描

declare @test bit = 1

select * from dbo.f_TEST(@test)


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

如果您的函数是内联TVP(如示例所示),则可以使用:

declare @test bit = 1
select * from dbo.f_TEST(@test) OPTION (RECOMPILE);
然后,在这两种情况下,您将获得单个聚集索引扫描

发件人:

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

你不妨试一试

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

union all

select top ((1-@test)*100) percent id, name from TEST2
当您的函数用于表时,选项(重新编译)在这里对您没有帮助。例如,该查询将扫描两个表

-- 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行(其他两行的情况也是如此)

如果您进行一些性能测试,您将看到,在本例中,优化器可能是正确的


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

它可以正常工作。在更改参数值时,请查看相关表中的“执行次数”。将被排除的表出现在计划中,因为它们必须被考虑,但这并不意味着它们将被扫描

另外,请查看筛选器上的启动表达式:


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

这篇文章将解释为什么你所做的工作是这样的。


我确信您希望对该函数做更多的工作,这就是为什么您可能不想创建存储过程的原因。有一种方法可以在查询中使用sporc执行的结果。但是,这与您在此处记录的问题不同。

表假脱机:(@ LAD2025:是的,我明白了。你的解决方案看起来更好。我测试了这一点,它的性能与第一个解决方案相媲美。无论哪种方式,这都是一个非常聪明的方法。聪明,确实。不错。我通常真的反对这个选项,但在这里可以考虑。答案是好的,但被栓的部分有点不对。)这里的问题不是因为值是作为参数传递给函数的,问题是使用变量本身,因此该句的第一部分更相关。
“查询中任何局部变量的当前值”
我唯一想要的是一些我可以添加到函数本身的提示,这样我就不会在每个查询中都添加重新编译提示…@RomanPekar,单语句TVF是内联的,所以你需要在主查询中添加
重新编译
提示。你可以将其设置为多语句TVF并在其中包含
重新编译
提示,但是这可能会使整体情况变得更糟,因为它不会被内联。没有理由为此而强制重新编译。请参阅下面的答案。接受的答案有什么问题,所以您决定打开“赏金”?您希望在答案中看到什么样的额外细节?只是想知道是否有一些更好的技术可以在s中使用据我所知,qlserver2016a,
选项(重新编译)
是唯一一个实质上用参数的实际值替换参数的提示,这使优化器能够确定它不必接触某个表。在所有其他情况下,当涉及参数时,优化器必须生成一个对任何参数组合都有效的计划。这样的计划将被缓存和重用将来使用不同的参数值,这意味着优化器无法从计划中删除任何表。编写动态Sql是唯一更好的方法,可以删除UDF。这是一个很好的答案,但问题是计划本身仍然是一样的,在我的情况下,这不太好(例如,对于不同的参数值,行数可能会非常不同)