Formatierung von SQL Statements (Teil 1)

This article in english…

Überblick

Häufig werden Großbuchstaben dazu genutzt, um einzelne Worte zu betonen. Dieses gilt zum Beispiel für Deutsch als Sprache. Latein, Griechisch,… Obwohl es auch Sprachen gibt, die keine Unterscheidung zwischen Groß- und Kleinschreibung kennen, hat sie sich dennoch weitestgehend durchgesetzt.

Das gilt nicht nur für die gesprochene Sprache, sondern auch für Programmiersprachen beziehungsweise Namenskonventionen für die Programmierung. So legt zum Beispiel die CamelCase-Notation fest, dass bei zusammengesetzten Wörtern die Anfangsbuchstaben der einzelnen Worte groß geschrieben werden.

CamelCase

Es gibt auch Strömungen, die das erste Wort des zusammengesetzten Begriffs klein schreiben:

camelCase

Die Nutzung von Großbuchstaben ist damit auch in der Programmierung ein anerkanntes Mittel, um Worte, Bezeichner, Funktionsnamen, etc. besonders zu kennzeichnen.

Die Definition von Regeln für die Schreibweise von Bezeichnern, Funktionsnamen, etc. ist unter Programmierern weitgehend akzeptiert und mündet letztlich in einer Namenskonvention, die festlegt, wie die Sprachelemente einer Programmiersprache zu schreiben sind. Dennoch scheint es für viele Entwickler eine Herausforderung zu sein, sich an Namenskonventionen zu halten. Tatsächlich lässt sich häufig sogar beobachten, dass Entwickler die „eigene“ präferierte Konvention nicht konsequent durchhalten.

Die Einhaltung von Namenskonventionen erhöht die Lesbarkeit von Text im Allgemeinen und in der Programmierung im Besonderen. der Gleiche satz noch mal GeSchrieben Mit abweichungen von der allgemein BEKANNTEN namensKonvention, dass Substantive Groß geschrieben werden und adjektive, wie auch verben In KleinBuchstaben, wird der text Unleserlich: „die einhaltung Von namensKonventionen Erhöht Die lesbarkeit von text im allgemeinen Und In der programmierung Im Besonderen.“

Das folgende Statement entstammt der View vTimeSeries aus der Datenbank AdventureWorksDW2017 und wurde von mir leicht überarbeitet. Ich habe mir hier nicht viel Mühe gegeben, eine wie auch immer geartete Namenskonventionen einzuhalten.

Source

In dem nachfolgenden Statement sind Funktionsnamen groß geschrieben, alle Feldnamen sind mit Delimiter notiert, Datentypen sind klein geschrieben und schließlich ist das Statements in sich konsistent ausgerichtet und formatiert…

Source

Ob die angewendete Formatierung nun gefällt oder nicht, das muss jeder für sich entscheiden. In jedem Fall erscheint das zweite Statement auf den ersten Blick aufgeräumter und leichter verständlich.

Damit sind nun auch schon einige Punkte genannt, die grundlegend in jeder Namenskonvention festgelegt sein sollten. Darüber hinaus gibt es noch einige andere Dinge, die in einem solchen Regelwerk festzuhalten sind:

Diese Liste ist nicht vollständig und soll Anregung für die Bereiche/Elemente sein, für die eine Namenskonvention zu erstellen ist.

In einem zweiten Teil zur Formatierung von SQL Statements werden Best Practices für die Strukturierung von SQL Statements zusammengetragen.

Reguläre Bezeichner

Alle Datenbankobjekte haben natürlich einen Namen, den sogenannten Bezeichner. Jeder Datenbankprovider spezifiziert Regeln für die Vergabe eines gültigen Bezeichners. So ist in SQL Server die Länge von Bezeichnern in der Regel auf 128 Zeichen beschränkt und ein regulärer Bezeichner darf zum Beispiel keine Leerzeichen enthalten. Die genaue Definition eines regulären Bezeichners kann in der Online-Dokumentation nachgelesen werden:

https://docs.microsoft.com/de-de/sql/relational-databases/databases/database-identifiers?view=sql-server-2017

