Letzten Monat habe ich einen Hintergrund zu Tabellenausdrücken in T-SQL bereitgestellt. Ich habe den Kontext aus der relationalen Theorie und dem SQL-Standard erklärt. Ich habe erklärt, wie eine Tabelle in SQL ein Versuch ist, eine Relation aus der relationalen Theorie darzustellen. Ich habe auch erklärt, dass ein relationaler Ausdruck ein Ausdruck ist, der auf einer oder mehreren Beziehungen als Eingaben operiert und zu einer Beziehung führt. In ähnlicher Weise ist ein Tabellenausdruck in SQL ein Ausdruck, der auf einer oder mehreren Eingabetabellen operiert und zu einer Tabelle führt. Der Ausdruck kann eine Abfrage sein, muss es aber nicht. Der Ausdruck kann beispielsweise ein Tabellenwertkonstruktor sein, wie ich später in diesem Artikel erläutern werde. Ich habe auch erklärt, dass ich mich in dieser Reihe auf vier spezifische Arten benannter Tabellenausdrücke konzentriere, die T-SQL unterstützt:abgeleitete Tabellen, allgemeine Tabellenausdrücke (CTEs), Ansichten und Inline-Tabellenwertfunktionen (TVFs).
Wenn Sie schon länger mit T-SQL arbeiten, sind Sie wahrscheinlich auf einige Fälle gestoßen, in denen Sie entweder Tabellenausdrücke verwenden mussten oder es im Vergleich zu alternativen Lösungen, die diese nicht verwenden, irgendwie bequemer waren. Hier sind nur einige Beispiele für Anwendungsfälle, die Ihnen in den Sinn kommen:
- Erstellen Sie eine modulare Lösung, indem Sie komplexe Aufgaben in Schritte unterteilen, die jeweils durch einen anderen Tabellenausdruck dargestellt werden.
- Mischen von Ergebnissen gruppierter Abfragen und Details, falls Sie sich entscheiden, keine Fensterfunktionen für diesen Zweck zu verwenden.
- Logische Abfrageverarbeitung verarbeitet Abfrageklauseln in der folgenden Reihenfolge:FROM>WHERE>GROUP BY>HAVING>SELECT>ORDER BY. Folglich sind auf derselben Verschachtelungsebene Spaltenaliase, die Sie in der SELECT-Klausel definieren, nur für die ORDER BY-Klausel verfügbar. Sie stehen den übrigen Abfrageklauseln nicht zur Verfügung. Mit Tabellenausdrücken können Sie Aliase, die Sie in einer inneren Abfrage definieren, in jeder Klausel der äußeren Abfrage wiederverwenden und auf diese Weise die Wiederholung langer/komplexer Ausdrücke vermeiden.
- Fensterfunktionen können nur in den SELECT- und ORDER BY-Klauseln einer Abfrage vorkommen. Bei Tabellenausdrücken können Sie einem Ausdruck basierend auf einer Fensterfunktion einen Alias zuweisen und diesen Alias dann in einer Abfrage für den Tabellenausdruck verwenden.
- Ein PIVOT-Operator umfasst drei Elemente:Gruppierung, Verbreitung und Aggregation. Dieser Operator identifiziert das Gruppierungselement implizit durch Eliminierung. Mithilfe eines Tabellenausdrucks können Sie genau die drei Elemente projizieren, die beteiligt sein sollen, und die äußere Abfrage den Tabellenausdruck als Eingabetabelle des PIVOT-Operators verwenden lassen, wodurch gesteuert wird, welches Element das Gruppierungselement ist.
- Modifikationen mit TOP unterstützen keine ORDER BY-Klausel. Sie können indirekt steuern, welche Zeilen ausgewählt werden, indem Sie einen Tabellenausdruck basierend auf einer SELECT-Abfrage mit dem TOP- oder OFFSET-FETCH-Filter und einer ORDER BY-Klausel definieren und die Änderung auf den Tabellenausdruck anwenden.
Dies ist bei weitem keine vollständige Liste. Ich werde einige der oben genannten Anwendungsfälle und andere in dieser Serie demonstrieren. Ich wollte hier nur einige Anwendungsfälle erwähnen, um zu veranschaulichen, wie wichtig Tabellenausdrücke in unserem T-SQL-Code sind und warum es sich lohnt, in das Verständnis ihrer Grundlagen zu investieren.
Im Artikel dieses Monats konzentriere ich mich speziell auf die logische Behandlung abgeleiteter Tabellen.
In meinen Beispielen verwende ich eine Beispieldatenbank namens TSQLV5. Sie finden das Skript, das es erstellt und füllt, hier und sein ER-Diagramm hier.
Abgeleitete Tabellen
Der Begriff abgeleitete Tabelle wird in SQL und T-SQL mit mehr als einer Bedeutung verwendet. Daher möchte ich zunächst klarstellen, auf welche ich mich in diesem Artikel beziehe. Ich beziehe mich auf ein bestimmtes Sprachkonstrukt, das Sie normalerweise, aber nicht nur, in der FROM-Klausel einer äußeren Abfrage definieren. Ich werde die Syntax für dieses Konstrukt in Kürze bereitstellen.
Die allgemeinere Verwendung des Begriffs abgeleitete Tabelle in SQL ist das Gegenstück zu einer abgeleiteten Relation aus der relationalen Theorie. Eine abgeleitete Relation ist eine Ergebnisrelation, die von einer oder mehreren eingegebenen Basisrelationen abgeleitet wird, indem relationale Operatoren aus der relationalen Algebra wie Projektion, Schnittmenge und andere auf diese Basisrelationen angewendet werden. In ähnlicher Weise ist eine abgeleitete Tabelle in SQL im allgemeinen Sinne eine Ergebnistabelle, die von einer oder mehreren Basistabellen abgeleitet wird, indem Ausdrücke anhand dieser Eingabe-Basistabellen ausgewertet werden.
Nebenbei habe ich überprüft, wie der SQL-Standard eine Basistabelle definiert, und es tat mir sofort leid, dass ich mich darum gekümmert habe.
4.15.2 BasistabellenEine Basistabelle ist entweder eine persistente Basistabelle oder eine temporäre Tabelle.
Eine persistente Basistabelle ist entweder eine reguläre persistente Basistabelle oder eine vom System versionierte Tabelle.
Eine reguläre Basistabelle ist entweder eine reguläre persistente Basistabelle oder eine temporäre Tabelle.“
Hier hinzugefügt ohne weitere Kommentare…
In T-SQL können Sie eine Basistabelle mit einer CREATE TABLE-Anweisung erstellen, aber es gibt andere Optionen, z. B. SELECT INTO und DECLARE @T AS TABLE.
Hier ist die Standarddefinition für abgeleitete Tabellen im allgemeinen Sinne:
4.15.3 Abgeleitete Tabellen
Eine abgeleitete Tabelle ist eine Tabelle, die direkt oder indirekt von einer oder mehreren anderen Tabellen durch die Auswertung eines Ausdrucks abgeleitet wird, z. B. einer
Hier gibt es ein paar interessante Dinge über abgeleitete Tabellen im allgemeinen Sinne zu beachten. Einer hat mit dem Kommentar zur Bestellung zu tun. Dazu komme ich später im Artikel. Ein weiterer Grund ist, dass eine abgeleitete Tabelle in SQL ein gültiger eigenständiger Tabellenausdruck sein kann, aber nicht muss. Der folgende Ausdruck stellt beispielsweise eine abgeleitete Tabelle dar und ist wird auch als gültiger eigenständiger Tabellenausdruck betrachtet (Sie können ihn ausführen):
Umgekehrt stellt der folgende Ausdruck eine abgeleitete Tabelle dar, ist aber keine ein gültiger eigenständiger Tabellenausdruck:
T-SQL unterstützt eine Reihe von Tabellenoperatoren, die eine abgeleitete Tabelle ergeben, aber nicht als eigenständige Ausdrücke unterstützt werden. Das sind:JOIN, PIVOT, UNPIVOT und APPLY. Sie benötigen eine Klausel, in der sie operieren können (normalerweise FROM, aber auch die USING-Klausel der MERGE-Anweisung) und eine Host-Abfrage.
Von hier an verwende ich den Begriff abgeleitete Tabelle, um ein spezifischeres Sprachkonstrukt zu beschreiben, und nicht im oben beschriebenen allgemeinen Sinne.
Eine abgeleitete Tabelle kann als Teil einer äußeren SELECT-Anweisung in ihrer FROM-Klausel definiert werden. Es kann auch als Teil von DELETE- und UPDATE-Anweisungen in ihrer FROM-Klausel und als Teil einer MERGE-Anweisung in ihrer USING-Klausel definiert werden. Ich werde später in diesem Artikel weitere Details zur Syntax bereitstellen, wenn sie in Änderungsanweisungen verwendet wird.
Hier ist die Syntax für eine vereinfachte SELECT-Abfrage für eine abgeleitete Tabelle:
Die abgeleitete Tabellendefinition erscheint dort, wo normalerweise eine Basistabelle erscheinen kann, in der FROM-Klausel der äußeren Abfrage. Es kann eine Eingabe für einen Tabellenoperator wie JOIN, APPLY, PIVOT und UNPIVOT sein. Wenn er als richtige Eingabe für einen APPLY-Operator verwendet wird, darf der Teil
Die äußere Anweisung kann alle üblichen Abfrageelemente enthalten. Im Fall einer SELECT-Anweisung:WHERE, GROUP BY, HAVING, ORDER BY und wie erwähnt Tabellenoperatoren in der FROM-Klausel.
Hier ist ein Beispiel für eine einfache Abfrage einer abgeleiteten Tabelle, die Kunden in den USA darstellt:
Diese Abfrage generiert die folgende Ausgabe:
In einer Anweisung, die eine abgeleitete Tabellendefinition beinhaltet, sind drei Hauptteile zu identifizieren:
Der Tabellenausdruck soll eine Tabelle darstellen und muss daher bestimmte Anforderungen erfüllen, die eine normale Abfrage nicht unbedingt erfüllen muss. Ich werde die Details in Kürze im Abschnitt „Ein Tabellenausdruck ist eine Tabelle“ erläutern.
Wie für den Namen der abgeleiteten Zieltabelle; Eine gängige Annahme unter T-SQL-Entwicklern ist, dass es sich lediglich um einen Namen oder Alias handelt, den Sie der Zieltabelle zuweisen. Betrachten Sie in ähnlicher Weise die folgende Abfrage:
Auch hier wird allgemein angenommen, dass AS C nur eine Möglichkeit zum Umbenennen oder Aliasieren der Tabelle Customers für die Zwecke dieser Abfrage ist, beginnend mit dem logischen Abfrageverarbeitungsschritt, in dem der Name zugewiesen wird, und weiter. Aus Sicht der relationalen Theorie hat das, was C darstellt, jedoch eine tiefere Bedeutung. C ist eine sogenannte Bereichsvariable. C ist eine abgeleitete Beziehungsvariable, die sich über die Tupel in der Eingabebeziehungsvariablen Kunden erstreckt. Im obigen Beispiel reicht C über die Tupel in Customers und wertet das Prädikat Land =N'USA' aus. Tupel, für die das Prädikat als wahr ausgewertet wird, werden Teil der Ergebnisrelation C.
Mit dem Hintergrund, den ich bisher geliefert habe, sollte das, was ich als nächstes erklären werde, wenig überraschend sein. Der
Lassen Sie uns diese Anforderungen einzeln aufschlüsseln und die Relevanz sowohl für die relationale Theorie als auch für SQL erörtern.
Denken Sie daran, dass eine Relation eine Überschrift und einen Körper hat. Die Überschrift einer Relation ist eine Menge von Attributen (Spalten in SQL). Ein Attribut hat einen Namen und einen Typnamen und wird durch seinen Namen identifiziert. Eine Abfrage, die nicht als Tabellenausdruck verwendet wird, muss nicht unbedingt allen Zielspalten Namen zuweisen. Betrachten Sie die folgende Abfrage als Beispiel:
Diese Abfrage generiert die folgende Ausgabe:
Die Abfrageausgabe enthält eine anonyme Spalte, die sich aus der Verkettung der Standortattribute mithilfe der Funktion CONCAT_WS ergibt. (Diese Funktion wurde übrigens in SQL Server 2017 hinzugefügt. Wenn Sie also den Code in einer früheren Version ausführen, können Sie diese Berechnung gerne durch eine alternative Berechnung Ihrer Wahl ersetzen.) Diese Abfrage funktioniert daher nicht eine Tabelle zurückgeben, ganz zu schweigen von einer Relation. Daher ist es nicht zulässig, eine solche Abfrage als Tabellenausdruck/inneren Abfrageteil einer abgeleiteten Tabellendefinition zu verwenden.
Probieren Sie es aus:
Sie erhalten die folgende Fehlermeldung:
Fällt Ihnen nebenbei noch etwas Interessantes an der Fehlermeldung auf? Es beschwert sich über Spalte 4 und hebt den Unterschied zwischen Spalten in SQL und Attributen in der relationalen Theorie hervor.
Die Lösung besteht natürlich darin, sicherzustellen, dass Sie Spalten, die sich aus Berechnungen ergeben, explizit Namen zuweisen. T-SQL unterstützt eine ganze Reihe von Techniken zur Benennung von Spalten. Ich nenne zwei davon.
Sie können eine Inline-Benennungstechnik verwenden, bei der Sie den Zielspaltennamen nach der Berechnung und einer optionalen AS-Klausel zuweisen, wie in
Diese Abfrage generiert die folgende Ausgabe:
Mit dieser Technik ist es sehr einfach, beim Überprüfen des Codes festzustellen, welcher Zielspaltenname welchem Ausdruck zugewiesen ist. Außerdem müssen Sie nur Spalten benennen, die sonst noch keine Namen haben.
Sie können auch eine externere Spaltenbenennungstechnik verwenden, bei der Sie die Zielspaltennamen in Klammern direkt nach dem Namen der abgeleiteten Tabelle angeben, etwa so:
Bei dieser Technik müssen Sie jedoch Namen für alle Spalten auflisten – einschließlich derjenigen, die bereits Namen haben. Die Zuordnung der Zielspaltennamen erfolgt positionsbezogen von links nach rechts, d. h. der erste Zielspaltenname repräsentiert den ersten Ausdruck in der SELECT-Liste der inneren Abfrage; der Name der zweiten Zielspalte stellt den zweiten Ausdruck dar; und so weiter.
Beachten Sie, dass im Falle einer Inkonsistenz zwischen den inneren und äußeren Spaltennamen, beispielsweise aufgrund eines Fehlers im Code, der Gültigkeitsbereich der inneren Namen die innere Abfrage ist – oder genauer gesagt die innere Bereichsvariable (hier implizit HR.Employees AS Employees) – und der Gültigkeitsbereich der äußeren Namen ist die äußere Bereichsvariable (in unserem Fall D). Es ist ein bisschen mehr mit dem Scoping von Spaltennamen verbunden, was mit der logischen Abfrageverarbeitung zu tun hat, aber das ist ein Thema für spätere Diskussionen.
Das Potenzial für Fehler bei der externen Benennungssyntax lässt sich am besten anhand eines Beispiels erklären.
Untersuchen Sie die Ausgabe der vorherigen Abfrage mit allen Mitarbeitern aus der HR.Employees-Tabelle. Betrachten Sie dann die folgende Abfrage und versuchen Sie, bevor Sie sie ausführen, herauszufinden, welche Mitarbeiter Sie im Ergebnis erwarten:
Wenn Sie erwarten, dass die Abfrage einen leeren Satz für die angegebenen Beispieldaten zurückgibt, da es derzeit keine Mitarbeiter mit sowohl einem Nachnamen als auch einem Vornamen gibt, die mit dem Buchstaben D beginnen, übersehen Sie den Fehler im Code.
Führen Sie nun die Abfrage aus und untersuchen Sie die tatsächliche Ausgabe:
Was ist passiert?
Die innere Abfrage gibt firstname als zweite Spalte und lastname als dritte Spalte in der SELECT-Liste an. Der Code, der die Zielspaltennamen der abgeleiteten Tabelle in der äußeren Abfrage zuweist, gibt den zweiten Nachnamen und den dritten Vornamen an. Der Code nennt Vorname als Nachname und Nachname als Vorname in der Bereichsvariablen D. Tatsächlich filtern Sie nur Mitarbeiter, deren Nachname mit dem Buchstaben D beginnt. Sie filtern keine Mitarbeiter, die sowohl einen Nachnamen als auch einen Vornamen haben, die beginnen mit dem Buchstaben D.
Die Inline-Aliasing-Syntax ist nicht anfällig für solche Fehler. Zum einen verwenden Sie normalerweise kein Alias für eine Spalte, die bereits einen Namen hat, mit dem Sie zufrieden sind. Zweitens:Selbst wenn Sie einer Spalte, die bereits einen Namen hat, einen anderen Alias zuweisen möchten, ist es nicht sehr wahrscheinlich, dass Sie mit der Syntax
Offensichtlich nicht sehr wahrscheinlich.
Zurück zu der Tatsache, dass die Überschrift einer Relation eine Menge von Attributen ist, und da ein Attribut durch seinen Namen identifiziert wird, müssen Attributnamen für dieselbe Relation eindeutig sein. In einer bestimmten Abfrage können Sie immer auf ein Attribut verweisen, indem Sie einen zweiteiligen Namen mit dem Bereichsvariablennamen als Qualifizierer verwenden, wie in
Betrachten Sie die folgende eigenständige Abfrage als Beispiel:
Diese Abfrage schlägt nicht mit einem doppelten Spaltennamenfehler fehl, da eine custid-Spalte tatsächlich C.custid und die andere O.custid innerhalb des Bereichs der aktuellen Abfrage heißt. Diese Abfrage generiert die folgende Ausgabe:
Versuchen Sie jedoch, diese Abfrage wie folgt als Tabellenausdruck in der Definition einer abgeleiteten Tabelle namens CO zu verwenden:
Was die äußere Abfrage betrifft, haben Sie eine Bereichsvariable mit dem Namen CO, und der Gültigkeitsbereich aller Spaltennamen in der äußeren Abfrage ist diese Bereichsvariable. Die Namen aller Spalten in einer bestimmten Bereichsvariablen (denken Sie daran, dass eine Bereichsvariable eine Beziehungsvariable ist) müssen eindeutig sein. Daher erhalten Sie die folgende Fehlermeldung:
Die Lösung besteht natürlich darin, den beiden custid-Spalten unterschiedliche Spaltennamen zuzuweisen, was die Bereichsvariable CO anbelangt, etwa so:
Diese Abfrage generiert die folgende Ausgabe:
Wenn Sie bewährte Verfahren befolgen, führen Sie die Spaltennamen explizit in der SELECT-Liste der äußersten Abfrage auf. Da es sich nur um eine Bereichsvariable handelt, müssen Sie den zweiteiligen Namen nicht für die äußeren Spaltenreferenzen verwenden. Wenn Sie den zweiteiligen Namen verwenden möchten, stellen Sie den Spaltennamen den Variablennamen CO aus dem äußeren Bereich voran, etwa so:
Ich habe ziemlich viel über benannte Tabellenausdrücke und die Reihenfolge zu sagen – genug für einen eigenen Artikel –, also werde ich diesem Thema einen zukünftigen Artikel widmen. Trotzdem wollte ich das Thema hier kurz ansprechen, da es so wichtig ist. Erinnern Sie sich, dass der Rumpf einer Relation eine Menge von Tupeln ist, und ähnlich ist der Rumpf einer Tabelle eine Menge von Zeilen. Eine Menge hat keine Ordnung. Dennoch lässt SQL zu, dass die äußerste Abfrage eine ORDER BY-Klausel hat, die eine Präsentationssortierbedeutung hat, wie die folgende Abfrage demonstriert:
Was Sie jedoch verstehen müssen, ist, dass diese Abfrage als Ergebnis keine Beziehung zurückgibt. Selbst aus der Sicht von SQL gibt die Abfrage keine Tabelle als Ergebnis zurück und ist es daher nicht als Tabellenausdruck betrachtet. Folglich ist es unzulässig, eine solche Abfrage als Tabellenausdrucksteil einer abgeleiteten Tabellendefinition zu verwenden.
Versuchen Sie, den folgenden Code auszuführen:
Sie erhalten die folgende Fehlermeldung:
Ich werde das es sei denn ansprechen Teil der Fehlermeldung in Kürze.
Wenn Sie möchten, dass die äußerste Abfrage ein geordnetes Ergebnis zurückgibt, müssen Sie die ORDER BY-Klausel in der äußersten Abfrage wie folgt angeben:
Wie für das es sei denn Teil der Fehlermeldung; T-SQL unterstützt den proprietären TOP-Filter sowie den Standard-OFFSET-FETCH-Filter. Beide Filter verlassen sich auf eine ORDER BY-Klausel im selben Abfragebereich, um für sie zu definieren, welche obersten Zeilen gefiltert werden sollen. Dies ist leider das Ergebnis einer Falle im Design dieser Funktionen, die die Präsentationsreihenfolge nicht von der Filterreihenfolge trennt. Wie dem auch sei, sowohl Microsoft mit seinem TOP-Filter als auch der Standard mit seinem OFFSET-FETCH-Filter erlauben die Angabe einer ORDER BY-Klausel in der inneren Abfrage, solange sie auch den TOP- bzw. OFFSET-FETCH-Filter angibt. Diese Abfrage ist also gültig, zum Beispiel:
Als ich diese Abfrage auf meinem System ausführte, generierte sie die folgende Ausgabe:
Es ist jedoch wichtig zu betonen, dass der einzige Grund, warum die ORDER BY-Klausel in der inneren Abfrage zulässig ist, darin besteht, den TOP-Filter zu unterstützen. Das ist die einzige Garantie, die Sie bei der Bestellung erhalten. Da die äußere Abfrage ebenfalls keine ORDER BY-Klausel hat, erhalten Sie trotz des beobachteten Verhaltens keine Garantie für eine bestimmte Präsentationsreihenfolge von dieser Abfrage. Das ist sowohl in T-SQL als auch im Standard der Fall. Hier ist ein Zitat aus dem Standard, der sich mit diesem Teil befasst:
Wie bereits erwähnt, gibt es noch viel mehr über Tabellenausdrücke und die Reihenfolge zu sagen, was ich in einem zukünftigen Artikel tun werde. Ich werde auch Beispiele liefern, die zeigen, wie das Fehlen der ORDER BY-Klausel in der äußeren Abfrage bedeutet, dass Sie keine Garantien für die Reihenfolge der Präsentation erhalten.
Ein Tabellenausdruck, z. B. eine innere Abfrage in einer abgeleiteten Tabellendefinition, ist also eine Tabelle. Ebenso ist eine abgeleitete Tabelle (im spezifischen Sinne) selbst auch eine Tabelle. Es ist kein Basistisch, aber es ist trotzdem ein Tisch. Gleiches gilt für CTEs, Views und Inline-TVFs. Sie sind keine Basistabellen, eher abgeleitete (im allgemeineren Sinne), aber dennoch Tabellen.
Abgeleitete Tabellen haben zwei Hauptmängel in ihrem Design. Beides hat damit zu tun, dass die abgeleitete Tabelle in der FROM-Klausel der äußeren Abfrage definiert ist.
Ein Designfehler hat mit der Tatsache zu tun, dass Sie, wenn Sie eine abgeleitete Tabelle aus einer äußeren Abfrage abfragen müssen und diese Abfrage wiederum als Tabellenausdruck in einer anderen abgeleiteten Tabellendefinition verwenden müssen, diese Abfragen der abgeleiteten Tabelle am Ende verschachteln. Bei der Datenverarbeitung führt die explizite Verschachtelung von Code mit mehreren Verschachtelungsebenen zu komplexem Code, der schwer zu warten ist.
Hier ist ein sehr einfaches Beispiel, das dies demonstriert:
Dieser Code gibt Bestelljahre und die Anzahl der Kunden zurück, die in jedem Jahr Bestellungen aufgegeben haben, nur für Jahre, in denen die Anzahl der Kunden, die Bestellungen aufgegeben haben, größer als 70 war.
Die Hauptmotivation für die Verwendung von Tabellenausdrücken besteht hier darin, mehrfach auf einen Spaltenalias verweisen zu können. Die innerste Abfrage, die als Tabellenausdruck für die abgeleitete Tabelle D1 verwendet wird, fragt die Tabelle Sales.Orders ab und weist dem Ausdruck YEAR(orderdate) den Spaltennamen orderyear zu und gibt auch die Spalte custid zurück. Die Abfrage für D1 gruppiert die Zeilen von D1 nach Bestelljahr und gibt Bestelljahr sowie die eindeutige Anzahl von Kunden zurück, die im betreffenden Jahr Bestellungen aufgegeben haben, die als numcusts bezeichnet werden. Der Code definiert basierend auf dieser Abfrage eine abgeleitete Tabelle namens D2. Die äußerste Abfrage fragt dann D2 ab und filtert nur Jahre, in denen die Anzahl der Kunden, die Bestellungen aufgegeben haben, größer als 70 war.
Ein Versuch, diesen Code zu überprüfen oder bei Problemen Fehler zu beheben, ist aufgrund der mehreren Verschachtelungsebenen schwierig. Anstatt den Code auf natürlichere Weise von oben nach unten zu überprüfen, müssen Sie ihn beginnend mit der innersten Einheit analysieren und allmählich nach außen gehen, da dies praktischer ist.
Der springende Punkt bei der Verwendung abgeleiteter Tabellen in diesem Beispiel war die Vereinfachung des Codes, indem die Notwendigkeit vermieden wird, Ausdrücke zu wiederholen. Aber ich bin mir nicht sicher, ob diese Lösung dieses Ziel erreicht. In diesem Fall ist es wahrscheinlich besser, einige Ausdrücke zu wiederholen, um die Notwendigkeit zu vermeiden, abgeleitete Tabellen insgesamt zu verwenden, etwa so:
Denken Sie daran, dass ich hier ein sehr einfaches Beispiel zur Veranschaulichung zeige. Stellen Sie sich Produktionscode mit mehr Verschachtelungsebenen und längerem, aufwändigerem Code vor, und Sie können sehen, wie die Wartung wesentlich komplizierter wird.
Ein weiterer Fehler im Design abgeleiteter Tabellen hat mit Fällen zu tun, in denen Sie mit mehreren Instanzen derselben abgeleiteten Tabelle interagieren müssen. Betrachten Sie die folgende Abfrage als Beispiel:
Dieser Code berechnet die Anzahl der in jedem Jahr bearbeiteten Bestellungen sowie die Differenz zum Vorjahr. Ignorieren Sie die Tatsache, dass es einfachere Möglichkeiten gibt, dieselbe Aufgabe mit Fensterfunktionen zu lösen – ich verwende diesen Code, um einen bestimmten Punkt zu veranschaulichen, daher sind die Aufgabe selbst und die verschiedenen Möglichkeiten, sie zu lösen, nicht von Bedeutung.
Ein Join ist ein Tabellenoperator, der seine beiden Eingaben als eine Menge behandelt – was bedeutet, dass es keine Reihenfolge zwischen ihnen gibt. Sie werden als linke und rechte Eingaben bezeichnet, sodass Sie eine davon (oder beide) als beibehaltene Tabelle in einem äußeren Join markieren können, aber dennoch gibt es keine erste und keine zweite unter ihnen. Sie dürfen abgeleitete Tabellen als Join-Eingaben verwenden, aber auf den Bereichsvariablennamen, den Sie der linken Eingabe zuweisen, kann in der Definition der rechten Eingabe nicht zugegriffen werden. Denn beide werden konzeptionell im selben logischen Schritt definiert, quasi zum selben Zeitpunkt. Folglich können Sie beim Verbinden von abgeleiteten Tabellen nicht zwei Bereichsvariablen basierend auf einem Tabellenausdruck definieren. Leider müssen Sie den Code wiederholen und zwei Bereichsvariablen basierend auf zwei identischen Kopien des Codes definieren. Dies erschwert natürlich die Wartbarkeit des Codes und erhöht die Wahrscheinlichkeit von Fehlern. Jede Änderung, die Sie an einem Tabellenausdruck vornehmen, muss auch auf den anderen angewendet werden.
Wie ich in einem zukünftigen Artikel erläutern werde, weisen CTEs in ihrem Design nicht diese beiden Fehler auf, die abgeleitete Tabellen aufweisen.
Mit einem Tabellenwertkonstruktor können Sie einen Tabellenwert basierend auf eigenständigen Skalarausdrücken konstruieren. Sie können eine solche Tabelle dann in einer äußeren Abfrage genauso verwenden wie eine abgeleitete Tabelle, die auf einer inneren Abfrage basiert. In einem zukünftigen Artikel bespreche ich lateral abgeleitete Tabellen und Korrelationen im Detail, und ich werde anspruchsvollere Formen von Tabellenwertkonstruktoren zeigen. In diesem Artikel konzentriere ich mich jedoch auf eine einfache Form, die ausschließlich auf in sich geschlossenen Skalarausdrücken basiert.
Die allgemeine Syntax für eine Abfrage eines Tabellenwertkonstruktors lautet wie folgt:. Ein
SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA'
T1 INNER JOIN T2
ON T1.keycol = T2.keycol
Syntax
FROM ( der abgeleiteten Tabelle Korrelationen zu Spalten aus einer äußeren Tabelle haben (mehr dazu in einem speziellen zukünftigen Artikel in der Serie). Andernfalls muss der Tabellenausdruck in sich abgeschlossen sein.
SELECT custid, companyname
FROM ( SELECT custid, companyname
FROM Sales.Customers
WHERE country = N'USA' ) AS UC;
custid companyname
------- ---------------
32 Customer YSIQX
36 Customer LVJSO
43 Customer UISOJ
45 Customer QXPPT
48 Customer DVFMB
55 Customer KZQZT
65 Customer NYUHS
71 Customer LCOUJ
75 Customer XOJYP
77 Customer LCYBZ
78 Customer NLTYP
82 Customer EYHKM
89 Customer YBQTI
SELECT custid, companyname
FROM Sales.Customers AS C
WHERE country = N'USA';
Ein Tabellenausdruck ist eine Tabelle
Alle Spalten müssen Namen haben
SELECT empid, firstname, lastname,
CONCAT_WS(N'/', country, region, city)
FROM HR.Employees;
empid firstname lastname (No column name)
------ ---------- ---------- -----------------
1 Sara Davis USA/WA/Seattle
2 Don Funk USA/WA/Tacoma
3 Judy Lew USA/WA/Kirkland
4 Yael Peled USA/WA/Redmond
5 Sven Mortensen UK/London
6 Paul Suurs UK/London
7 Russell King UK/London
8 Maria Cameron USA/WA/Seattle
9 Patricia Doyle UK/London
SELECT *
FROM ( SELECT empid, firstname, lastname,
CONCAT_WS(N'/', country, region, city)
FROM HR.Employees ) AS D;
Für Spalte 4 von „D“ wurde kein Spaltenname angegeben. < expression > [ AS ] < column name >
, etwa so:SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
CONCAT_WS(N'/', country, region, city) AS custlocation
FROM HR.Employees ) AS D;
empid firstname lastname custlocation
------ ---------- ---------- ----------------
1 Sara Davis USA/WA/Seattle
2 Don Funk USA/WA/Tacoma
3 Judy Lew USA/WA/Kirkland
4 Yael Peled USA/WA/Redmond
5 Sven Mortensen UK/London
6 Paul Suurs UK/London
7 Russell King UK/London
8 Maria Cameron USA/WA/Seattle
9 Patricia Doyle UK/London
SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
CONCAT_WS(N'/', country, region, city)
FROM HR.Employees ) AS D(empid, firstname, lastname, custlocation);
SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid, firstname, lastname,
CONCAT_WS(N'/', country, region, city)
FROM HR.Employees
WHERE lastname LIKE N'D%' ) AS D(empid, lastname, firstname, custlocation)
WHERE firstname LIKE N'D%';
empid firstname lastname custlocation
------ ---------- --------- ---------------
1 Davis Sara USA/WA/Seattle
9 Doyle Patricia UK/London
SELECT empid, firstname, lastname, custlocation
FROM ( SELECT empid AS empid, firstname AS lastname, lastname AS firstname,
CONCAT_WS(N'/', country, region, city) AS custlocation
FROM HR.Employees
WHERE lastname LIKE N'D%' ) AS D
WHERE firstname LIKE N'D%';
Alle Spaltennamen müssen eindeutig sein
SELECT C.custid, O.custid, O.orderid
FROM Sales.Customers AS C
LEFT OUTER JOIN Sales.Orders AS O
ON C.custid = O.custid;
custid custid orderid
----------- ----------- -----------
1 1 10643
1 1 10692
1 1 10702
1 1 10835
1 1 10952
1 1 11011
2 2 10308
2 2 10625
2 2 10759
2 2 10926
...
SELECT *
FROM ( SELECT C.custid, O.custid, O.orderid
FROM Sales.Customers AS C
LEFT OUTER JOIN Sales.Orders AS O
ON C.custid = O.custid ) AS CO;
Die Spalte „custid“ wurde mehrfach für „CO“ angegeben. SELECT *
FROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid
FROM Sales.Customers AS C
LEFT OUTER JOIN Sales.Orders AS O
ON C.custid = O.custid ) AS CO;
custcustid ordercustid orderid
----------- ----------- -----------
1 1 10643
1 1 10692
1 1 10702
1 1 10835
1 1 10952
1 1 11011
2 2 10308
2 2 10625
2 2 10759
2 2 10926
...
SELECT CO.custcustid, CO.ordercustid, CO.orderid
FROM ( SELECT C.custid AS custcustid, O.custid AS ordercustid, O.orderid
FROM Sales.Customers AS C
LEFT OUTER JOIN Sales.Orders AS O
ON C.custid = O.custid ) AS CO;
Keine Bestellung
SELECT orderid, val
FROM Sales.OrderValues
ORDER BY val DESC;
SELECT orderid, val
FROM ( SELECT orderid, val
FROM Sales.OrderValues
ORDER BY val DESC ) AS D;
Die ORDER BY-Klausel ist in Ansichten, Inline-Funktionen, abgeleiteten Tabellen, Unterabfragen und allgemeinen Tabellenausdrücken ungültig, es sei denn, TOP, OFFSET oder FOR XML ist ebenfalls angegeben. SELECT orderid, val
FROM ( SELECT orderid, val
FROM Sales.OrderValues ) AS D
ORDER BY val DESC;
SELECT orderid, val
FROM ( SELECT TOP (3) orderid, val
FROM Sales.OrderValues
ORDER BY val DESC ) AS D;
orderid val
-------- ---------
10865 16387.50
10981 15810.00
11030 12615.05
Designfehler
SELECT orderyear, numcusts
FROM ( SELECT orderyear, COUNT(DISTINCT custid) AS numcusts
FROM ( SELECT YEAR(orderdate) AS orderyear, custid
FROM Sales.Orders ) AS D1
GROUP BY orderyear ) AS D2
WHERE numcusts > 70;
SELECT YEAR(orderdate) AS orderyear, COUNT(DISTINCT custid) AS numcusts
FROM Sales.Orders
GROUP BY YEAR(orderdate)
HAVING COUNT(DISTINCT custid) > 70;
SELECT CUR.orderyear, CUR.numorders,
CUR.numorders - PRV.numorders AS diff
FROM ( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
FROM Sales.Orders
GROUP BY YEAR(orderdate) ) AS CUR
LEFT OUTER JOIN
( SELECT YEAR(orderdate) AS orderyear, COUNT(*) AS numorders
FROM Sales.Orders
GROUP BY YEAR(orderdate) ) AS PRV
ON CUR.orderyear = PRV.orderyear + 1;
Tabellenwertkonstruktor