web-dev-qa-db-ja.com

無効なレコードがフィルタリングされた後、WHERE句を含むSUBQUERYでNVARCHARをBIGINTに変換する際のエラー

BIGINTに変換できないデータを含むNVARCHAR列を持つテーブルがあります。私はそれをよく知っており、ISNUMERIC(BB.NVARCHARCOL) = 1を使用して除外しています。これにもかかわらず、Error converting data type nvarchar to bigint.を示すデータをクエリしようとすると、エラーが発生します

以下は正常に機能します(SQLによってエラーは報告されません)。

SELECT *
FROM myNormalTable AA INNER JOIN myBadTable BB ON BB.NVARCHARCOL = AA.MYBIGINTCOL
WHERE ISNUMERIC(BB.NVARCHARCOL) = 1

以下はエラーをスローします:

SELECT *
FROM (
    SELECT *
    FROM myNormalTable AA INNER JOIN myBadTable BB ON BB.NVARCHARCOL = AA.MYBIGINTCOL
    WHERE ISNUMERIC(BB.NVARCHARCOL) = 1
    ) ZZ 
WHERE ZZ.MYBIGINTCOL = 1234

このバリエーションもエラーをスローします。

SELECT *
FROM (
    SELECT *
    FROM myNormalTable AA INNER JOIN
        (SELECT CAST(NVARCHARCOL AS BIGINT) NVARCHARCOL FROM myBadTable WHERE ISNUMERIC(NVARCHARCOL) = 1) BB
      ON BB.NVARCHARCOL = AA.MYBIGINTCOL
    ) ZZ 
WHERE ZZ.MYBIGINTCOL = 1234

これはエラーにならず、すべてのレコードが正常に返されることに注意してください...

SELECT CAST(NVARCHARCOL AS BIGINT) NVARCHARCOL FROM myBadTable WHERE ISNUMERIC(NVARCHARCOL) = 1

サブクエリでBIGINTをNVARCHARに変換するという解決策を見つけることができました。

SELECT *
FROM (
    SELECT *
    FROM myNormalTable AA INNER JOIN myBadTable BB ON BB.NVARCHARCOL = CAST(AA.MYBIGINTCOL AS NVARCHAR)
    WHERE ISNUMERIC(BB.NVARCHARCOL) = 1
    ) ZZ 
WHERE ZZ.MYBIGINTCOL = 1234

レコードを一時テーブルに挿入した場合:

SELECT CAST(NVARCHARCOL AS BIGINT) NVARCHARCOL INTO #TEMP FROM myBadTable WHERE ISNUMERIC(NVARCHARCOL) = 1

その一時テーブルをサブクエリで正常に使用できます。

SELECT *
FROM (
    SELECT *
    FROM myNormalTable AA INNER JOIN #TEMP BB ON BB.NVARCHARCOL = AA.MYBIGINTCOL
    WHERE ISNUMERIC(BB.NVARCHARCOL) = 1
    ) ZZ 
WHERE ZZ.MYBIGINTCOL = 1234

世界で何が起こっているのですか?SQLは、外部クエリを実行する前に、サブクエリを使用してより小さな結果セットを取得することを拒否しているようです。私が何をしても、テーブル全体を使用する必要があります。SQLServer 2012 Developerエディション

3
Brad

代わりにtry_cast()を使用してください。

_select *
from (
  select *
  from myNormalTable AA
  inner join #TEMP BB on try_cast(BB.NVARCHARCOL as bigint) = AA.MYBIGINTCOL
  --where try_cast(BB.NVARCHARCOL as bigint) is not null /* not neccessary for inner join */
 ) ZZ
where ZZ.MYBIGINTCOL = 1234
_

SQL Server 2012以降:変換がエラーではなく失敗すると、これらのそれぞれがnullを返します。

3
SqlZim

SqlZimはすでに 彼の答え のエラーを回避するための優れた方法を提供しています。ただし、質問とコメントでは、1つのクエリがエラーをスローし、他のクエリがエラーをスローしない理由について、興味を持っているように見えます。私はあなたの問題を再現することができます:

CREATE TABLE dbo.X_BIGINT_TABLE (ID BIGINT NOT NULL);

INSERT INTO dbo.X_BIGINT_TABLE WITH (TABLOCK)
SELECT TOP (1000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values;

CREATE TABLE dbo.X_NVARCHAR_TABLE (ID_NV NVARCHAR(10) NOT NULL);

INSERT INTO dbo.X_NVARCHAR_TABLE WITH (TABLOCK)
SELECT TOP (999) CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS NVARCHAR(10))
FROM master..spt_values

UNION ALL

SELECT 'ZOLTAN';

このクエリは正常に機能します。

