web-dev-qa-db-ja.com

SQL Server:データ型varcharから数値への変換エラー

私はテーブルを持っています:

Account_Code | Desc
503100       | account xxx
503103       | account xxx
503104       | account xxx
503102A      | account xxx
503110B      | account xxx

ここで、Account_Codevarcharです。

以下のクエリを作成すると:

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句を使用する必要があります。

21
user1947840

SQL Server 2012以降

代わりにTry_Convertを使用してください:

TRY_CONVERTは渡された値を受け取り、指定されたdata_typeに変換しようとします。キャストが成功すると、TRY_CONVERTは指定されたdata_typeとして値を返します。エラーが発生した場合、nullが返されます。ただし、明示的に許可されていない変換を要求すると、TRY_CONVERTはエラーで失敗します。

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_Codecouldが外部クエリ内にNULL値を含むようになりました。適切な評価順序が必要です。

この評価順序の問題について Erland SommarskogのMicrosoft Connectリクエスト をお読みください。実際、彼はそれをバグと呼んでいます。

ここには追加の問題がありますが、今は対処できません。

追伸今日はブレインストーミングをしました。私が提案した「伝統的な方法」の代替は、SQL Server 2000でも機能する外部参照を持つSELECT式です。(CROSS/OUTER APPLYを学習してから改善されたことに気付きました旧バージョンのSQL Serverでのクエリ機能も、-SELECTON、および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ステートメントよりもずっと短いです。

29
ErikE

SQL ServerがCONVERTnumeric(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を返します。

7
mprost

おかげで、代わりにこれを試してください

Select 
  STR(account_code) as account_code_Numeric,
  descr 
from account 
where  STR(account_code) = 1

お手伝いさせていただきます

1
Majed jemmieh

問題はサブクエリではなく、外部クエリのWHERE句にあると思います。使用するとき

WHERE account_code between 503100 and 503105

SQLサーバーは、Account_codeフィールドのすべての値を整数に変換して、指定された条件でテストしようとします。明らかに、一部の行に整数以外の文字がある場合、そうするのに失敗します。

1
Blindfold