Die Definition eines regulären Bezeichners ist jedoch meines Erachtens zu weit gefasst.

Während z.B. der Unterstrich _ als trennendes Zeichen zwischen Wort-Bestandteilen weit verbreitet ist und Bestandteil eines regulären Bezeichners sein kann, vermeide ich dieses Zeichen weitgehend, da die Notation eines Bezeichners nach CamelCase den gleichen Zweck erfüllt und Bezeichner kompakter erscheinen lässt.

Laut Definition zulässige Sonderzeichen wie das @#und $ vermeide ich komplett. Die Verwendung dieser Zeichen machen einen Bezeichner unleserlich. Sie sind einem Buchstaben aus dem lateinischen Alphabet zu ähnlich und können doch nicht als solcher direkt erfasst werden.

Source

Ich beschränke mich daher bei der Wahl eines Bezeichners auf Buchtstaben des lateinischen Alphabetes [a-zA-Z] sowie die Ziffern [0-9], sofern sich die Verwendung von Ziffern nicht vermeiden lässt. Damit lassen sich die Regeln für die Wahl eines guten Bezeichners auf zwei Regel reduzieren:

  • Nur Buchstaben des lateinischen Alphabetes
  • Hilfsweise Ziffern

Eine konsequente Vergabe von regulären Bezeichnern geht natürlich immer Hand in Hand mit der Entwicklung einer Namenskonvention für Objektnamen.

Bezeichnungsbegrenzer

Sobald ein Bezeichner ein Leerzeichen enthält, handelt es sich nicht mehr um einen regulären Bezeichner. Die Verwendung von nicht regulären Bezeichnern ist kein guter Programmierstil. Dennoch sind nicht reguläre Bezeichner zulässig, wenn die Bezeichner entweder von Anführungszeichen oder den eckigen Klammern eingeschlossen sind.

Die Verwendung von Anführungszeichen ist in den meisten SQL Dialekten Standard. Microsoft erlaubt hier aber auch – abweichend vom Standard – eckige Klammern. Ich bevorzuge die eckigen Klammern als proprietäre Ausprägung für Bezeichnungsbegrenzer.

Da Bezeichnungsbegrenzer Bezeichner nach meinem Empfinden wesentlich besser visuell von anderen Sprachelementen eines SQL Statements abgrenzen und damit wesentlich zur Lesbarkeit eines SQL Statements beitragen, verwende ich Bezeichnungsbegrenzer grundsätzlich unabhängig davon, ob Bezeichner regulär sind oder nicht:

  • Schemas
  • Tabellen
  • Views
  • Feldnamen
  • Aliase
  • Alle Objekte aus dem Bereich der Programmierung (Funktionen, Prozeduren, etc.)

Das folgende Statement entspricht exakt dem zweiten Statement aus diesem Artikel. Die Bezeichner sind jedoch nicht mit Bezeichnungsbegrenzern notiert.

Source

Offensichtlich gibt Microsoft der Verwendung der eckigen Klammern den Vorzug, da zahlreiche automatisiert erstellte Skripte als Bezeichnungsbegrenzer die eckigen Klammern verwenden. So verwendet SQL Server Management Studio (SSMS) ebenfalls eckige Klammern bei automatisch generierten SELECT- und DDL Statements (über das Kontext-Menü zu einer Tabelle). Microsoft ist hier leider nicht sehr konsequent und verzichtet bei der Erstellung von Views über den Wizard sowie in dem SQL Panel des Edit-Features auf Bezeichnungsbegrenzer, soweit diese weggelassen werden können. Die folgenden beiden Screenshots von automatisch generiertem SQL können als gutes Beispiel für nicht wartbaren Code angesehen werden:

View

Edit Feature

Die Verwendung von Bezeichnungsbegrenzern ist übrigens nur dann zulässig, wenn die SQL Server Einstellung QUOTED_IDENTIFIER auf ON gesetzt ist

SET QUOTED_IDENTIFIER ON

Ist diese Einstellung auf OFF gesetzt, sind nicht reguläre Bezeichner unzulässig:

https://docs.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql?view=sql-server-2017

Der Spacer

