SQL - 优先考虑 WHERE 条件

Posted

技术标签:

【中文标题】SQL - 优先考虑 WHERE 条件【英文标题】:SQL - Prioritizing WHERE conditions 【发布时间】:2016-12-21 08:33:50 【问题描述】:

我有一种情况,一个看起来相当简单的 select 语句需要几分钟才能完成;这个语句看起来像:

SELECT *
  FROM MyView
 WHERE MyFunction(Col_1, Col_2, Col_3, Col_4) = 1
   AND Col_8 = 20
;

现在,时间分布(大致)为 select 的 1/3 和 MyFunction 调用的 2/3(通过简单地注释函数调用并与完整的 select 时间进行比较来衡量)。

现在,仅第二个条件(即Col_8 = 20)已经减少了记录数。

我尝试运行相同的查询两次,首先调用函数,然后调用它,两者都返回相同的值(当然)但也花费了相同的时间。

我想知道如果第一个条件已经失败,如何阻止函数的调用,并想到了两个替代方案:

    将第一个条件设置为Col_8 = 20,将第二个条件设置为第一个条件的CASE(即如果第一个条件失败返回FALSE,否则调用该函数),

    将查询构建为选择中的选择。

最好的(出于可能的原因!)将是在某些编程语言中(Ada 是第一个出现的),您可以在其中编写如下内容:

<condition 1> AND THEN <condition 2>...

【问题讨论】:

如果你在col_8上添加一个索引,执行时间应该减少到函数需要的时间 看起来您正在使用标量值函数。您是否知道 SVF 是魔鬼,并且在几乎所有情况下使用内联表值函数 (TVF) 都是一个更好的主意? 谢谢@juergend。至于名字,当然是为了问题。真正的数据库有更有意义的名称。作为 fod 索引,假设 view 是给定的并且不能更改。 CASE 方法是(大部分)保证的唯一方法。 “将查询构建为选择中的选择。”不能保证,因为 SQL Server 可以推送谓词。 向我们展示视图定义及其表和索引。 【参考方案1】:

标量函数是问题!

这些操作强制优化器执行RBAR 操作,即表扫描。

更多信息:http://www.sqlservercentral.com/articles/T-SQL/135321/

因此,要修复,您需要对标量函数进行 bin 处理!你有几个选择...

    内联移动函数逻辑 将标量函数重写为表值函数

为了说明这一点,我将使用以下代码作为标量函数的 [非常] 基本近似来运行这些选项:

CREATE FUNCTION MyFunction (
   @a int
 , @b int
 , @c int
 , @d int
)
  RETURNS bit
AS
  BEGIN
    DECLARE @return_value bit = 0;
    IF @a + @b + @c + @c > 5
      BEGIN
        SET @return_value = 1;
      END
    ;

    RETURN @return_value
  END
;

移动逻辑内联:

SELECT *
FROM   MyView
WHERE  Col_8 = 20
AND    Col_1 + Col_2 + Col_3 + Col_4 > 5
;

制作一个 TVF:

CREATE FUNCTION MyNewFunction (
   @a int
 , @b int
 , @c int
 , @d int
)
  RETURNS TABLE
AS
  RETURN
SELECT Cast(CASE WHEN @a + @b + @c + @c > 5 THEN 1 ELSE 0 END AS bit) AS return_value
;

然后调用它

SELECT *
FROM   MyView
 CROSS
 APPLY dbo.MyNewFunction(Col_1, Col_2, Col_3, Col_4) AS x
WHERE  MyView.Col_8 = 20
AND    x.return_value = 1
;

【讨论】:

这是我提到的一个select中的select,也提到这个函数消耗了2/3的时间。 这不会强制执行顺序。见connect.microsoft.com/SQLServer/feedback/details/537419/… 一个巨大的NO。子查询不保证执行顺序。 哦-我不知道子查询-谢谢你们-每天都是上学日! @FDavidov 答案已更新以提供替代方法,这应该可以缓解您正在使用的标量 UDF 的性能问题。【参考方案2】:

WHERE 语句中的函数很糟糕,因为它们必须为每一行执行。此外,在这种情况下无法进行索引。如果your function is deterministic 及其使用的列来自一个表,您可以在视图的基表中使用它来创建持久计算列。该列可以在您的WHERE 语句中使用,也可以被索引以获得更好的性能:

ALTER TABLE MyBaseTable
ADD ComputedCol AS MyFunction(Col_1, Col_2, Col_3, Col_4) PERSISTED

然后您可以使用计算列进行选择:

SELECT *
FROM MyView
WHERE ComputedCol = 1 AND Col_8 = 20

【讨论】:

感谢您的回答。我的问题不在于改变现有实现中的内容,而是在于将执行顺序设置为 WHERE 子句中的条件的优先级/设置执行顺序的能力。【参考方案3】:
SELECT  *
FROM    MyView
WHERE   case 
            when Col_8 = 20 
            then case 
                    when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
                    then 1 
                 end 
        end = 1
;

如果你在 Col_8 上有索引

SELECT      *

FROM        MyView

WHERE       Col_8 = 20 

        and case 
                when Col_8 = 20 
                then case 
                        when MyFunction(Col_1, Col_2, Col_3, Col_4) = 1 
                        then 1 
                     end 
            end = 1
;

【讨论】:

嘿嘟嘟。是的,这是我在问题中提出的选项之一,但希望有更多ELLEGANT。谢谢。【参考方案4】:

实际上,通过使用子选择,您并不能保证任何事情,执行顺序是未知的。

我有一个不同的建议,不确定它是否会提高性能。使用派生表:

SELECT *
INTO TMP_FOR_SELECT
FROM MyView
WHERE Col_8 = 20;

SELECT * FROM TMP_FOR_SELECT 
WHERE  MyFunction(Col_1, Col_2, Col_3, Col_4) = 1;

除此之外,我只能在Col_8 上建议一个索引,这将有助于优化器更快地找到结果。

【讨论】:

这不能保证比派生表或 CTE 更多的东西(即根本不) +1,这是对过滤器强制排序的唯一可靠方法。但是,您可能不小心使用了 ORACLE 语法而不是 SQL Server。 SQL Server 是SELECT ... INTO TMP_FOR_SELECT FROM ...。另外,我会使用一个真正的临时表,即#tmp_for_select @MartinSmith 实际上确实保证只有带有Col_8 = 20 的记录将被发送到函数。除非我错过了什么? @Heinzi,显然,这不是唯一的方法 @DuduMarkovitz:CASE WHEN 不会总是短路,请参阅this answer 上的评论。

以上是关于SQL - 优先考虑 WHERE 条件的主要内容,如果未能解决你的问题,请参考以下文章

Where 子句基于优先级

mysql进阶5:分组查询

PreparedStatement 可以不考虑 WHERE 子句中的某些条件吗?

sql中where的与jnner join on的连接条件.哪个优先级别高?

Anorm:WHERE 条件,有条件

SQL中where 1=1和0=1的作用