故障排除查询表达式中的错误和警告

有时,SQL Server 2008 计算查询中的表达式的速度快于 SQL Server 2000。此行为具有以下非常有用的优点:

  • 可以将计算列的索引和查询中与计算列表达式相同的表达式进行匹配。

  • 防止出现多余的表达式计算结果。

但是,根据查询的性质和数据库中的数据,如果查询包含的现有表达式不安全,则 SQL Server 2008 中可能会出现运行时异常。这些运行时异常包括:

  • 算术异常:被零除、溢出和下溢。

  • 转换失败,如丢失精度和尝试将非数值字符串转换为数值。

  • 对一组不保证全为非空的值进行聚合。

在 SQL Server 2000 中,这些异常可能不会在使用特定数据的特定应用程序中发生。但是,由于更改统计信息而更改的查询计划可能会导致 SQL Server 2008 中出现异常。通过修改查询以包含条件表达式(例如 NULLIF 或 CASE),可以防止出现这些运行时异常。

重要说明重要提示

可以对在查询的搜索条件、选择列表或任何其他位置上出现的表达式进行划分并重新组织为一个或多个独立的表达式。SQL Server 可以按照这些独立表达式相互的任何顺序对其进行计算。在计算结果列之前,不必应用包含联接在内的筛选操作。

在以下示例中,可以随时计算选择列表中的 x/y 表达式,即使对于最终不满足查询输出条件的行也是如此。

USE tempdb
GO
IF OBJECT_ID('T','U') IS NOT NULL
    DROP TABLE T
IF OBJECT_ID('S','U') IS NOT NULL
    DROP TABLE S
GO
CREATE TABLE T(x float, y float, z nvarchar(30))
CREATE TABLE S(a float, b float)
GO
INSERT INTO T VALUES (1, 0, 'unknown')
INSERT INTO T VALUES(1, 2, '10')
GO
INSERT INTO S VALUES (1, 1)
INSERT INTO S VALUES (1, 2)
INSERT INTO S VALUES (1, 3)
INSERT INTO S VALUES (1, 4)
INSERT INTO S VALUES (1, 5)

以下查询在 SQL Server 2008 中失败,但在 SQL Server 2000 中可以完成。

SELECT x/y FROM T INNER JOIN S ON x = a AND y > b
OPTION(HASH JOIN)

由于在 y=0 的情况下,计算 x/y 表达式将导致被零除错误,因此,查询将失败。

使查询正确执行的一种解决方案是使用以下代码:

SELECT x/NULLIF(y,0) FROM T INNER JOIN S ON x = a AND y > b
OPTION(HASH JOIN)

如果 y = 0,则表达式 NULLIF(y,0) 将返回 NULL。否则,表达式将返回值 y。表达式 x/NULL 将生成 NULL,并且不会出现异常。

请参考以下将字符数据转换为数值类型的示例。

SELECT CONVERT(tinyint, z) FROM T INNER JOIN S ON x = a AND y > b
OPTION(HASH JOIN)

当查询尝试将字符串 'unknown' 转换为 tinyint 时,查询因发生转换错误而失败。此问题的一种解决方案是修改查询,以便仅当 z 为 numeric 时,通过引入下面的 CASE 语句来执行转换:

SELECT CASE WHEN ISNUMERIC(z) = 1
    THEN CONVERT(tinyint, z) 
    ELSE 0 
END
FROM T INNER JOIN S ON x = a AND y > b
OPTION(HASH JOIN)

第二种解决方案是放弃在数据库中使用特殊字符串值 'unknown',而改用 NULL。第三种解决方案是将 z 列的类型更改为 tinyint 以完全避免转换。由于上述解决方案需要分别进行数据和架构更改,因此,与修改查询相比,应用这些解决方案可能需要更多的操作。不过,如果这些解决方案还能使其他查询更易于编写,则可能需要考虑使用它们。

聚合函数发出的 NULL 输入警告

如果聚合函数(如 MIN)的输入包含 NULL,则这些函数将发出空值已消除的警告。此警告可能依赖于计划。如果不希望处理聚合函数中的 NULL 输入,也不希望发出警告,则可以在本地修改查询以消除空值。请参考以下示例中的 SELECT 语句:

USE tempdb
GO
IF OBJECT_ID('newtitles','U') IS NOT NULL
....DROP TABLE newtitles 
GO
CREATE TABLE dbo.newtitles 
   (title varchar (80) NULL ,
    pubdate datetime NULL)
GO
INSERT dbo.newtitles VALUES('Title 1', NULL)
INSERT dbo.newtitles VALUES('Title 2', '20050311')
GO
SELECT t.title, t.pubdate, m.min_pubdate
FROM newtitles AS t,
   (SELECT MIN(pubdate) AS min_pubdate 
    FROM newtitles) AS m
WHERE t.pubdate = m.min_pubdate
GO

在 SQL Server 2008 中,此查询将生成一个警告。若要避免出现此警告,请在聚合之前,通过添加条件 WHERE pubdate IS NOT NULL 更改查询以筛选出空值。

SELECT t.title, t.pubdate, m.min_pubdate
FROM newtitles AS t,
   (SELECT MIN(pubdate) AS min_pubdate 
    FROM newtitles
    WHERE pubdate IS NOT NULL) AS m
WHERE t.pubdate = m.min_pubdate
GO

请参阅

其他资源