J'ai une table:
Account_Code | Desc
503100 | account xxx
503103 | account xxx
503104 | account xxx
503102A | account xxx
503110B | account xxx
Où 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.
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
.
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.
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.
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
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.