2010-09-21 10 views
6

Ich habe eine Abfrage, die ich gerade in der Datenbank gefunden habe, die fehlschlägt, einen Bericht zu stürzen. Die Grundgedanken der Abfrage:Wie kann ich 'zwischen' numerischen Daten in einem nicht numerischen Feld abfragen?

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

Nun myField enthält keine numerische Daten in allen Zeilen [es ist von nvarchar type] ... aber diese Abfrage wurde offensichtlich so ausgelegt, dass es sich nur um Reihen interessiert wo die Daten in diesem Feld numerisch sind.

Das Problem dabei ist, dass T-SQL (nahe wie ich sie verstehe) nicht die nicht kurzzuschließen Where-Klausel wodurch es auf Aufzeichnungen zu Graben, wo die Daten nicht numerisch mit der Ausnahme ist:

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

Kurz vor dem Dumping aller Zeilen, wo MyField numerisch ist in eine temporäre Tabelle und dann Abfrage für Zeilen, wo das Feld in dem angegebenen Bereich ist, was kann ich tun, das optimal ist?

Mein erstes Parsen rein um zu versuchen, die zurückgegebenen Daten zu analysieren und sehen, was los war:

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

Aber ich bekomme die gleichen Fehler, den ich für die erste Abfrage tat, was ich bin nicht sicher, ich verstehe weil ich keine Daten umwandele, die zu diesem Zeitpunkt nicht numerisch sein sollten. Die Unterabfrage sollte nur Zeilen enthalten, in denen myField numerische Daten enthält.

Vielleicht brauche ich meinen Morgentee, aber macht das für jeden Sinn? Ein anderer Satz Augen würde helfen.

Vielen Dank im Voraus

+0

Die abgeleitete Tabelle zuerst erhält nicht materialisiert dann 'die WHERE' Klausel angewendet. Es wird eher wie eine Ansicht behandelt, bei der der Optimierer das zweite wie das erste umschreiben wird, da es von einer relationalen Algebra POV identisch ist. –

+0

Laut [dieser Seite] (http://msdn.microsoft.com/en-us/library/aa226054 (SQL.80% 29.aspx)) muss "nvchar" nicht explizit in "int" konvertiert werden. – NullUserException

Antwort

5

IsNumeric nur sagt Ihnen, dass der String zu einem der der numerischen Typen in SQL Server konvertiert werden. Es kann in der Lage sein, es zu Geld oder zu einem Gleitkomma zu konvertieren, aber es ist möglicherweise nicht möglich, es zu einem int zu konvertieren.

Ändern Sie Ihre

IsNumeric(myField) = 1 

sein:

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

(das heißt, Sie myField nur Ziffern enthalten soll, und passen in einen int)

bearbeiten Beispiele:

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

Ergebnis:

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

(1 row(s) affected) 
+0

Dies in Kombination mit der Case-Anweisung funktioniert wie ein Zauber. Du hast mir tatsächlich geholfen, einen arithmetischen Überlauf zu lösen, der eine Zahl umwandelte, die für bigint even zu lang war. Das war also hilfreich. +1 – BenAlabaster

+0

@BenAlabaster - sehr gut - also habe ich deine Frage beantwortet, bevor du eine Chance hast, sie zu posten? :-) –

3

Sie müßten SQL zwingen, die Ausdrücke in einer bestimmten Reihenfolge zu bewerten. Hier ist eine Lösung

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 

und eine andere

Select * 
From table 
Where 

CASE 
    WHEN IsNumeric(myField) = 1 And IsNull(myField, '') <> '' 
    THEN Convert(int, myField) ELSE @StartRange-1 
END Between @StartRange And @EndRange 
  • Die erste Technik "intermediate Materialisierung" ist: Es zwingt eine Art auf einem Arbeitstisch.
  • Die zweite beruht auf der Bewertung CASE ORDER
  • garantiert Weder ist hübsch oder whizzy

SQL deklarative ist: Sie optimiser sagen, was Sie wollen, nicht, wie es zu tun. Die Tricks oben zwingen Dinge in einer bestimmten Reihenfolge zu tun.

+0

Tatsächlich beweist die Frage, dass diese SQL-Implementierung nicht deklarativ ist.Sie ​​sagen dem Optimierer, was Sie wollen, und es kann Ihre Deklaration nicht in einen ausführbaren Ausführungsplan konvertieren.In einer echten deklarativen Sprache, sogar die Bedingung 'Convert (int, myField) Zwischen @StartRange und @EndRange UND IsNumeric (myField) = 1' ist richtig. (Einfach mit ternärer Logik, wobei 'NULL UND FALSCH' 'FALSE' ist) – MSalters

+0

@MSalters: Ich würde sagen, dass die BETWEEN oder die ISNUMERIC widersprüchlich sind auf dieser Ebene SQL macht seine deklarative Sache und entscheidet, wie man die Daten am besten bekommt, bot, was ORDER die Bedingungen auswertet BETWEEN verwendet eher einen Index, ISNUMERIC ist das nicht Das Umschreiben einer Müllabfrage wegen Mistdaten ist kein Teil der "deklarativen" Definition – gbn

+0

Sorr y, dachte das "ternäre Logik" -Bit machte es klar. Die "Convert" -Seite des Ausdrucks würde zu NULL (Fehler) ausgewertet, wenn die "IsNumeric" -Seite zu FALSE ausgewertet wird. Eine echte deklarative Sprache würde dies nicht als "Müllfrage" bezeichnen, da das Ergebnis sehr gut definiert ist. – MSalters

1

Nicht sicher, ob dies Ihnen hilft, aber ich habe irgendwo gelesen, dass eine falsche Konvertierung mit CONVERT immer Fehler in SQL generiert. Also ich denke, es wäre besser, CASE in where-Klausel zu verwenden, um zu vermeiden, dass CONVERT in allen Zeilen ausgeführt wird

+0

Das funktioniert, ich bin mir nicht sicher, ob es optimal ist, aber das ist mindestens einen Schritt weiter als zuvor. Vielen Dank. – BenAlabaster

1

Verwenden Sie eine CASE Anweisung.

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