2010-09-21 25 views
6

J'ai une requête que je viens de trouver dans la base de données qui ne réussit pas à faire tomber un rapport. L'essentiel de base de la requête:Comment puis-je interroger «entre» des données numériques sur un champ non numérique?

Select * 
From table 
Where IsNull(myField, '') <> '' 
And IsNumeric(myField) = 1 
And Convert(int, myField) Between @StartRange And @EndRange 

Maintenant, myField ne contient pas de données numériques dans toutes les lignes [il est de type nvarchar] ... mais cette requête a été évidemment conçu de telle sorte qu'il ne se soucie que sur les lignes où les données dans ce champ sont numériques.

Le problème est que T-SQL (près de ce que je comprends) ne pas le cas court-circuit provoquant ainsi l'article à un fossé sur les dossiers où les données ne sont pas numérique à l'exception:

Msg 245, Level 16, State 1, Line 1 Conversion failed when converting the nvarchar value '/A' to data type int.

À court de dumping toutes les lignes où myField est numérique dans une table temporaire, puis interroger cela pour les lignes où le champ est dans la plage spécifiée, que puis-je faire c'est optimal?

Ma première analyse syntaxique uniquement pour tenter d'analyser les données renvoyées et voir ce qui se passait était:

Select * 
From (
    Select * 
    From table 
    Where IsNull(myField, '') <> '' 
    And IsNumeric(myField) = 1 
) t0 
Where Convert(int, myField) Between @StartRange And @EndRange 

Mais je reçois la même erreur, je l'ai fait pour la première requête que je ne suis pas sûr que je comprends car je ne convertis aucune donnée qui ne devrait pas être numérique à ce stade. La sous-requête doit uniquement renvoyer des lignes où myField contient des données numériques.

Peut-être que j'ai besoin de mon thé du matin, mais est-ce que cela a du sens pour n'importe qui? Un autre ensemble d'yeux aiderait.

Merci à l'avance

+0

La table dérivée ne soit pas matérialisée d'abord, puis la clause 'WHERE' appliquée. Il est traité plus comme une vue où l'optimiseur réécrira simplement le 2ème pour être comme le premier à partir d'une algèbre relationnelle POV ils sont les mêmes. –

+0

Selon [cette page] (http://msdn.microsoft.com/en-us/library/aa226054 (SQL.80% 29.aspx), il n'est pas nécessaire de convertir explicitement 'nvchar' en' int'. – NullUserException

Répondre

5

IsNumeric ne vous indique que la chaîne peut être converti en un de les types numériques dans SQL Server. Il peut être en mesure de le convertir en argent, ou à un flotteur, mais peut ne pas être en mesure de le convertir en un int.

Modifier votre

IsNumeric(myField) = 1 

être:

not myField like '%[^0-9]%' and LEN(myField) < 9 

(qui est, vous voulez myField contenir que les chiffres et insérer dans un int)

Modifier exemples:

select ISNUMERIC('.'),ISNUMERIC('£'),ISNUMERIC('1d9') 

résultat:

----------- ----------- ----------- 
1   1   1 

(1 row(s) affected) 
+0

Ceci en combinaison avec la déclaration de cas fonctionne comme un charme. Vous m'avez aidé à résoudre un dépassement arithmétique en convertissant un nombre qui était trop long pour bigint même. Donc c'était utile. +1 – BenAlabaster

+0

@BenAlabaster - excellent - alors j'ai répondu à votre question de suivi avant que vous ayez eu l'occasion de l'afficher? :-) –

3

Il faudrait que tu forcer SQL pour évaluer les expressions dans un certain ordre. Voici une solution

Select * 
From (TOP 2000000000 
    Select * 
    From table 
    Where IsNumeric(myField) = 1 
    And IsNull(myField, '') <> '' 
    ORDER BY Key 
) t0 
Where Convert(int, myField) Between @StartRange And @EndRange 

et un autre

Select * 
From table 
Where 

CASE 
    WHEN IsNumeric(myField) = 1 And IsNull(myField, '') <> '' 
    THEN Convert(int, myField) ELSE @StartRange-1 
END Between @StartRange And @EndRange 
  • La première technique est "intermédiaire matérialisation": il force une sorte sur une table de travail.
  • Le 2 repose sur l'évaluation ORDER CASE est garanti
  • Ni est assez ou whizzy

SQL est déclarative: vous dites au Optimiseur ce que vous voulez, pas comment le faire. Les tours ci-dessus forcent les choses à être faites dans un certain ordre.

+0

En fait, la question prouve que cette implémentation SQL n'est pas déclarative: vous dites à l'optimiseur ce que vous voulez et il ne parvient pas à convertir votre déclaration en un plan d'exécution viable.Un vrai langage déclaratif, même la condition Convert (int, myField) Entre @StartRange et @EndRange ET IsNumeric (myField) = 1' est correct. (Facile avec la logique ternaire, où 'NULL AND FALSE' est' FALSE') – MSalters

+0

@MSalters: Je dirais que le BETWEEN ou l'ISNUMERIC sont contradictoires à SQL fait sa chose déclarative et décide de la meilleure façon d'obtenir les données, bot quel ORDER pour évaluer les conditions BETWEEN est plus susceptible d'utiliser un index, ISNUMERIC ne l'est pas Réécrire une requête poubelle à cause des données de merde ne fait pas partie de la définition "déclarative" – gbn

+0

Sorr y, pensait que le bit "logique ternaire" le rendait clair. Le côté 'Convert' de l'expression évalue à NULL (erreur) chaque fois que le côté' IsNumeric' est évalué à FALSE. Un vrai langage déclaratif ne jugerait pas cela comme une «requête brute» car le résultat est très bien défini. – MSalters

1

Je ne sais pas si cela vous aide, mais j'ai lu quelque part qu'une conversion incorrecte à l'aide de CONVERT génèrera toujours une erreur dans SQL. Je pense donc qu'il serait préférable d'utiliser CASE dans la clause where pour éviter que CONVERT ne s'exécute sur toutes les lignes

+0

Cela fonctionne, je ne suis pas sûr que c'est optimal, mais c'est au moins un pas de plus que je ne l'étais auparavant. Merci. – BenAlabaster

1

Utilisez une instruction CASE.

declare @StartRange int 
declare @EndRange int 

set @StartRange = 1 
set @EndRange = 3 

select * 
from TestData 
WHERE Case WHEN ISNUMERIC(Value) = 0 THEN 0 
      WHEN Value IS NULL THEN 0 
      WHEN Value = '' THEN 0 
      WHEN CONVERT(int, Value) BETWEEN @StartRange AND @EndRange THEN 1 
      END = 1