web-dev-qa-db-ja.com

ストアドプロシージャで実行するとCASTスローエラーが発生するが、生のクエリとして実行すると発生しない

ここで非常に奇妙なことが起こっています。

次のようなクエリがあります。

SELECT CAST(FT.DOP AS SMALLINT) FROM TRACKING_DATA WHERE date > @mydate and identifier = 0000000000

生のクエリとして実行すると、データは正常に返されます。

Where句を変更するストアドプロシージャに配置すると、このエラーがスローされます。

Msg 244, Level 16, State 2, Procedure myprocedure, Line 107 [Batch Start Line 2]
The conversion of the varchar value '58629' overflowed an INT2 column. Use a larger integer column.

だからここに奇妙です。このようなクエリでwhere句のすべての可能なデータを調べます。

SELECT DISTINCT DOP FROM TRACKING_DATA where identifier = 000000000000

そして

SELECT DISTINCT CAST(DOP AS smallint) FROM TRACKING_DATA where identifier = 000000000000

そして、これは私が取り戻すものです。

17
12
9
19
8
14
6
16
11
13
7
10
0
18
5
15
4

SMALLINTに対してリモートで大きすぎるものはどこにもありません。だから私は思った、多分それは印刷できないASCII文字です。しかし、何も見つけることができません。

今は少し困惑しています。それは生のクエリとしてfindを実行し、プロシージャとして爆発し、whereが有効であることに基づいてすべての可能なデータを爆発させます。私の唯一の疑いは、クエリプランがフィルタリングで奇妙なことをしている、またはプロシージャとして実行したときに別の検証で実行していることです。

8
Chris Rice

これらのエラーを回避するには、インデックス作成に依存せず、代わりにクエリを記述してこの状況を防ぐ方がよいでしょう。 SQL Server 2012を使用しているので、1つのオプションはTRY_CASTを使用することです。

SELECT TRY_CAST(FT.DOP AS SMALLINT) 
FROM TRACKING_DATA 
WHERE date > @mydate and identifier = 0000000000;

これにより、varcharからsmallintへの変換に失敗した値に対してNULLが選択されます。しかし、そのような結果がないこと、またはアプリケーションがNULLの結果を処理できることがわかっている限り、問題はありません。

13
Josh Darnell

それで、それはそれが使用することを決め続けたいまいましいクエリ計画でした。

爆発している値はテーブルに存在していましたが(それはあり得ないはずですが、それは別の問題です)、完全に異なる識別子に関連付けられていました。

問題のクエリはclustered seekdateをカバーするがidentifierをカバーしないインデックスでこれを行うと、ENTIREテーブルがスキャンされましたが、これは多くの理由で間違っています。

where句に適切なインデックスを追加しました。日付の後に進む前に識別子でフィルター処理を行うため、この手順は問題なく終了しています。私は統計の前後を取得できればいいのにと思っていますが、今でも実行速度が速いと確信しています。

12
Chris Rice

あなたは明らかに異なるクエリプランを取得しており、キャストはwhere句によってフィルターで除外された値に対して試行されています。理由を理解しようとはしません。原因が何であれ、後で別の理由で明らかに発生する可能性があります。フィルタリングに依存することは信頼できません。

すべきことは、インデックス、ヒント、統計に関係なく失敗しないクエリを作成することです。

これには3つの方法があると思います。

  1. 一時テーブル/テーブル変数を使用します。

    declare @result table (dop varchar);
    Insert into @result
    SELECT FT.DOP  FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    
    SELECT cast(dop as SMALLINT) dop from @result
    
  2. Caseステートメントを使用します。

    SELECT CAST(case when FT.DOP like '%[^0-9]%' then null else ft.dop end AS SMALLINT) 
    FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    
  3. 代わりに、キャストのtry_castを使用します。

    SELECT try_CAST(FT.DOP AS SMALLINT) 
    FROM TRACKING_DATA 
    WHERE date > @mydate and identifier = 0000000000
    

個人的には、できれば3をお勧めします。

1
jmoreno