Ich wollte nur ein Beispiel geben, um zu erklären, wie NULL
in Oracle zu "unerwarteten" Verhaltensweisen führen kann, aber ich habe etwas gefunden, was ich nicht erwartet hätte ...
Setup:
%Vor%Dies gibt, was ich erwartet habe:
%Vor%Wenn ich Tabellenaliasnamen entferne, bekomme ich:
%Vor%und das ist ziemlich überraschend für mich.
Ein Grund kann in den Ausführungsplänen für die zwei Abfragen gefunden werden; Mit Tabellenaliasen erstellt Oracle einen HASH JOIN und prüft dann auf T1.val = T2.val
:
Ohne Aliase wird zuerst ein Vorkommen der Tabelle nach Nicht-Null-Werten gefiltert, wodurch nur eine Zeile ausgewählt wird, und dann wird ein KARTESISCHER mit dem zweiten Vorkommen gebildet, was zwei Zeilen ergibt; auch wenn es korrekt ist, würde ich das Ergebnis eines Cartesian erwarten, aber ich habe keine Zeile mit DESCR = 'NULL VALUE'.
%Vor%Ist das irgendwie richtig / erwartet? Ist der Ergebniswert des Kartesischen nicht noch seltsamer als die Anzahl der zurückgegebenen Zeilen? Missverstehe ich die Pläne oder vermisse etwas, das so groß ist, dass ich es nicht sehen kann?
Nach Ссылка
using(val)
wird hier als ON tabnull.val=tabnull.val
übersetzt So
Um einen Plan zu erstellen, muss Oracle [virtuell] verschiedene Aliase für jedes JOIN-Mitglied zuweisen, sieht aber keinen Grund, den zweiten Alias an einer beliebigen Stelle in SELECT und ON zu verwenden. Also
%Vor%Planen
%Vor%BEARBEITEN : Ich sage unten, dass die Syntax illegal ist; bei weiterem Nachdenken, das ist BS von meiner Seite, weiß ich das nicht genau (ich kann nicht darauf hinweisen, wo in der Sprachdefinition Aliase für einen Self-Join benötigt werden). Ich glaube immer noch, dass die folgende Erklärung wahrscheinlich richtig ist, sei es für den "Bug" oder für das "undefinierte Verhalten", das ich unten erwähne.
*
Die Syntax ist illegal (Sie wussten das - Sie waren nur neugierig, was passieren würde, und wenn Sie die Ausgabe verstehen können). Ich stimme mit Jarl überein, dass Sie eine Fehlermeldung erhalten haben sollten. Klar, Oracle hat das nicht so programmiert.
Da dies keine gültige Syntax ist, kann das, was Sie sehen, nicht als Fehler bezeichnet werden (daher stimme ich Nicks Kommentar nicht zu). Das Verhalten ist "undefiniert" - wenn Sie eine Syntax verwenden, die nicht von der Oracle-Sprachdefinition unterstützt wird, können Sie irgendwelche verrückten Ergebnisse erhalten, für die Oracle keine Verantwortung übernimmt.
OK, gibt es eine Erklärung für das, was Sie sehen? Ich glaube, dass es in der Tat eine kartesische Verbindung ist, und keine Vereinigung, wie Nick vorgeschlagen hat.
Setzen wir uns in die Schuhe des Optimierers. Es sieht die erste Tabelle in der FROM-Liste, es scannt sie, soweit so gut.
Dann liest es die zweite Tabelle, und es hat eine Liste von Spalten wie folgt:
tabNULL.val, tabNULL.descr, tabNULL.val, tabNULL.descr
Die Join-Bedingung ist tabNULL.val = tabNULL.val
Der Optimierer ist dumm, er ist nicht schlau. Anders als Sie erkennen Sie an dieser Stelle nicht, dass tabNULL
für zwei verschiedene Inkarnationen des Tisches stehen soll. Er glaubt tabNULL.val
auf beiden Seiten der Gleichung ist der gleiche Wert und beide beziehen sich auf die erste "Inkarnation" der Tabelle. Der einzige Fall, in dem dies fehlschlägt, ist, wenn tabNULL.val
NULL ist, also REWRITE die Abfrage mit der Klausel wird tabNULL.val IS NOT NULL
.
Nur die erste Tabelle wird auf tabNULL.val IS NOT NULL
überprüft; der Optimierer "weiß" nicht tabNULL.val
erscheint wieder in der Liste und es kann eine UNTERSCHIEDLICHE Bedeutung haben! Dann passiert der Beitritt; An diesem Punkt gibt es keine anderen Bedingungen mehr, so dass BEIDE Zeilen in der zweiten Inkarnation der Tabelle Zeilen im Join für A, ONE CHAR
aus der ersten Tabelle erzeugen.
Dann wird in der Projektion wieder nur der FIRST tabNULL.val
gelesen und BEIDEN Spalten in der Ausgabe aufgefüllt. Sie fragen die Abfrage-Engine, den Wert tabNULL.val
zweimal zurückzugeben, und in Ihrem Kopf ist es von verschiedenen Orten, aber es gibt nur einen Speicherort mit der Bezeichnung tabNULL.val
, und es speichert, was aus der ersten Tabelle kam.
Natürlich wissen sehr wenige mit Sicherheit, was der Optimierer und die Abfrage-Engine tun, aber in diesem Fall denke ich, dass dies eine ziemlich sichere Schätzung ist.
Das Schlüsselwort USING
ist neu für mich, aber gemäß dem, was ich lese, ist es nur eine neue Art, die SQL-Join-Syntax zu vereinfachen. (Siehe Oracle USING Keyword )
select * from tabNull T1 inner join tabNull T2 using(val);
entspricht:
select * from tabNull T1 inner join tabNull T2 on T1.val = T2.val;
select * from tabNull inner join tabNull using(val);
entspricht:
select * from tabNull inner join tabNull on tabNull.val = tabNull.val;
Das Problem ist, dass in der zweiten Abfrage die Tabellennamen im Join tabNull.val = tabNull.val
nicht eindeutig sind
Dies ist eine schlechte Syntax, die zu einem Fehler geführt hätte, wenn die traditionelle Joinsyntax verwendet worden wäre
Meine beste Vermutung ist, dass Oracle ein vollständiges Cross-Produkt auf den beiden Tabellen durchgeführt hat (das hat alle Zeilen verdoppelt) und dann die Nullen eliminiert, weil USING
Equi-Joins verwenden muss (dh gleich " =
") und null
ist nichts gleich.
Tut mir leid, ich glaube nicht, dass das wirklich eine Antwort ist. Es ist meist nur ein Kommentar / eine Antwort in Ihrem Beitrag:
Ist der Ergebniswert des Kartesischen nicht noch seltsamer als die Anzahl der zurückgegebenen Zeilen?
Jeder Schritt eines Plans hat eine "Projektion", dh die Liste der Spalten / Ausdrücke, die vom Schritt ausgegeben werden. Was passiert, ist, dass die identischen Aliasnamen bewirken, dass Oracles Projektion zwei Spalten kombiniert, die in nur einer Spalte projiziert werden.
Dies ist einfacher zu sehen, wenn Sie in Ihrem Beispiel zwei separate Tabellen verwenden und ein Paar eindeutig benannte Spalten hinzufügen, um zu sehen, was gerade passiert:
%Vor%Wie Sie sehen können, war Ihre Theorie über den kartesischen Join korrekt.