从表中选择少数或所有记录的有效方法

时间:2013-10-28 16:30:35

标签: sql sql-server

我有一个查询优化问题。假设有一张包含所有发票的表格。使用TVP(表值参数)我想通过提供1..n id来选择几条记录,或者通过提供值为-1的单个id来返回所有记录。

    DECLARE @InvoiceIdSet AS dbo.TBIGINT;
    INSERT INTO @InvoiceIdSet VALUES (1),(2),(3),(4)
    --INSERT INTO @InvoiceIdSet VALUES (-1)

    SELECT TOP 100
            I.Id ,
            Number ,
            DueDate ,
            IssuedDate ,
            Amount ,
            Test3
    FROM    dbo.Invoices I
    --WHERE   EXISTS ( SELECT NULL
    --                 FROM   @InvoiceIdSet
    --                 WHERE  I.Id = ID
    --                        OR ID = -1 )
    --CROSS APPLY @InvoiceIdSet s WHERE i.Id = s.ID OR s.ID = -1
    JOIN @InvoiceIdSet S ON S.ID = I.Id OR S.ID=-1

无论我使用哪种选择方法,查询执行效率都很高,直到我开始使用OR运算符,此时它开始花费很长时间返回几条记录,但所有记录都是快速返回的。

任何指示和建议都将受到高度赞赏。

Without OR

With OR

第一个计划没有OR,第二个计划是OR。

更新 在摆弄了不同的选项之后,无论参数数量多少,我都以最快的速度完成了这个解决方案。

首先更改UserDefinedTableType以包含主键索引:

CREATE TYPE [dbo].[TBIGINT] AS TABLE(
[ID] [bigint] NOT NULL PRIMARY KEY CLUSTERED
)

现在,select语句如下所示:

SELECT TOP 100
    I.Id ,
    Number ,
    DueDate ,
    IssuedDate ,
    Amount ,
    Test3
FROM    dbo.Invoices I
WHERE   I.ID IN ( SELECT    S.ID
              FROM      @InvoiceIdSet S
              WHERE     S.ID <> -1
              UNION ALL
              SELECT    S.ID
              FROM      dbo.Invoices S
              WHERE     EXISTS ( SELECT NULL
                                 FROM   @InvoiceIdSet
                                 WHERE  ID = -1 ) )

计划变得更大,但在少数(第一个计划)和所有(第二个计划)记录之间,性能几乎不变。

Few Records

All Records

正如您所看到的,计划现在是相同的,并且在不到一秒的时间内从1M行返回所需的记录。

我很想听听社区对此解决方案的看法。

感谢大家的帮助。

4 个答案:

答案 0 :(得分:1)

如果添加or S.ID=-1,则SQL Server知道每行的条件为真;因此,查询计划将在第二个计划中使用扫描。 正如Martin Smith在评论中所说,SQL Server在这种情况下并不够聪明。您需要有2个查询(如果存在-1则为1,如果仅选择某些行则为其他查询)。这样,SQL Server可以生成2个计划,并且它们都适用于它们所涵盖的场景。 您也可以使用重新编译(但总是会完成RECOMPILE,这通常会浪费资源)。或者您可以动态构造查询。动态地意味着你只生成2个查询,并且它们都将被缓存,因此不需要重新编译,但是要小心它是如何编写的,因此它不容易受到SQL注入的攻击。<​​/ p>

由于

答案 1 :(得分:1)

动态查询是解决方案之一。

DECLARE @InvoiceIdSet AS dbo.TBIGINT;
INSERT INTO @InvoiceIdSet VALUES (1),(2),(3),(4)
--INSERT INTO @InvoiceIdSet VALUES (-1);

DECLARE @SqlStatement NVARCHAR(MAX), @Params NVARCHAR(MAX);

SET @SqlStatement = 
N'    SELECT TOP 100
            inv.Id ,
            inv.Number ,
            inv.DueDate ,
            inv.IssuedDate ,
            inv.Amount ,
            inv.Test3
    FROM    dbo.Invoices inv
' + CASE 
        WHEN EXISTS(SELECT * FROM @InvoiceIdSet i WHERE i.ID = -1) 
        THEN '' 
        ELSE 'WHERE inv.ID IN (SELECT i.ID FROM @pInvoiceIdSet i)'
    END;

SET @Params = N'@pInvoiceIdSet dbo.TBIGINT READONLY';

EXEC sp_executesql @SqlStatement, @Params, @pInvoiceIdSet = @InvoiceIdSet;

答案 2 :(得分:1)

我将在这里接受我自己的答案:

DECLARE @InvoiceIdSet AS TBIGINT
--INSERT  INTO @InvoiceIdSet
--VALUES  ( 1 ),
--        ( 2 ),
--        ( 3 ),
--        ( 4 )
INSERT  INTO @InvoiceIdSet VALUES  ( -1 )

SELECT TOP 100
        I.Id ,
        Number ,
        DueDate ,
        IssuedDate ,
        Amount ,
        Test3
FROM    dbo.Invoices I
WHERE   I.ID IN ( SELECT    S.ID
                  FROM      @InvoiceIdSet S
                  WHERE     NOT EXISTS ( SELECT NULL
                                         FROM   @InvoiceIdSet
                                         WHERE  ID = -1 )
                  UNION ALL
                  SELECT    S.ID
                  FROM      dbo.Invoices S
                  WHERE     EXISTS ( SELECT NULL
                                     FROM   @InvoiceIdSet
                                     WHERE  ID = -1 ) )

它适用于所有和某些场景。

答案 3 :(得分:0)

通过提供减1作为获取所有内容的方法,看起来你在这里做了一个技巧。

我假设这是存储过程中的东西,所以可能在此场景中提供null作为参数并尝试以下内容;

DECLARE @IDparam int

SELECT TOP 100
            I.Id ,
            Number ,
            DueDate ,
            IssuedDate ,
            Amount ,
            Test3
    FROM    dbo.Invoices I
    JOIN @InvoiceIdSet S ON S.ID = I.Id AND COALESCE(@IDparam, I.Id) = I.Id

如果@IDParam为null,则它将在where子句中使用I.Id。可能加快速度。