SELECT *
FROM dbo.X_BIGINT_TABLE BI 
INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
WHERE ISNUMERIC(NV.ID_NV) = 1;

このクエリはエラーをスローします。

SELECT *
FROM (
    SELECT *
    FROM dbo.X_BIGINT_TABLE BI 
    INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
    WHERE ISNUMERIC(NV.ID_NV) = 1
) ZZ 
WHERE ZZ.ID = 500;

メッセージ8114、レベル16、状態5、行25

データ型nvarcharをbigintに変換中にエラーが発生しました。

SQL Serverクエリオプティマイザーは、変更がクエリの最終結果に影響を与えない限り、十分な見積もりコストでクエリプランを見つけようとするクエリの要素を並べ替えることができます。概念を説明するために、2番目のクエリをリファクタリングできる1つの可能な方法を見ていきましょう。明確にするために、これはこの例でクエリオプティマイザーが実行する実際の段階的なプロセスではありません。クエリから始めます。

SELECT *
FROM (
    SELECT *
    FROM dbo.X_BIGINT_TABLE BI 
    INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
    WHERE ISNUMERIC(NV.ID_NV) = 1
) ZZ 
WHERE ZZ.ID = 500;

述語を押し下げます。

SELECT *
FROM (
    SELECT *
    FROM dbo.X_BIGINT_TABLE BI 
    INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
    WHERE BI.ID = 500 AND ISNUMERIC(NV.ID_NV) = 1
) ZZ;

派生テーブルは不要になったので、削除してください。

SELECT *
FROM dbo.X_BIGINT_TABLE BI 
INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
WHERE BI.ID = 500 AND ISNUMERIC(NV.ID_NV) = 1

BI.ID = NV.ID_NVがわかっているので、Z.IDにもNV.ID_NVにフィルターを適用できます。

SELECT *
FROM dbo.X_BIGINT_TABLE BI 
INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
WHERE BI.ID = 500 AND ISNUMERIC(NV.ID_NV) = 1 AND NV.ID_NV = 500

両方の結合列で単一の値にフィルタリングするため、結合をINNER JOINとして実装する必要はなくなりました。 CROSS JOINに書き換えることができます。

SELECT * 
FROM 
(
    SELECT *
    FROM dbo.X_BIGINT_TABLE BI 
    WHERE BI.ID = 500
) 
CROSS JOIN 
(
    SELECT *
    FROM dbo.X_NVARCHAR_TABLE NV
    WHERE ISNUMERIC(NV.ID_NV) = 1 AND NV.ID_NV = 500
);

2番目のクエリのクエリプランを見ると、最終結果が最終的に変換されたクエリに非常に類似していることがわかります。

transformed query

参考のために、フィルター述語のテキストを次に示します。

CONVERT_IMPLICIT(bigint,[SE_DB].[dbo].[X_NVARCHAR_TABLE].[ID_NV] as [NV].[ID_NV],0)=(500) 
AND isnumeric(CONVERT_IMPLICIT(varchar(20),[SE_DB].[dbo].[X_NVARCHAR_TABLE].[ID_NV] as [NV].[ID_NV],0))=(1)

SQL Serverが述語のCONVERT_IMPLICIT部分をisnumeric部分の前に評価すると、エラーが発生します。

一般的な規則として、SQLクエリを記述するときは、暗黙の操作順序に依存しないようにします。今日は正常に機能するクエリがあるかもしれませんが、データがテーブルに追加された場合、または別のクエリプランが選択された場合、エラーが発生し始めます。もちろん、例外があります(一種)。実際には、通常、CASEステートメントのさまざまな部分が記述された順序で評価されますが、それでも予期しない エラーが発生する可能性があります 。クエリの一部に余分なTOPを追加して、特定の順序の操作を促すこともできます。次のクエリについて考えてみます。

SELECT *
FROM (
    SELECT TOP (9223372036854775807) *
    FROM dbo.X_BIGINT_TABLE BI 
    INNER JOIN dbo.X_NVARCHAR_TABLE NV ON BI.ID = NV.ID_NV
    WHERE ISNUMERIC(NV.ID_NV) = 1
    ) ZZ 
WHERE ZZ.ID = 500;

あなたと私はTOPがクエリの結果を変更しないことを知っていますが、オプティマイザが9223372036854775807を超える行を派生テーブルが返さないという保証はないため、TOPを評価する必要があります。技術的には、そのクエリで最初の9223372036854775807行を要求してから、IDが500以外の行をフィルターで除外します。ID = 500述語を派生テーブルにプッシュすると結果が変わるため、SQL Serverは実行しませんそれ。この例では、クエリはエラーなしで実行され、フィルタリングは最後に行われます。

top plan

4
Joe Obbish