web-dev-qa-db-fra.com

SQL Server: erreur lors de la conversion du type de données varchar en numérique

J'ai une table:

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

Account_Code est un varchar.

Quand je crée une requête ci-dessous:

Select 
  cast(account_code as numeric(20,0)) as account_code,
  descr 
from account 
where isnumeric(account_code) = 1

Il fonctionne bien en renvoyant tous les enregistrements ayant une valeur numérique valide dans account_code colonne.

Mais lorsque j'essaie d'ajouter une autre sélection, imbriquée dans la version précédente de 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

la requête retournera une erreur

Erreur lors de la conversion du type de données varchar en numérique.

Que se passe-t-il là-bas?

J'ai déjà converti en numérique si account_code valide, mais il semble que la requête tente toujours de traiter un enregistrement non valide.

Je dois utiliser la clause BETWEEN dans ma requête.

21
user1947840

SQL Server 2012 et versions ultérieures

Il suffit d'utiliser Try_Convert au lieu:

TRY_CONVERT prend la valeur qui lui est transmise et tente de la convertir dans le type de données spécifié. Si la conversion réussit, TRY_CONVERT renvoie la valeur sous le type de données spécifié. si une erreur se produit, null est renvoyé. Toutefois, si vous demandez une conversion explicitement non autorisée, TRY_CONVERT échoue avec une erreur.

En savoir plus sur Try_Convert .

SQL Server 2008 et versions antérieures

La manière traditionnelle de gérer cela consiste à protéger chaque expression avec une instruction case de sorte que peu importe son évaluation, elle ne créera pas d'erreur, même s'il semble logique que l'instruction CASE ne soit plus nécessaire. Quelque chose comme ça:

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

Cependant, j'aime utiliser des stratégies telles que celle-ci avec SQL Server 2005 et les versions ultérieures:

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

Cela change stratégiquement le Account_Code valeurs pour NULL à l'intérieur de la table X lorsqu'elles ne sont pas numériques. J'ai d'abord utilisé CROSS APPLY mais comme Mikael Eriksson si bien souligné, cela entraînait la même erreur parce que l'analyseur de requête se heurtait exactement au même problème d'optimisation de ma tentative de forcer l'ordre d'expression (le refoulement du prédicat l'a neutralisé) . En passant à OUTER APPLY _ il a changé le sens réel de l'opération pour que X.Account_Code pourrait contenir NULL valeurs dans la requête externe, nécessitant ainsi un ordre d'évaluation correct.

Vous voudrez peut-être lire demande de Microsoft Connect d'Erland Sommarskog à propos de ce problème d'ordre d'évaluation. Il appelle en fait un bug.

Il y a d'autres problèmes ici, mais je ne peux pas les aborder maintenant.

P.S. J'ai eu un brainstorm aujourd'hui. Une alternative à la "méthode traditionnelle" que j'ai suggérée est une expression SELECT avec une référence externe, qui fonctionne également dans SQL Server 2000. (J'ai remarqué cela depuis l'apprentissage de CROSS/OUTER APPLY J'ai également amélioré ma capacité de recherche avec les anciennes versions de SQL Server, car je suis de plus en plus polyvalent avec les fonctionnalités de "référence externe" de SELECT, ON et WHERE clauses!)

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

C'est beaucoup plus court que l'instruction CASE.

29
ErikE

Rien ne garantit que SQL Server ne tentera pas d'exécuter le CONVERT vers numeric(20,0)avant il exécute le filtre dans la clause WHERE.

Et, même si c'est le cas, ISNUMERIC n'est pas adéquat, car il reconnaît £ Et 1d4 Comme numériques, aucun des deux ne pouvant être converti en numeric(20,0). (*)

Divisez-le en deux requêtes distinctes. La première filtre les résultats et les place dans une table ou une variable de table. La seconde effectue la conversion. (Les sous-requêtes et les CTE ne permettent pas à l'optimiseur de tenter la conversion avant le filtre)

Pour votre filtre, utilisez probablement account_code not like '%[^0-9]%' Au lieu de ISNUMERIC.


(*) ISNUMERIC répond à la question à laquelle personne (à ma connaissance) n'a jamais voulu poser la question suivante: "cette chaîne peut-elle être convertie en n'importe lequel des types de données numériques - Je me fiche de qui? " - quand évidemment, ce que la plupart des gens veulent demander, c'est "cette chaîne peut-elle être convertie en x?" où x est un type de données cible spécifique.

9

Si vous exécutez SQL Server 2012 ou une version plus récente, vous pouvez également utiliser la nouvelle fonction TRY_PARSE () :

Renvoie le résultat d'une expression traduite dans le type de données demandé ou null si la conversion échoue dans SQL Server. Utilisez TRY_PARSE uniquement pour la conversion de chaîne en types date/heure et nombre.

Ou TRY_CONVERT / TRY_CAST :

Renvoie une valeur convertie dans le type de données spécifié si la conversion réussit. sinon, renvoie null.

7
mprost

merci, essayez ceci à la place

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

Je suis heureux de t'aider

1
Majed jemmieh

Je pense que le problème n'est pas dans la sous-requête mais dans la clause WHERE de la requête externe. Quand vous utilisez

WHERE account_code between 503100 and 503105

Le serveur SQL essaiera de convertir chaque valeur de votre champ Account_code en entier pour le tester dans les conditions fournies. Évidemment, cela échouera s'il y aura des caractères non-entiers dans certaines lignes.

1
Blindfold