Formatierung von SQL Statements (Teil 2)

This article in english…

Ü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.