クエリ式に関するエラーと警告のトラブルシューティング

SQL Server 2008 では、SQL Server 2000 よりも高速にクエリ内の式が評価されることがあります。これによって、次の重要なメリットがもたらされます。

  • 計算列のインデックスと、その計算列の式と同じクエリ内の式とを照合できる。

  • 式の結果の余分な計算を回避できる。

ただし、安全ではない式がクエリ内に既に含まれている場合、クエリの性質やデータベース内のデータによっては、SQL Server 2008 でランタイム例外が発生する場合があります。これらの実行時例外には次に示すものがあります。

  • 算術例外。0 除算、オーバーフロー、アンダーフローなど。

  • 精度の低下、数値以外の文字列の数への変換などの変換エラー。

  • すべて NULL 以外であることが保証されない値セットに対する集計。

SQL Server 2000 では、これらの例外は特定のデータを使用する特定のアプリケーションでは発生しない場合があります。ただし、統計の変更によって変更されるクエリ プランが原因で、SQL Server 2008 に例外が発生する可能性があります。これらのランタイム例外は、クエリを変更して NULLIF または CASE などの条件式を含めることによって回避できます。

重要な注意事項重要

検索条件、選択リスト、またはクエリ内のそれ以外の部分に使用される式は、分解されて 1 つ以上の独立した式に再構成される場合があります。このように独立した式は、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)

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)

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

このクエリが失敗するのは、y=0 に対して式が評価されるときに、x/y 式で 0 除算エラーが発生するためです。

次のコードは、このクエリを正しく実行するための解決策です。

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

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)

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

このクエリは、文字列 'unknown' から tinyint への変換を試行したときに変換エラーが発生して失敗します。この問題の解決策の 1 つは、次に示すように CASE ステートメントを導入して、z が numeric の場合にのみ変換が実行されるようにクエリを変更することです。

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)

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)

2 つ目の解決策は、特殊な文字列値 'unknown' をデータベース内で使用せず、代わりに NULL を使用する方法です。3 つ目の解決策は、z 列の型を tinyint に変更して、変換をまったく行わないようにする方法です。このような解決策では、それぞれデータとスキーマを変更する必要があるので、これらの解決策を適用するにはクエリを変更するよりも多くの作業を伴う場合があります。ただし、他のクエリの記述も簡単にできるのであれば、このような解決策も有効です。

集計関数が発行する NULL 入力に関する警告

MIN などの集計関数の入力に NULL が含まれている場合、NULL 値が削除されたという警告が関数から発行されます。発行される警告はプランによって異なる場合があります。集計への 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

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 を追加して、集計の前に 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

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

関連項目

その他の技術情報