…oder vielleicht sollte ich eher schreiben, der vertikale Spacer. Die natürliche Leserichtung (eines SQL Statements) ist von links nach rechts und von oben nach unten.

Während Software als Folge von Einzelanweisungen erstellt wird, ist SQL darauf ausgelegt viele Arbeitsschritte in einer einzigen Anweisung auszuführen. Ein SQL Statement kann schnell hundert und mehr Zeilen enthalten. Hieraus ergibt sich eine besondere Herausforderung, gute SQL Statements zu schreiben. Ein Wesentliches Kriterium für die schnelle Erfassung der Struktur und auch der Aufgabe eines Statements ist nicht nur eine klare Gliederung und Formatierung des Statements, sondern auch die Kompaktheit. Bei einer hohen Auflösung und ansonsten normalen Anzeigeeinstellungen dürften auf einem normalen Monitor in SSMS maximal 40 Zeilen sichtbar sein, wenn nur ein Abfragefenster und kein Ergebnisfenster sichtbar sind. Im Normalfall sind aber wohl maximal 25 bis 30 Zeilen sichtbar.

Es gibt durchaus Kollegen in der programmierenden Zunft, die hinter jeder Zeile

Code eine Leerzeile einfügen. Eine übermäßige Verwendung von leeren Zeilen nötigt

den Leser eines Statements zum übermäßigen Gebrauch der Navigationstasten oder

des Mausrades. Schlimmer noch ist, dass hierdurch der Gesamtkontext eines

Statements schlechter zu erfassen ist.

Leerzeilen können ein gutes stilistisches Mittel sein um logische Blöcke voneinander zu trennen. Der übermäßige Gebrauch jedoch macht ein Statement schwerer lesbar.

Das Semikolon

Gemäß SQL Standard sind alle SQL Anweisungen mit einem Semikolon abzuschließen. Zumindest SQL Server ist hier tolerant und zwingt den Programmierer nicht dazu, das Semikolon zu verwenden. Es gibt nur wenige Fälle in denen die Verwendung eines Semikolon Pflicht ist.

So muss zum Beispiel bei Verwendung einer Common Table Expression die Anweisung, die vor dem einleitenden Schlüsselwort WITH steht, mit einem Semikolon abgeschlossen sein. Um möglichen Problemen aus dem Weg zu gehen notieren zahlreiche Entwickler das einleitende WITH als ;WITH.

Die Verwendung des Semikolon zeugt in jedem Fall nicht nur von Sorgfalt, sie fördert die Lesbarkeit und erleichtert im Zweifel auch die Migration einer SQL Anwendung in eine Datenbank eines anderen Providers.

Das Komma

Feldlisten sind in SQL durch ein Komma zu trennen. In der Welt der Programmierer gibt es jene, die das Komma einem Feldnamen voranstellen und solche, die es nach einem Feldnamen notieren. Das Für und Wider beschränkt sich im Wesentlichen darauf, wie schwierig es ist, neue Felder hinzuzufügen oder zu löschen. Der eigentliche Grund, warum das Komma aber an den Anfang gehört, ist die Lesbarkeit und die Möglichkeit zur Formatierung mit dem Spalten Editor (siehe auch Funktionale Ästhetik von SQL).

Komma vor dem Feldnamen

Source

Wenn ich als weiteren Feldnamen nun FrenchDayNameOfWeek hinzufügen möchte, fällt es mir leichter, das Feld am Ende hinzuzufügen, da nur der Text

,[FrenchDayNameOfWeek]

hinter dem Feld SpanishDayNameOfWeek einzufügen ist:

Source

Soll das Feld als erstes Feld eingefügt werden, sind zwei Änderungen erforderlich: Vor dem Feld EnglishDayNameOfWeek ist die Zeile

[FrenchDayNameOfWeek]

und vor dem Feld EnglishDayNameOfWeek ein Komma einzufügen:

Source

Komma nach dem Feldnamen

Wenn das Komma hinter den Feldnamen geschrieben wird, stellte sich der Sachverhalt genau umgekehrt dar: Es ist leichter ein neues Feld an Position 1 hinzuzufügen, als an letzter Stelle.

Das Komma und die Lesbarkeit

