私はテーブルを持っています:
Account_Code | Desc
503100 | account xxx
503103 | account xxx
503104 | account xxx
503102A | account xxx
503110B | account xxx
ここで、Account_Code
はvarchar
です。
以下のクエリを作成すると:
Select
cast(account_code as numeric(20,0)) as account_code,
descr
from account
where isnumeric(account_code) = 1
account_code
列に有効な数値を持つすべてのレコードを返すことにより、正常に実行されます。
しかし、以前のSQLにネストされた別の選択を追加しようとすると:
select account_code,descr
from
(
Select cast(account_code as numeric(20, 0)) as account_code,descr
from account
where isnumeric(account_code) = 1
) a
WHERE account_code between 503100 and 503105
クエリはエラーを返します
データ型varcharから数値への変換エラー。
そこで何が起きているのでしょうか?
account_code
が有効な場合は既に数値に変換しましたが、クエリはまだ無効なレコードを処理しようとしているようです。
クエリでBETWEEN
句を使用する必要があります。
SQL Server 2012以降
代わりにTry_Convert
を使用してください:
TRY_CONVERTは渡された値を受け取り、指定されたdata_typeに変換しようとします。キャストが成功すると、TRY_CONVERTは指定されたdata_typeとして値を返します。エラーが発生した場合、nullが返されます。ただし、明示的に許可されていない変換を要求すると、TRY_CONVERTはエラーで失敗します。
SQL Server 2008以前
これを処理する従来の方法は、CASEステートメントで論理式が必要であると論理的に思われる場合でも、いつ評価されるかに関係なく、すべての式をcaseステートメントで保護することです。このようなもの:
SELECT
Account_Code =
Convert(
bigint, -- only gives up to 18 digits, so use decimal(20, 0) if you must
CASE
WHEN X.Account_Code LIKE '%[^0-9]%' THEN NULL
ELSE X.Account_Code
END
),
A.Descr
FROM dbo.Account A
WHERE
Convert(
bigint,
CASE
WHEN X.Account_Code LIKE '%[^0-9]%' THEN NULL
ELSE X.Account_Code
END
) BETWEEN 503100 AND 503205
ただし、SQL Server 2005以降では、次のような戦略を使用するのが好きです。
SELECT
Account_Code = Convert(bigint, X.Account_Code),
A.Descr
FROM
dbo.Account A
OUTER APPLY (
SELECT A.Account_Code WHERE A.Account_Code NOT LIKE '%[^0-9]%'
) X
WHERE
Convert(bigint, X.Account_Code) BETWEEN 503100 AND 503205
これが行うことは、Account_Code
値が数値でない場合、NULL
テーブル内でX
に戦略的に切り替えます。最初にCROSS APPLY
を使用しましたが、 Mikael Eriksson として適切に指摘しましたが、クエリパーサーが式の順序を強制する私の試みを最適化するまったく同じ問題に遭遇したため、これは同じエラーになりました(述語プッシュダウンはそれを打ち負かしました)。 OUTER APPLY
に切り替えると、操作の実際の意味が変更され、X.Account_Code
couldが外部クエリ内にNULL
値を含むようになりました。適切な評価順序が必要です。
この評価順序の問題について Erland SommarskogのMicrosoft Connectリクエスト をお読みください。実際、彼はそれをバグと呼んでいます。
ここには追加の問題がありますが、今は対処できません。
追伸今日はブレインストーミングをしました。私が提案した「伝統的な方法」の代替は、SQL Server 2000でも機能する外部参照を持つSELECT
式です。(CROSS/OUTER APPLY
を学習してから改善されたことに気付きました旧バージョンのSQL Serverでのクエリ機能も、-SELECT
、ON
、およびWHERE
句の「外部参照」機能でより汎用性が増しているためです!)
SELECT
Account_Code =
Convert(
bigint,
(SELECT A.AccountCode WHERE A.Account_Code NOT LIKE '%[^0-9]%')
),
A.Descr
FROM dbo.Account A
WHERE
Convert(
bigint,
(SELECT A.AccountCode WHERE A.Account_Code NOT LIKE '%[^0-9]%')
) BETWEEN 503100 AND 503205
CASE
ステートメントよりもずっと短いです。
SQL ServerがCONVERT
をnumeric(20,0)
に実行しようとしないという保証はありませんbeforeは、WHERE
句でフィルターを実行します。
また、ISNUMERIC
は、£
および1d4
を数値として認識し、どちらもnumeric(20,0)
に変換できないため、適切ではありません。 (*)
それを2つの個別のクエリに分割します。最初のクエリは結果をフィルタリングし、一時テーブルまたはテーブル変数に配置し、2番目のクエリは変換を実行します。 (サブクエリとCTEは、オプティマイザがフィルタの前に変換を試みることを防ぐには不十分です)
フィルターには、おそらくISNUMERIC
の代わりにaccount_code not like '%[^0-9]%'
を使用してください。
(*)ISNUMERIC
は、(私が知っている限りでは)誰も尋ねたくない質問に答えます-「この文字列は、数値データ型のanyに変換できますか? -どちらでも構いませんか?」 -明らかに、ほとんどの人が尋ねたいのは「この文字列をxに変換できますか?」ここで、x
は特定ターゲットデータ型です。
SQL Server 2012以降を実行している場合は、新しい TRY_PARSE() 関数も使用できます。
要求されたデータ型に変換された式の結果を返します。SQLServerでキャストが失敗した場合はnullを返します。 TRY_PARSEは、文字列から日付/時刻および数値型への変換にのみ使用してください。
または TRY_CONVERT / TRY_CAST :
キャストが成功した場合、指定されたデータ型にキャストされた値を返します。それ以外の場合は、nullを返します。
おかげで、代わりにこれを試してください
Select
STR(account_code) as account_code_Numeric,
descr
from account
where STR(account_code) = 1
お手伝いさせていただきます
問題はサブクエリではなく、外部クエリのWHERE句にあると思います。使用するとき
WHERE account_code between 503100 and 503105
SQLサーバーは、Account_codeフィールドのすべての値を整数に変換して、指定された条件でテストしようとします。明らかに、一部の行に整数以外の文字がある場合、そうするのに失敗します。