Untersuchen der SELECT-Anweisung
Transact-SQL oder T-SQL ist ein Dialekt der SQL-Sprache (nach ANSI-Standard), die von Microsoft SQL-Produkten und -Diensten verwendet wird. Er ist ähnlich wie Standard-SQL. Unser Hauptschwerpunkt wird auf der SELECT-Anweisung liegen, bei der es mit Abstand die meisten Optionen und Varianten einer DML-Anweisung gibt.
Sehen wir uns zuerst an, wie eine SELECT-Anweisung verarbeitet wird. Die Reihenfolge, in der eine SELECT-Anweisung geschrieben wird, ist nicht die Reihenfolge, in der sie von der SQL Server-Datenbank-Engine ausgewertet und verarbeitet wird.
Betrachten Sie die folgende Abfrage:
SELECT OrderDate, COUNT(OrderID) AS Orders
FROM Sales.SalesOrder
WHERE Status = 'Shipped'
GROUP BY OrderDate
HAVING COUNT(OrderID) > 1
ORDER BY OrderDate DESC;
Die Abfrage besteht aus einer SELECT-Anweisung, die wiederum aus mehreren Klauseln besteht. Jede Klausel definiert einen bestimmten Vorgang, der auf die abgerufenen Daten angewendet werden muss. Bevor wir die Laufzeitreihenfolge von Vorgängen untersuchen, sehen wir uns kurz an, welche Aufgabe diese Abfrage hat, obwohl die Details der verschiedenen Klauseln in diesem Modul nicht behandelt werden.
Die SELECT-Klausel gibt die Spalte OrderDate und die Anzahl der OrderID-Werte zurück, denen der Name (oder der Alias) Orders zugewiesen wird:
SELECT OrderDate, COUNT(OrderID) AS Orders
Die FROM-Klausel identifiziert, welche Tabelle die Quelle der Zeilen für die Abfrage ist. In diesem Fall ist das die Tabelle Sales.SalesOrder:
FROM Sales.SalesOrder
Die WHERE-Klausel filtert Zeilen aus den Ergebnissen, wobei nur diejenigen Zeilen, die die angegebene Bedingung erfüllen, beibehalten werden. In diesem Fall sind das Aufträge mit dem Status „shipped“ (versendet):
WHERE Status = 'Shipped'
Die GROUP BY-Klausel nimmt die Zeilen, die die Filterbedingung erfüllt haben, und gruppiert sie nachOrderDate, sodass alle Zeilen mit demselben OrderDate als eine einzelne Gruppe betrachtet werden und für jede Gruppe eine einzige Zeile zurückgegeben wird:
GROUP BY OrderDate
Nachdem die Gruppen gebildet wurden, filtert die HAVING-Klausel die Gruppen basierend auf ihrem eigenen Prädikat. In die Ergebnisse werden nur Datumsangaben mit mehr als einer Bestellung einbezogen:
HAVING COUNT(OrderID) > 1
Für eine Vorschau dieser Abfrage ist die letzte Klausel „ORDER BY“. Sie sortiert die Ausgabe in absteigender Reihenfolge von OrderDate:
ORDER BY OrderDate DESC;
Nachdem Sie gesehen haben, welche Aufgabe die einzelnen Klauseln haben, sehen wir uns jetzt die Reihenfolge an, in der SQL Server sie tatsächlich auswertet:
- Die FROM-Klausel wird zuerst ausgewertet, um die Quellzeilen für den restlichen Teil der Anweisung bereitzustellen. Eine virtuelle Tabelle wird erstellt und an den nächsten Schritt übergeben.
- Anschließend wird die WHERE-Klausel ausgewertet. Sie filtert diejenigen Zeilen aus der Quelltabelle, die mit einem Prädikat übereinstimmen. Die gefilterte virtuelle Tabelle wird an den nächsten Schritt übergeben.
- GROUP BY ist die nächste Klausel. Sie organisiert die Zeilen in der virtuellen Tabelle entsprechend eindeutigen Werten, die in der „GROUP BY“-Liste enthalten sind. Eine neue virtuelle Tabelle mit der Gruppenliste wird erstellt und an den nächsten Schritt übergeben. Ab diesem Punkt im Arbeitsablauf wird von anderen Elementen möglicherweise nur auf Spalten in der „GROUP BY“-Liste oder auf Aggregatfunktionen verwiesen.
- Als Nächstes wird die HAVING-Klausel ausgewertet. Sie filtert ganze Gruppen basierend auf ihrem Prädikat heraus. Die in Schritt 3 erstellte virtuelle Tabelle wird gefiltert und an den nächsten Schritt übergeben.
- Schließlich wird die SELECT-Klausel ausgeführt. Sie bestimmt, welche Spalten in den Abfrageergebnissen angezeigt werden sollen. Weil die SELECT-Klausel nach den anderen Schritten ausgewertet wird, können alle dort erstellten Spaltenaliase (in unserem Beispiel, Orders) in der GROUP BY- oder HAVING-Klausel nicht verwendet werden.
- Die ORDER BY-Klausel ist die letzte auszuführende Klausel. Sie sortiert die Zeilen so, wie es durch die zugehörige Spaltenliste festgelegt wurde.
Wenn Sie dieses Wissen auf unsere Beispielabfrage anwenden möchten – hier ist die logische Reihenfolge zur Laufzeit der vorstehenden SELECT-Anweisung:
FROM Sales.SalesOrder
WHERE Status = 'Shipped'
GROUP BY OrderDate
HAVING COUNT(OrderID) > 1
SELECT OrderDate, COUNT(OrderID) AS Orders
ORDER BY OrderDate DESC;
Nicht alle diese möglichen Klauseln sind in jeder von Ihnen geschriebenen SELECT-Anweisung erforderlich. Die einzige erforderliche Klausel ist die SELECT-Klausel, die in einigen Fällen eigenständig verwendet werden kann. Normalerweise wird auch eine FROM-Klausel einbezogen, um die gerade abgefragte Tabelle zu identifizieren. Darüber hinaus gibt es in Transact-SQL andere Klauseln, die hinzugefügt werden können.
Wie Sie gesehen haben, schreiben Sie T-SQL-Abfragen nicht in derselben Reihenfolge, in der sie logisch ausgewertet werden. Die Laufzeitreihenfolge der Auswertung bestimmt, welche Daten für welche Klauseln zur Verfügung stehen. Eine Klausel kann nämlich nur auf die Informationen zugreifen, die über eine bereits verarbeitete Klausel bereits verfügbar gemacht wurden. Aus diesem Grund ist es wichtig, beim Schreiben von Abfragen die echte logische Verarbeitungsreihenfolge zu verstehen.
Auswählen aller Spalten
Die SELECT-Klausel wird oft als SELECT-Liste bezeichnet, weil sie die Werte auflistet, die in den Ergebnissen der Abfrage zurückgegeben werden sollen.
Die einfachste Form einer SELECT-Klausel ist die Verwendung des Sternchens (*) zum Zurückgeben aller Spalten. Bei Verwendung in T-SQL-Abfragen wird dieses Zeichen als Stern bezeichnet. Obwohl SELECT * für einen Schnelltest geeignet ist, sollten Sie diese Option in der Produktionsarbeit aus den folgenden Gründen nicht verwenden:
- Änderungen an der Tabelle, die Spalten hinzufügen oder neu anordnen, werden in den Abfrageergebnissen widergespiegelt. Das kann zu einer unerwarteten Ausgabe bei Anwendungen oder Berichten führen, die diese Abfrage verwenden.
- Die Rückgabe von nicht benötigten Daten kann Ihre Abfragen verlangsamen und zu Leistungsproblemen führen, wenn die Quelltabelle eine große Anzahl von Zeilen enthält.
Im folgenden Beispiel werden beispielsweise alle Spalten aus der (hypothetischen) Tabelle Production.Product abgerufen.
SELECT * FROM Production.Product;
Das Ergebnis dieser Abfrage ist ein Rowset, das alle Spalten für alle Zeilen der Tabelle enthält, die ungefähr so aussehen könnten:
ProductID
Name
ProductNum
Color
StandardCost
ListPrice
Size
Weight
ProductCatID
680
HL Road Frame - Black, 58
FR-R92B-58
Schwarz
1.059,31
1.431,5
58
1016.04
18
706
HL Road Frame - Red, 58
FR-R92R-58
Red
1.059,31
1.431,5
58
1016.04
18
707
Sport-100 Helmet, Red
HL-U509-R
Red
13.0863
34.99
35
708
Sport-100 Helmet, Black
HL-U509
Schwarz
13.0863
34.99
35
...
...
...
...
...
...
...
...
...
Auswählen bestimmter Spalten
Mithilfe einer expliziten Spaltenliste können Sie genau steuern, welche Spalten in welcher Reihenfolge zurückgegeben werden. Jede Spalte im Ergebnis hat den Namen der Spalte als Kopfzeile.
Sehen Sie sich beispielsweise die folgende Abfrage an, die wieder die hypothetische Tabelle Production.Product verwendet.
SELECT ProductID, Name, ListPrice, StandardCost
FROM Production.Product;
Dieses Mal enthalten die Ergebnisse nur die angegebenen Spalten:
ProductID
Name
ListPrice
StandardCost
680
HL Road Frame - Black, 58
1.431,5
1.059,31
706
HL Road Frame - Red, 58
1.431,5
1.059,31
707
Sport-100 Helmet, Red
34.99
13.0863
708
Sport-100 Helmet, Black
34.99
13.0863
...
...
...
...
Auswählen von Ausdrücken
Zusätzlich zum Abrufen von Spalten, die in der angegebenen Tabelle gespeichert sind, kann eine SELECT-Klausel Berechnungen und Bearbeitungen durchführen, bei denen Operatoren zum Kombinieren von Spalten und Werten oder von mehreren Spalten verwendet werden. Das Ergebnis der Berechnung oder Bearbeitung muss ein einwertiges (skalares) Ergebnis sein, das im Ergebnis als separate Spalte angezeigt wird.
Die folgende Abfrage beispielsweise enthält zwei Ausdrücke:
SELECT ProductID,
Name + '(' + ProductNumber + ')',
ListPrice - StandardCost
FROM Production.Product;
Die Ergebnisse dieser Abfrage könnten ungefähr so aussehen:
ProductID
680
HL Road Frame – Black, 58(FR-R92B-58)
372,19
706
HL Road Frame – Red, 58(FR-R92R-58)
372,19
707
Sport-100 Helmet, Red(HL-U509-R)
21.9037
708
Sport-100 Helmet, Black(HL-U509)
21.9037
...
...
...
Bei diesen Ergebnissen müssen einige interessante Aspekte beachtet werden:
- Die von den beiden Ausdrücken zurückgegebenen Spalten haben keine Spaltennamen. Je nachdem, mit welchem Tool Sie Ihre Abfrage übermitteln, könnte ein fehlender Spaltenname durch eine leere Spaltenüberschrift, einen literalen Indikator „kein Spaltenname“ oder einen Standardnamen wie column1 angegeben werden. Später in diesem Abschnitt werden Sie sehen, wie Sie in der Abfrage einen Alias für den Spaltennamen angeben.
- Im ersten Ausdruck wird der Operator + verwendet, um (zeichenbasierte) Zeichenfolgenwerte zu verketten, während im zweiten Ausdruck der Operator - verwendet wird, um einen numerischen Wert von einem anderen zu subtrahieren. Wenn der Operator + bei numerischen Werten verwendet wird, führt er eine Addition aus. Natürlich ist es wichtig, die Datentypen der Spalten zu verstehen, die Sie in Ausdrücke einbeziehen. Datentypen werden im nächsten Abschnitt erläutert.
Angeben von Spaltenaliasen
Sie können für jede von der SELECT-Abfrage zurückgegebene Spalte einen Alias angeben – entweder als Alternative zum Namen der Quellspalte oder zum Zuweisen eines Namens zur Ausgabe eines Ausdrucks.
Hier ist beispielsweise die gleiche Abfrage wie zuvor, aber mit Aliasen, die für die einzelnen Spalten angegeben wurden:
SELECT ProductID AS ID,
Name + '(' + ProductNumber + ')' AS ProductName,
ListPrice - StandardCost AS Markup
FROM Production.Product;
Die Ergebnisse aus dieser Abfrage enthalten die angegebenen Spaltennamen:
ID
ProductName
Markup
680
HL Road Frame – Black, 58(FR-R92B-58)
372,19
706
HL Road Frame – Red, 58(FR-R92R-58)
372,19
707
Sport-100 Helmet, Red(HL-U509-R)
21.9037
708
Sport-100 Helmet, Black(HL-U509)
21.9037
...
...
...
Hinweis
Das Schlüsselwort „AS“ ist optional, wenn ein Alias angegeben wird. Es hat sich aber bewährt, dieses Schlüsselwort zur Verdeutlichung mit einzubeziehen.
Formatieren von Abfragen
Möglicherweise stellen Sie anhand der Beispiele in diesem Abschnitt fest, dass Sie beim Formatieren Ihres Abfragecodes flexibel sein können. So können Sie beispielsweise jede Klausel (oder die ganze Abfrage) in eine einzige Zeile schreiben oder sie über mehrere Zeilen aufteilen. In den meisten Datenbanksystemen wird die Groß-/Kleinschreibung nicht beachtet, und einige Elemente der T-SQL-Sprache sind optional (einschließlich des Schlüsselworts „AS“, wie bereits erwähnt, und sogar des Semikolons am Ende einer Anweisung).
Beachten Sie die folgenden Richtlinien, damit Ihr T-SQL-Code einfach lesbar (und deshalb einfacher zu verstehen und zu debuggen!) ist:
- Schreiben Sie T-SQL-Schlüsselwörter wie „SELECT“, „FROM“, „AS“ usw. in Großbuchstaben. Die Großschreibung von Schlüsselwörtern ist eine häufig verwendete Konvention, dank derer jede Klausel einer komplexen Anweisung einfacher gefunden werden kann.
- Beginnen Sie für jede Hauptklausel einer Anweisung eine neue Zeile.
- Wenn die „SELECT“-Liste mehr als ein paar Spalten, Ausdrücke oder Aliase enthält, sollten Sie jede Spalte in einer eigenen Zeile auflisten.
- Ziehen Sie Zeilen mit Unterklauseln oder Spalten ein, um zu verdeutlichen, welcher Code zu der jeweiligen Hauptklausel gehört.