Das Komma ist ein Trennzeichen. Es markiert den Wechsel von einem Feld zum nächsten. Wird das Komma jeweils hinter einen Feldnamen geschrieben, verliert es seinen trennenden Charakter aufgrund der unterschiedlichen Längen der Feldnamen.

In dem folgenden Statement ist nicht sofort ersichtlich, ob der Bezeichner Alias ein Feldname ist oder eine andere Bedeutung hat. Der Betrachter muss die Zeile oberhalb des Bezeichners Alias lesen um festzustellen, dass der Bezeichner als Alias zu lesen ist.

Source

Anders stellt es sich dar, wenn das Komma vor die Feldnamen geschrieben wird. In diesem Fall ist sofort ersichtlich, dass der Bezeichner Alias kein Feldname ist, da ihm kein Komma vorangestellt ist:

Source

Das Komma und der Spalten-Editor

In dem folgenden Statement ist es schon eine kleine Herausforderung, den Feldnamen einen Alias zu verpassen.

Source

Es ist das Komma um mindestens eine Position nach rechts zu verschieben und vor dem Komma ist das Schlüsselwort AS sowie der Alias selbst einzufügen. Und das jeweils für 4 Zeilen bzw. Feldnamen.

Source

Der Aufwand, der hierfür betrieben werden muss, ist beträchtlich und kann erheblich reduziert werden, wenn das SQL Statement so formatiert ist, dass es effizient mit dem Spalteneditor bearbeitet werden kann.

Source

Es sind keine Kommas zu verschieben. Über den Spalteneditor ist das Schlüsselwort AS nur einmal zu tippen, die Feldnamen können als Basis für die Vergabe des Alias verwendet und über den Spalteneditor als Block hinter das Schlüsselwort AS kopiert werden, um diese anschließend – ebenfalls über den Spalteneditor – um den Präfix Alias zu erweitern. Das Beste an dieser Vorgehensweise ist: Der Aufwand hierfür ist weitestgehend unabhängig von der Anzahl der zu bearbeitenden Zeilen bzw. Feldnamen.

Die effiziente Verwendung des Spalteneditors ist erst mit vorangestellten Kommas möglich und damit ein absolutes Killer-Argument für vorangestellte Kommas.

Funktionsnamen

Funktionsnamen werden in SSMS durch rosafarbene Buchstaben hervorgehoben und sind damit per se leicht(er) zu identifizieren. Da aber nicht alle Editoren eine farbliche Hervorhebung von Funktionsnamen verwenden, sollten Funktionsnamen auch durch eine einheitliche Groß- oder Kleinschreibung kenntlich gemacht werden.

Funktionsnamen sollten also entweder nur groß oder nur klein geschrieben werden.

Microsoft selbst – aber auch andere Datenbankprovider – notieren Funktionsnamen in der Online Dokumentation in Großbuchstaben. Warum also von dem Standard abweichen?!

Aliase

Eine weit verbreitete Praxis ist es, einen Tabellen-Alias als „sprechende“ Abkürzung aus dem Tabellennamen abzuleiten. Für die Tabelle FactInternetSales könnte zum Beispiel der Alias IS verwendet werden. Die Buchstaben ergeben sich aus den Anfangsbuchstaben der Wort-Bestandteile des Tabellennamens (ohne Berücksichtigung des Präfix Fact). Da IS ein reserviertes Wort ist, muss dieser Alias zwingend mit Bezeichnungsbegrenzern umschlossen werden. Für andere Tabellen könnten folgende Aliase abgeleitet werden:

DimCustomer CUST
DimProduct P
DimProductCategory PC
DimProductSubcategory PSC

Ein SELECT Statement über die oben genannten Tabellen könnte dann wie folgt aussehen:

Source

Die unterschiedliche Einrückung der Feldnamen selbst resultiert aus den unterschiedlichen Längen der Aliase. Diese haben eine Länge zwischen 1 und 4 Zeichen. Die Feldliste erscheint „unruhig“ und kann bei komplexeren Statements und Verwendung von weiteren Sprachelementen (Funktionen, CASE-Anweisungen, etc.) schnell unleserlich werden.

