8 Stimmen

Wie kann ich wahr/falsch/unbekannt ohne Wiederholung auf -1/0/null abbilden?

Ich arbeite derzeit an einem Tool, das meinen Benutzern helfen soll, ihren SQL-Code auf SQL-Server 2005 zu portieren. Zu diesem Zweck analysiere ich den SQL-Code in einen Syntaxbaum, suche nach Konstrukten, die beachtet werden müssen, ändere ihn und wandle ihn wieder in T-SQL um.

Eine Sache, die ich unterstützen möchte, ist die "bools sind auch Werte"-Semantik von anderen RDBMS. MS-Access erlaubt mir zum Beispiel, Folgendes zu schreiben select A.x and A.y as r from A was in T-SQL unmöglich ist, weil:

  1. Spalten können nicht den Typ Boolean haben (Spaltenwerte können nicht and'ed sein)
  2. Logische Prädikate können nicht verwendet werden, wenn Ausdrücke erwartet werden.

Daher wandelt meine Transformationsroutine die obige Aussage in diese um:

select case 
  when (A.x<>0) and (A.y<>0)
   then -1 
  when not((A.x<>0) and (A.y<>0)) 
   then 0 
  else 
   null 
  end as r 
 from A;

Das funktioniert, ist aber lästig, weil ich den logischen Ausdruck (der sehr komplex sein oder Unterabfragen usw. enthalten kann) duplizieren muss, um zu unterscheiden zwischen true , false y unknown - wird letztere auf Null abgebildet. Ich frage mich also, ob die T-SQL-Profis hier einen besseren Weg kennen, um dies zu erreichen?

UPDATE: Ich möchte darauf hinweisen, dass bei Lösungen, die versuchen, die Operanden im Ganzzahlbereich zu halten, zu berücksichtigen ist, dass einige Operanden können logische Ausdrücke in der erster Platz . Dies bedeutet, dass eine effiziente Lösung für die Umwandlung eines bool in einen Wert erforderlich ist. Zum Beispiel:

select A.x and exists (select * from B where B.y=A.y) from A;

1voto

Scott Weinstein Punkte 18520

Ich glaube nicht, dass es eine gute Antwort gibt, es ist wirklich eine Einschränkung von TSQL.

Sie können für jeden benötigten booleschen Ausdruck eine UDF erstellen

CREATE FUNCTION AndIntInt
(
    @x as int,@y as int
)
RETURNS int
AS
BEGIN

    if (@x<>0) and (@y<>0)
        return -1
    if not((@x<>0) and (@y<>0)) 
        return  0 
    return null
END

verwendet über

select AndIntInt(A.x,A.y) as r from A

1voto

RichardTheKiwi Punkte 102469

Boolesche Behandlung

Access scheint die Logik zu verwenden, dass bei 2 Booleschen

  • Beide müssen true sein, um true zurückzugeben
  • Entweder false gibt false zurück (unabhängig von Nullen)
  • Andernfalls wird null zurückgegeben.

Ich bin nicht sicher, ob andere DBMS (Oracle, DB2, PostgreSQL) so mit bool+null umgehen, aber diese Antwort basiert auf der Access-Bestimmung (MySQL und SQLite stimmen überein). Die Ergebnistabelle ist unten dargestellt.

X      Y      A.X AND B.Y
0      0      0
0      -1     0
0      (null) 0
-1     0      0
-1     -1     -1
-1     (null) (null)
(null) 0      0
(null) -1     (null)
(null) (null) (null)

SQL Server-Hilfe 1: Funktion für booleschen Wert aus einem beliebigen "Einzelwert"

In SQL Server im Allgemeinen füllt diese Funktion die Lücke für die fehlende any value as boolean Funktionalität. Sie gibt ein ternäres Ergebnis zurück, entweder 1/0/null - 1 und 0 sind das SQL Server-Äquivalent von true/false (ohne tatsächlich boolesch zu sein).

drop function dbo.BoolFromAny
GO
create function dbo.BoolFromAny(@v varchar(max)) returns bit as
begin
return (case
    when @v is null then null
    when isnumeric(@v) = 1 and @v like '[0-9]%' and (@v * 1.0 = 0) then 0
    else 1 end)
end
GO

Hinweis: Ausgehend von Access wird nur der numerische Wert 0 als FALSE ausgewertet. Dazu werden einige SQL Server-Tricks verwendet

  1. alles ist in varchar konvertierbar. Daher ist nur eine Funktion erforderlich, die eine varchar-Eingabe benötigt.
  2. isnumeric ist nicht umfassend, '.' liefert 1 für isnumeric aber scheitert an @v * 1.0 so dass ein expliziter Test auf LIKE [0-9]%`` ist erforderlich, um isnumerisch zu "reparieren".
  3. @v * 1.0 ist erforderlich, um einige arithmetische Probleme zu überwinden. Wenn Sie die Zeichenkette "1" ohne *1.0 an die Funktion übergeben, wird sie bombardiert

Jetzt können wir die Funktion testen.

select dbo.BoolFromAny('abc')
select dbo.BoolFromAny(1)
select dbo.BoolFromAny(0)  -- the only false
select dbo.BoolFromAny(0.1)
select dbo.BoolFromAny(-1)
select dbo.BoolFromAny('')
select dbo.BoolFromAny('.')
select dbo.BoolFromAny(null)  -- the only null

Sie können es jetzt sicher in einer Abfrage gegen JEDE EINZELNE SPALTE verwenden, wie zum Beispiel

SELECT dbo.BoolFromAny(X) = 1

SQL Server-Helfer 2: Funktion zur Rückgabe des Ergebnisses von BOOL AND BOOL

Der nächste Teil ist die Erstellung der gleichen Wahrheitstabelle in SQL Server. Diese Abfrage zeigt Ihnen, wie zwei Bit-Spalten und die einfache CASE-Anweisung zusammenwirken, um dieselbe Tabelle wie in Access und Ihre kompliziertere Tabelle zu erstellen.

select a.a, b.a,
  case
  when a.a = 0 or b.a = 0 then 0
  when a.a = b.a then 1
  end
from
(select 1 A union all select 0 union all select null) a,
(select 1 A union all select 0 union all select null) b
order by a.a, b.a

Dies lässt sich leicht als Funktion ausdrücken

create function dbo.BoolFromBits(@a bit, @b bit) returns bit as
begin
  return case
    when @a = 0 or @b = 0 then 0
    when @a = @b then 1
    end
end

SQL Server Konvertierung von anderen Ausdrücken (nicht von einem einzelnen Wert)

Aufgrund der fehlenden Unterstützung für die Umwandlung von Bits in Booleans müssen Ausdrücke, die in SQL Server bereits [true/false/null] sind, in einer CASE-Anweisung wiederholt werden.

Ein Beispiel ist ein "true boolean" in SQL Server, das nicht das Ergebnis einer Spalte sein kann.

select A > B -- A=B resolves to one of true/false/null
from C

Muß ausgedrückt werden als

select case when A is null or B is null then null when A > B then 1 else 0 end
from C

Wenn aber A kein Einzelwert ist, sondern eine Unterabfrage wie (select sum(x)...) wie Sie sehen, erscheint A zweimal und wird in der CASE-Anweisung zweimal ausgewertet (wiederholt).

ABSCHLUSSPRÜFUNG Jetzt setzen wir alle Konvertierungsregeln in diesem langen Ausdruck um

SELECT X AND Y=Z AND C FROM ..
( assume X is numeric 5, and C is varchar "H" )
( note C contributes either TRUE or NULL in Access )

Dies wird auf SQL Server übertragen (Verkettung der beiden Funktionen und Verwendung von CASE)

SELECT dbo.BoolFromBits(
       dbo.BoolFromBits(dbo.BoolFromAny(X), CASE WHEN Y=Z then 1 else 0 end),
                       dbo.BoolFromAny(C))
FROM ...

Zugang Bool or bool

Der Vollständigkeit halber ist hier die Wahrheitstabelle für Access bool OR bool . Im Wesentlichen ist es das Gegenteil von AND, also

  • Beide müssen false sein, um false zurückzugeben
  • Beides ist wahr und gibt wahr zurück (unabhängig von Nullen)
  • Andernfalls wird null zurückgegeben.

Die SQL SERVER-Fallanweisung würde also lauten

  case
  when a.a = 1 or b.a = 1 then 1
  when a.a = b.a then 0
  end

(das Weglassen einer ELSE-Klausel ist beabsichtigt, da das Ergebnis NULL ist, wenn es weggelassen wird)

-1voto

Solomon Rutzky Punkte 44018

EDIT: Aufgrund zusätzlicher Informationen, die der Frage hinzugefügt wurden, und aufgrund von Kommentaren zu einem der Antwortvorschläge formuliere ich diese Antwort neu:

Wenn Sie auf SQL Server portieren, würde ich erwarten, dass Sie die Daten auch so umwandeln, dass sie den SQL Server-Typen entsprechen. Wenn Sie ein boolesches Feld haben, dann entsprechen True, False und Unknown den Werten 1, 0 und NULL in einem NULLable BIT-Feld.

In diesem Sinne müssen Sie sich nur um die Umwandlung reiner boolescher Werte kümmern. Ausdrücke wie z. B.:

exists (select * from B where B.y=A.y)

und:

A.x in (1,2,3)

sind bereits in einer praktikablen Form. Das heißt, Aussagen wie:

IF (EXISTS(SELECT 1 FROM Table))

und:

IF (value IN (list))

sind bereits korrekt. Sie müssen sich also nur darum kümmern, dass "1" für "Wahr" allein nicht ausreicht. Daher können Sie "1"-Werte in boolesche Ausdrücke umwandeln, indem Sie prüfen, ob sie tatsächlich gleich "1" sind. Zum Beispiel:

IF (value = 1)

ist das Äquivalent zu dem, was Sie vorher als hatten:

IF (value)

Wenn man all dies zusammennimmt, sollte man in der Lage sein, alle Instanzen von reinen booleschen Werten des alten Codes einfach in boolesche Ausdrücke in der Form "Wert = 1" zu übersetzen, da eine 1 ein Wahr, eine 0 ein Falsch und NULL ein Falsch ergibt.

Die eigentliche Komplexität besteht jedoch darin, dass die Auswahl des Wertes im Vergleich zur Prüfung über eine WHERE-Bedingung unterschiedlich ist. Boolesche Ausdrücke werden in WHERE-Bedingungen korrekt ausgewertet, haben aber keine direkte Darstellung in SELECT (zumal NULL / Unbekannt nicht wirklich boolesch ist). Sie können also die Übersetzung "Wert = 1" in WHERE-Bedingungen verwenden, aber Sie benötigen immer noch eine CASE-Anweisung, wenn Sie den Wert als Ergebnis AUSWÄHLEN möchten.

Wie bereits kurz erwähnt, ist es sinnlos, "NULL AND NULL" für die Zwecke einer WHERE-Bedingung in NULL zu konvertieren, da NULL / Unknown nicht wirklich boolesch ist. In der Tat ist NULL wirklich FALSCH, da nicht festgestellt werden kann, dass es WAHR ist. Auch dies könnte für Ihre Zwecke in einer SELECT-Anweisung anders sein, weshalb die CASE-Anweisung auch hier die einzige Wahl ist.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X