SQL Server 2008执行计划问题

时间:2010-07-08 10:31:28

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

我向sql guru发了一个问题。

有两个表几乎相同的结构。

根据传入存储过程的参数,我需要从一个或另一个表中收集数据。

如何以最佳方式做到这一点?

请不要建议将这些表合并为一个 - 这是不合适的。

我做了以下(MS SQL Server 2008):

Select *
FROM
    String s
    JOIN (
        SELECT id
            ,TypeCode
            ,ProdNo         
                FROM Table1
        WHERE @param = 1 AND TypeCode= 'INV'

        UNION

        SELECT id
            ,TypeCode
            ,ProdNo
        FROM Table2
        WHERE @param = 2 AND TypeCode= 'INV'

    ) m ON m.Id = s.Id
WHERE s.Id = 256

但是当我查看执行计划时,我感到很惊讶,因为它从并行线程中的两个表获取数据,并且仅在经过@param值过滤之后。

我认为过滤将在第一阶段进行,数据从单表收集。

有没有办法只从一个表中进行选择而不将查询分成两个查询并使用IF运算符?

由于

5 个答案:

答案 0 :(得分:4)

你能用一个简单的IF语句吗?

IF @Param = 1 
  BEGIN
    EXEC SQL
  END
ELSE IF @Param = 2
  BEGIN
    EXEC SQL
  END 
ELSE
  RAISERROR('Invalid Parameter', 16, 1)

或者您可以动态构建查询并使用sp_executesql存储过程执行它。

DECLARE @Sql NVARCHAR(100)

SET @Sql = N'SELECT * FROM ('

IF @Param = 1
  SET @Sql = @Sql + N'SELECT 1 a, 2 b, 3 c'
ELSE IF @param = 2
  SET @Sql = @Sql + N'SELECT 4 a, 5 b, 6 c'
ELSE
  RAISERROR('Invalid Parameter', 16, 1)

SET @Sql = @Sql + ') tbl'
EXEC sp_executesql @sql

答案 1 :(得分:4)

你真的需要阅读这个Dynamic Search Conditions in T-SQL by Erland Sommarskog。你不应该担心重复代码,这不是一些家庭作业。只是担心让执行计划使用索引。当使SQL代码“漂亮”时,唯一要考虑的是缩进和放大例如,任何其他更改都可能导致查询计划变慢。我在超慢查询中看到了对超快查询结果的微不足道的更改。 GO FOR SPEED(索引使用)和必要的重复代码。另见:The Curse and Blessings of Dynamic SQL

您标记了问题sql-server-2008,因此如果您运行SQL 2008 SP1 CU5(10.0.2746)和SQL 2008 R2 CU1(10.50.1702)及更高版本,则会出现新行为(如OPTION(RECOMPILE)上的“动态搜索条件”文章,并未出现在所有版本的SQL 2008或2005中。此行为基本上在运行时评估@Local_Variables值,并相应地重新编译查询。在你的情况下,这应该导致你的UNION的一半在编译时被淘汰。

答案 2 :(得分:1)

我建议的第一件事就是将ID过滤器放在联合中。 我还将UNION更改为UNION ALL。这样可以避免评估DISTINCT行

Select * 
FROM 
    String s 
    JOIN ( 
        SELECT id 
            ,TypeCode 
            ,ProdNo          
                FROM Table1 
        WHERE @param = 1 AND TypeCode= 'INV' AND id = 256

        UNION ALL

        SELECT id 
            ,TypeCode 
            ,ProdNo 
        FROM Table2 
        WHERE @param = 2 AND TypeCode= 'INV' AND id = 256

    ) m ON m.Id = s.Id 
WHERE s.Id = 256 

答案 3 :(得分:0)

SQL Server并不那么聪明 - 在编写查询时,您应该只确保发送最少量的SQL来获取所需的数据(不发送多余的语句),还要提供最多的信息(通过过滤器)可以为查询优化器提供尽可能多的关于数据的提示。如您所见,它将执行您发送的所有SQL。

所以听起来你需要从我正在阅读的东西中使用动态SQL。这也使您能够合并SQL的常见部分,减少重复数量。例如,你可以拥有(只需要你的内部代码 - 你可以将其余的东西包裹起来):

DECLARE @sql NVARCHAR(1000)

SET @sql = 'SELECT id, TypeCode, ProdCode'
IF @param = 1
   SET @sql = @sql + ' FROM table1'
IF @param = 2
   SET @sql = @sql + ' FROM table2'
SET @sql = @sql + ' WHERE TypeCode = ''INV'''

EXECUTE sp_ExecuteSQL @sql

请注意,如果你要把它变成一个更复杂的东西,关于小Bobby Tables:可能滥用sp_ExecuteSQL并打开大洞,但使用正确 - 使用参数化动态SQL - 它就像存储一样好过程

答案 4 :(得分:0)

如果你可以创建一个tDUMMY表(一个虚拟行),请给它一个镜头。

Select 
    * 
FROM 
    String s 
    JOIN ( 
        SELECT 
             id, TypeCode, ProdNo
        FROM 
             tDUMMY INNER JOIN Table1 ON TypeCode= 'INV'
        WHERE
             @param = 1

 UNION ALL

        SELECT 
             id, TypeCode, ProdNo
        FROM 
             tDUMMY INNER JOIN Table2 ON TypeCode= 'INV'
        WHERE
             @param = 2
     ) m ON m.Id = s.Id 
WHERE s.Id = 256

理论上查询优化器应首先过滤tDUMMY表,然后尝试连接。因此,如果@param = 1,第二个查询应该快得多(它将再次检查1行tDUMMY,但它不应该检查table2)

注意 - 我也把它变成了UNION ALL(但不会产生太大的影响),因为无论如何,一方总是不会返回任何行。