编写SQL查询的最佳方法,该方法检查列中是否为非NULL值或NULL


17

我有一个带有默认值为NULL的参数的SP,然后我想执行以下查询:

SELECT ...
FROM ...
WHERE a.Blah = @Blah AND (a.VersionId = @VersionId OR (@VersionId IS NULL AND a.VersionId IS NULL));

WHERE上述检查两个非空值和一个NULL值@VersionId

就性能而言,最好改用一条IF语句并将查询复制到一个搜索非NULL的查询中,而将另一个复制为NULL的查询,这样会更好吗?:

IF @VersionId IS NULL BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId IS NULL;
ELSE BEGIN
    SELECT ...
    FROM ...
    WHERE a.Blah = @Blah AND a.VersionId = @VersionId;
END

还是查询优化器使它基本相同?

更新:

(注意:我正在使用SQL Server)

(据我所知,a.VersionId = @VersionId在两种情况下都无法使用,是吗?)



我通常使用以下代码:ISNULL(a.VersionId,@VersionId)= @VersionId
628426 2015年

Answers:


36

这个图案

column = @argument OR (@argument IS NULL AND column IS NULL)

可以替换为

EXISTS (SELECT column INTERSECT SELECT @argument)

这将使您将NULL与NULL匹配,并使引擎能够column有效地使用索引。要对这种技术进行深入的深入分析,请参考Paul White的博客文章:

由于在特定情况下有两个参数,因此可以将相同的匹配技术与@Blah–一起使用,这样便可以大致简洁地重写整个WHERE子句:

WHERE
  EXISTS (SELECT a.Blah, a.VersionId INTERSECT SELECT @Blah, @VersionId)

索引为时,可以快速运行(a.Blah, a.VersionId)


还是查询优化器使它基本相同?

在这种情况下,可以。从SQL Server 2005开始的所有版本(至少)中,优化器都可以识别该模式col = @var OR (@var IS NULL AND col IS NULL),并通过适当的IS比较将其替换。这确实依赖于内部重写匹配,因此在更复杂的情况下,这并不总是可靠的。

在SQL Server版本从2008 SP1 CU5包容性,也有使用的选项参数中嵌入优化通过OPTION (RECOMPILE),其中的任何参数或变量的运行值嵌入查询作为编译前一个文字。

因此,至少在很大程度上,在这种情况下,选择取决于样式,尽管INTERSECT结构无疑是紧凑而优雅的。

以下示例显示了每个版本的“相同”执行计划(不包括文字和变量引用):

DECLARE @T AS table
(
    c1 integer NULL,
    c2 integer NULL,
    c3 integer NULL

    UNIQUE CLUSTERED (c1, c2)
);

-- Some data
INSERT @T
    (c1, c2, c3)
SELECT 1, 1, 1 UNION ALL
SELECT 2, 2, 2 UNION ALL
SELECT NULL, NULL, NULL UNION ALL
SELECT 3, 3, 3;

-- Filtering conditions
DECLARE 
    @c1 integer,
    @c2 integer;

SELECT
    @c1 = NULL,
    @c2 = NULL;

-- Writing the NULL-handling out explicitly
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
);

-- Using INTERSECT
SELECT * 
FROM @T AS T
WHERE EXISTS 
(
    SELECT T.c1, T.c2 
    INTERSECT 
    SELECT @c1, @c2
);

-- Using separate queries
IF @c1 IS NULL AND @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 IS NULL
ELSE IF @c1 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 IS NULL
    AND T.c2 = @c2
ELSE IF @c2 IS NULL
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 IS NULL
ELSE
    SELECT * 
    FROM @T AS T
    WHERE T.c1 = @c1
    AND T.c2 = @c2;

-- Using OPTION (RECOMPILE)
-- Requires 2008 SP1 CU5 or later
SELECT * 
FROM @T AS T
WHERE 
(
    T.c1 = @c1
    OR (@c1 IS NULL AND T.c1 IS NULL)
)
AND 
(
    T.c2 = @c2
    OR (@c2 IS NULL AND T.c2 IS NULL)
)
OPTION (RECOMPILE);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.