我有一个查询优化问题。假设有一张包含所有发票的表格。使用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运算符,此时它开始花费很长时间返回几条记录,但所有记录都是快速返回的。
任何指示和建议都将受到高度赞赏。
第一个计划没有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 ) )
计划变得更大,但在少数(第一个计划)和所有(第二个计划)记录之间,性能几乎不变。
正如您所看到的,计划现在是相同的,并且在不到一秒的时间内从1M行返回所需的记录。
我很想听听社区对此解决方案的看法。
感谢大家的帮助。
答案 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
。可能加快速度。