Überblick
SQL und Software-Programmiersprachen folgen unterschiedlichen Paradigmen. Software wird als Folge von Einzelanweisungen erstellt. SQL hingegen ist darauf ausgelegt, viele Arbeitsschritte in einer einzigen Anweisung auszuführen. Ein einziges SQL-Statement erstreckt sich im Gegensatz zu einer Software-Anweisung manchmal über hunderte Zeilen.
Integrierte Entwicklungsumgebungen wie Microsoft Visual Studio unterstützen bei der Software-Entwicklung sehr gut mit automatischer Quellcode-Formatierung. Anders sieht es bei der Entwicklung von SQL-Artefakten im SQL Server Management Studio (SSMS) aus. SSMS unterstützt den Entwickler nur mäßig bis gar nicht.
Hieraus ergibt sich eine besondere Herausforderung ein gutes SQL-Statement zu schreiben.
SQL stellt einen reichen Wortschatz zur Verfügung. Der Kern von SQL ist jedoch die Abfrage von Daten. Dafür steht zumindest die Abkürzung SQL: Structured Query Language. Als Synonym hierfür kann das SELECT Statement angesehen werden. In diesem Artikel habe ich einige Best Practice Regeln für die Strukturierung und Formatierung eines SELECT-Statements zusammengetragen. Natürlich lassen sich die hier genannten Regeln ohne weiteres auch auf ein INSERT-, UPDATE- oder DELETE-Statement anwenden, genauso aber auch auf die Schlüsselworte für die Ablaufsteuerung von Statements: IF…ELSE, TRY…CATCH, WHILE, etc.
Sämtliche Best Practices haben gemein, dass gleichartige Elemente nach Möglichkeit linksbündig ausgerichtet und einheitlich einzurücken sind. Durch die Einrückung wird ein Statement strukturiert. Durch eine einheitliche Ausrichtung gleichartiger Sprachelemente erhält das Statement einen tabellenartigen Aufbau. Beides dient einer schnellen visuellen Erfassung und Navigation auch innerhalb komplexer Statements. Natürlich ist es nicht möglich, beiden Forderungen immer zu entsprechen.
Wenn sich alle Entwickler eines Mehrentwicklerprojektes auf eine einheitliche Notation einigen können, wird jeder Entwickler und werden vor allem neue Team-Mitglieder dieses sehr schnell schätzen lernen. Folgende Aspekte der Strukturierung von SQL Statements werden hier betrachtet:
- Einrückung
- Grundlegende Sprachelemente
- SELECT Feldliste
- WHERE Klausel
- FROM Klausel
- GROUP BY, HAVING, ORDER BY Klauseln
Allgemeines zur Einrückung
Unter dem Aspekt der Lesbarkeit und schnellen Erfassung der Kapitel ist diese eingerückte Version eines Inhaltsverzeichnisses im Vergleich zu der nachfolgenden nicht eingerückten Variante informativer und intuitiver:
1. Kapitel der ersten Ebene 1.1. Kapitel der zweiten Ebene 1.1.1. Kapitel der dritten Ebene 1.1.2. Kapitel der dritten Ebene 1.2. Kapitel der zweiten Ebene 1.2.1. Kapitel der dritten Ebene 1.2.2. Kapitel der dritten Ebene 1.2.3. Kapitel der dritten Ebene 1.2.4. Kapitel der dritten Ebene 1.3. Kapitel der zweiten Ebene 1.3.1. Kapitel der dritten Ebene 1.3.2. Kapitel der dritten Ebene 1.3.3. Kapitel der dritten Ebene 2. Kapitel der ersten Ebene 2.1. Kapitel der zweiten Ebene 2.1.1. Kapitel der dritten Ebene 2.1.2. Kapitel der dritten Ebene 2.2. Kapitel der zweiten Ebene 2.2.1. Kapitel der dritten Ebene 2.2.2. Kapitel der dritten Ebene
Zum Vergleich ein Inhaltsverzeichnis ohne Einrückung
1. Kapitel der ersten Ebene 1.1. Kapitel der zweiten Ebene 1.1.1. Kapitel der dritten Ebene 1.1.2. Kapitel der dritten Ebene 1.2. Kapitel der zweiten Ebene 1.2.1. Kapitel der dritten Ebene 1.2.2. Kapitel der dritten Ebene 1.2.3. Kapitel der dritten Ebene 1.2.4. Kapitel der dritten Ebene 1.3. Kapitel der zweiten Ebene 1.3.1. Kapitel der dritten Ebene 1.3.2. Kapitel der dritten Ebene 1.3.3. Kapitel der dritten Ebene 2. Kapitel der ersten Ebene 2.1. Kapitel der zweiten Ebene 2.1.1. Kapitel der dritten Ebene 2.1.2. Kapitel der dritten Ebene 2.2. Kapitel der zweiten Ebene 2.2.1. Kapitel der dritten Ebene 2.2.2. Kapitel der dritten Ebene
Sicherlich lassen sich auch Inhaltsverzeichnisse finden, die unter graphischen Gesichtspunkten komplett linksbündig ausgerichtet sind. Um die Übersichtlichkeit links ausgerichteter Inhaltsverzeichnisse zu erhöhen, werden dann allerdings auch Formatierungs-Optionen (Groß-/Klein-Schreibung, Fett, Kursiv, …) auf die Kapitel der gleichen Ebene angewendet.
Formatierungsoptionen stehen in SSMS nicht zur Verfügung. Daher bleibt als strukturierendes Mittel lediglich die Einrückung.
Grundlegende Sprachelemente
Ein SELECT-Statement besteht aus den folgenden grundlegenden Klauseln:
SELECT
FROM
WHERE
GROUP BY
HAVING
ORDER BY
Betrachtet man die genannten Hauptelemente als Elemente der ersten Ebene wären die jeweils zulässigen Sprachelemente der zweiten Ebene gemäß der Forderung aus dem vorigen Kapitel einzurücken. Daraus ergibt sich die folgende grundlegende Struktur eines SQL Statements:
SELECT Feldliste FROM Datenquellen WHERE Bedingungen auf Datenquelle GROUP BY Gruppierungsfelder HAVING Bedingungen auf Aggregationen ORDER BY Sortier-Felder
In jedem Fall sollten die Hauptelemente eines SQL-Statements in separaten Zeilen notiert werden.
Als Abgrenzung hierzu möchte ich zwei Beispiele für Formatierungen geben, die häufig zu finden sind und die diese klare Strukturierung nicht berücksichtigen. In beiden Beispielen ist der Leser des Statements dazu gezwungen das Statement zumindest in Teilen zu lesen, um zu erfassen, wo ein Hauptelement beginnt und wo er aufhört.
Linksbündige Ausrichtung von Haupt- und Unterelementen
Gelegentlich findet man dass Elemente der obersten Ebene und die jeweils zulässigen Elemente der nächsten Ebene gleichermaßen eingerückt sind. Zu beobachten ist das insbesondere innerhalb der FROM Klausel. Die Datenquellen (Tabellen/Views/CTE’s) sind genauso eingerückt wie das einleitende Schlüsselwort FROM:
SELECT Feldliste FROM Tabelle1 JOIN Tabelle2 ON [...] JOIN Tabelle2 ON [...] WHERE Bedingungen auf Datenquelle GROUP BY Gruppierungsfelder HAVING Bedingungen auf Aggregationen ORDER BY Sortier-Felder
Rechtsbündige Ausrichtung von Schlüsselwörtern
In diesem Beispiel sind die grundlegenden Klauseln des SELECT Statements – ohne Berücksichtigung des Schlüsselwortes BY – rechtsbündig ausgerichtet. Durch diese Art der Einrückung entsteht zusätzlich ein erhöhter Aufwand für die Ausrichtung der Elemente, da man mit unterschiedlich starken Einrückungen arbeiten muss.
SELECT Feld1, Feld2, Feld3 FROM Tabelle1 LEFT JOIN Tabelle2 ON [...] LEFT JOIN Tabelle2 ON [...] WHERE Bedingung1 OR Bedingung2 OR Bedingung3 GROUP BY Gruppierungsfelder HAVING Bedingung1 OR Bedingung2 OR Bedingung3 ORDER BY Sortier-Felder WHERE Klausel
SELECT Feldliste
Die natürliche Leserichtung (eines SQL-Statements) ist von links nach rechts und von oben nach unten. Mit Blick auf die Navigationsmöglichkeiten mit Tastatur und der Maus erscheint die vertikale Navigation um einiges leichter als die horizontale Navigation. Das Mausrad und die Bild auf/Bild ab Tasten ermöglichen eine schnelle vertikale Navigation auch innerhalb langer komplexer Statements, wenn Feldlisten untereinandergeschrieben werden.
Feldnamen sollten daher als vertikale Liste mit vorangestellten Kommas geschrieben werden (s. a. Formatierung von SQL-Statements (Teil 1); Abschnitt Das Komma). Je Zeile ist nur ein Feld zu notieren. Da die Feldliste dem Schlüsselwort SELECT logisch untergeordnet ist, sind die Feldnamen entsprechend der vereinbarten Einrückungsweite einzurücken.
SELECT Feldl1 ,Feldl2 ,Feldl3 FROM [...] WHERE [...] GROUP BY Feldl1 ,Feldl2 ,Feldl3 HAVING [...] ORDER BY [...] Feldl1 ,Feldl2 ,Feldl3
WHERE-Klausel
Die WHERE-Klausel sei an dieser Stelle entgegen der Abfolge der Hauptelemente eines SELECT-Statements als nächstes erläutert, da die hier aufgestellten Forderungen gleichermaßen auf die FROM– und die HAVING-Klauseln anwendbar sind. Eine WHERE-Klausel enthält einen oder mehrere Constraints, die durch logische Operatoren verknüpft sind. Bei der Formatierung von Constraints in der WHERE Klausel sind zwei Punkte besonders zu berücksichtigen.
- Ausrichtung von Operanden
- Einrückung gleichwertiger Constraints
Ausrichtung von Operanden
Ein einfacher Constraint besteht normalerweise aus zwei Operanden und einem Operator: (=, !=, <>, IN, NOT IN, etc. In einem komplexen Constraint, der aus mehreren einzelnen Constraints zusammengesetzt ist, sind die Operanden und Operatoren jeweils linksbündig untereinander auszurichten. In dem folgenden Beispiel sind die Feldnamen unterschiedlich lang und es werden unterschiedliche Operatoren angewendet:
[...] WHERE T01.[Feld___1] = 'Irgendwas' AND T01.[Feld__2] <> 1 AND T01.[Feld_____3] NOT IN (1, 2, 3) AND T01.[Feld4] = T02.[Feld5]
Hieraus ergibt sich eine tabellenartige Notation der Constraints, die dem Leser eine schnelle und sichere visuelle Navigation innerhalb der Bestandteile der Constraints erlaubt.
Einrückung gleichwertiger Constraints
Enthält die WHERE Klausel mehr als einen Constraints , sind diese mit einem logischen Operator, wie zum Beispiel dem UND oder ODER zu verknüpfen. Bei komplexen Constraints kann auch die Verwendung von Klammern erforderlich sein, um die Auswertungsreihenfolge der einzelnen Ausdrücke festzulegen. Je nach Komplexität des Gesamtausdrucks sind die einzelnen Ausdrücke bisweilen stark verschachtelt.
Um komplexe und verschachtelte Ausdrücke besser lesen zu können, sollte der Strukturierung und Formatierung einer WHERE Klausel besondere Aufmerksamkeit gewidmet werden: Gleichwertige Constraints sollten untereinander mit gleicher Einrückung notiert werden. Eine logische Verknüpfung von gleichwertigen Constraints wird mit einer Einrückung notiert, die der Verarbeitungsreihenfolge der Constraints entspricht.
[...] WHERE ( ( [Operand01] = [Operand02] OR [Operand03] = [Operand05] OR [Operand05] = [Operand06] ) AND ( [Operand07] = [Operand08] OR [Operand09] = [Operand10] ) AND [Operand11] = [Operand12] ) OR ( [Operand13] = [Operand14] )
Die logischen Verknüpfungen von Teilausdrücken können bei entsprechender Einrückung in lesbarer Form präsentiert werden. Ein Screenshot der gleichen WHERE Klausel in Notepad++ mag die Begründung hierfür einleuchtender erscheinen lassen, da die Klammern bzw. die jeweilige Einrückung über vertikale Hilfslinien im Abstand der Tabulatorweite nachvollziehbarer sind:
FROM Klausel
Folgen wir dem Dogma des strukturierten Aufbaus eines SQL-Statements, sind auch die untergeordneten Elemente der FROM Klausel eingerückt zu notieren. In der Regel handelt es sich hier um die Angabe von Datenquellen, also Tabellen, Views und Common Table Expressions.
Anmerkung zu Sub-SELECTS
Sub-SELECT-Statements sollten in einer FROM Klausel vermieden werden. Sub-SELECTS machen ein SQL Statement unleserlich und können viel übersichtlicher als Common Table Expressions (CTE) notiert und damit auch mehrfach innerhalb eines SQL Statements verwendet werden.
Damit ist bei der Festlegung einer Formatierungsanweisung auf die Bestandteile der JOIN-Statements abzustellen:
- Tabelle
- JOIN Operator
- ON Schlüsselwort
- JOIN Constraints
In dem folgenden Codebeispiel hat der Leser keinen visuellen Anker für die Identifikation der Elemente der FROM-Klausel:
FROM Tabelle1 T01 JOIN Tabelle2 T02 ON T01.[FK] = T02.[ID] JOIN Tabelle3 T030 ON T02.[FK] = T03.[ID] JOIN Tabelle4 T04 ON T03.[FK] = T04.[ID] WHERE [...]
Die möglichen Elemente der FROM Klausel gehören nach meiner Auffassung der Lesbarkeit wegen in separate Zeilen. Als Ausnahme hiervon sollten herangejointe Tabellen direkt hinter dem JOIN Operator stehen. Das Schlüsselwort ON steht linksbündig zu dem JOIN Operator in der folgenden Zeile. Für die JOIN Constraints versuche ich die Festlegungen für die WHERE-Klausel anzuwenden.
SELECT [...] FROM Tabelle1 T01 JOIN Tabelle2 T02 ON T01.[FK] = T02.[ID] JOIN Tabelle3 T030 ON T02.[FK1] = T03.[Fk1] AND T02.[Feld2] = T03.[Feld2] JOIN Tabelle4 T04 ON T03.[FK] = T04.[ID] WHERE [...]
GROUP BY, HAVING, ORDER BY
Für die verbleibenden Hauptelemente gelten die analoge Regeln wie sie bereit in den vorangegangenen Kapiteln beschrieben wurden.