Man stelle sich ein SELECT Statement auf der Basis von 20 und mehr Tabellen vor. Irgendwann wird es schwer, für einen Tabellennamen einen sinnvollen Alias zu verwenden. Spätestens ab dem 5 Alias in einem Statement dürfte die Herleitung des Alias aus Tabellennamen die Lesbarkeit des Statements nicht mehr verbessern, da die Aliase dann doch zu kryptisch sind.

Wären systematische, möglicherweise sogar indizierte Aliase nicht leichter in einem komplexen Statement zu identifizieren?

Systematische Aliase könnten wie folgt definiert sein:

  1. Aliase sollen auf eine fixe Anzahl Zeichen begrenzt sein
  2. Aliase sind zu indizieren (mit einem oder mehreren führenden Buchstaben)

Verwendet man zum Beispiel als Alias den Buchstaben T für Tabelle (oder F für Fakten-Tabelle, D für Dimension, etc.) gefolgt von einer zweistelligen 1-basierten Indizierung, dann ergeben sich die folgenden Aliase: T01, T02, D01, F01,…

Unter Verwendung dieser Aliase erscheint das obige Statement lesbarer:

Source

Die Feldnamen innerhalb der SELECT Liste sind sauber ausgerichtet.

Das eigentliche Killer-Argument ist aber auch hier: Die Notation von gleichlangen Aliasen ermöglicht erst den Einsatz des Power-Features Spalteneditor.

Neben diesem Killer-Argument gibt es aber noch einen anderen gewichtigen Grund, überhaupt Aliase zu verwenden: Erst durch die Verwendung von Aliasen, kann das Feature Intellisense effizient genutzt werden. Notiert der Entwickler einen Alias gefolgt von einem Punkt, dann öffnet sich ein Kontext-Menü, das nur die zu dem Alias gehörenden Feldnamen auflistet:

Das Kontextmenü kann – wenn sich der Cursor hinter dem Punkt befindet – auch über die Tastenkombination STRG+Leertaste manuell aufgerufen werden. Das Kontext-Menü kann natürlich auch ohne den Kontext des vorangestellten Alias genutzt werden. Allerdings bietet SSMS dann mehr oder minder den gesamten Sprachschatz von T-SQL zur Auswahl an.

Aliase machen SQL Code leserlicher und damit wartbar. Daher sollten auch bei einfacheren Statements immer Aliase verwendet werden.

Qualifizierte Feldnamen

Werden von einem Statement mehrere Tabellen referenziert, wird schnell der Fall eintreten, dass die somit aus allen referenzierten Tabellen Feldnamen nicht eindeutig sind. In dem folgenden Beispiel sollen zu einem Fakt ResellerSales Namen und Telefonnummer sowohl des Mitarbeiters als auch des Vertriebspartners ausgegeben werden:

Source

Das Statement ist nicht ausführbar, da die Spalte Phone in beiden herangejointen Dimensionen DimEmployee und DimReseller vorhanden ist. Die Angabe eines Alias ist hier zwingend erforderlich

T01.[Phone] AS [Emplyee_Phone]

oder – sofern keine Tabellenaliase verwendet werden – ist der Tabellenname selbst dem Feld voranzustellen:

[DimEmplyoee].[Phone] AS [Emplyee_Phone]

Letztere Variante trägt jedoch nicht zur Lesbarkeit des Statements bei.

Ein voll qualifizierter Feldname besteht insgesamt aus 5 Elementen:

[Server].[Datenbank].[Schema].[Tabelle].[Feld]

Ein Beispiel hierfür findet sich in der folgenden Abbildung (Tabellenname enthält den Namen der Datenbank):

Source

Eine über das Schema hinausgehende Qualifizierung verhindert die Portierbarkeit einer Anwendung in eine Datenbank, die nicht den gleichen Namen AdventuerWorksDW2017 hat. Ich habe schon ETL-Strecken gesehen, in denen Tabellen bis zum Datenbanknamen voll qualifiziert angegeben waren:

[Datenbank].[Schema].[Tabelle].[Feld]

Die Schnittstelle war nach Fertigstellung nicht in der Produktion deploybar.