Formatierung von SQL Statements (Teil 1) — Bezeichner, Delimiter, Kommata, Aliase

Wer ein schlecht oder gar nicht formatiertes SELECT mit 30 Spalten und einem halben Dutzend Joins einmal hat debuggen müssen, weiß: nicht das SQL kostet den Tag, sondern die Suche danach, was es eigentlich tut. Formatierung von SQL ist keine Geschmacksfrage, sondern ein Wartungs-Werkzeug — und sie beginnt bei einer Namenskonvention.

→ Teil einer Reihe. Dieser Artikel ist Teil 1 und behandelt Bezeichner, Delimiter, Kommata und Aliase. Für die Strukturierung längerer Statements geht es weiter in Teil 2 — Strukturierung und Formatierung.

Was in diesem Artikel steht:

  • Bezeichner und Delimiter — wann eckige Klammern, wann Anführungszeichen, was ein regulärer Bezeichner ist
  • Komma, Semikolon, Spacer — die kleinen Trenner und ihre große Wirkung auf Lesbarkeit
  • Tabellen-Aliase und qualifizierte Feldnamen — warum systematische T01-Aliase in breiten Statements besser skalieren als sprechende Abkürzungen
  • Blockauswahl als Killer-Argument für vorangestellte Kommata

SSMS dient als Beispiel-Editor. Die Prinzipien gelten auch für DataGrip, VS Code und DBeaver. Die SQL-Beispiele referenzieren AdventureWorksDW2017.

Inhalt

Warum überhaupt eine Namenskonvention?

Großbuchstaben sind in vielen geschriebenen Sprachen ein etabliertes Mittel, einzelne Worte zu betonen. In der Programmierung übersetzt sich das in Notationen wie CamelCase (Anfangsbuchstaben aller Worte groß), camelCase (alle bis auf das erste klein) oder snake_case (alles klein mit Unterstrichen zwischen Wörtern). Diese und ähnliche Regeln münden in einer Namenskonvention — einer Festlegung, wie Bezeichner, Funktionsnamen und Datentypen geschrieben werden. Tatsächlich lässt sich häufig 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 leicht überarbeitet. Es hält keine Namenskonvention ein:

  1: SELECT 
  2: case [Model] 
  3: WHEN 'Mountain-100' THEN 'M200' when 'Road-150' THEN 'R250' when 'Road-650' THEN 'R750' 
  4: WHEN 'Touring-1000' THEN 'T1000' ELSE LEFT(Model, 1) + Right([Model], 3) 
  5: END + ' ' + [Region] AS [ModelRegion] ,(convert(Integer, CalendarYear) * 100) + CONVERT(int, Month) AS [TimeIndex] 
  6: ,SUM(Quantity) AS [Quantity] 
  7: ,sum(Amount) AS Amount, calendaryear,[Month]
  8: ,[dbo].[udfbuildiso8601date] ([CALENDARYEAR], [Month], 25)
  9: as reportingdate
 10: FROM [dbo].[vDMPrep] 
 11: where [Model] IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 
 12: 'Road-650', 'Road-750', 'Touring-1000') 
 13: GROUP BY CASE [Model] 
 14: WHEN 'Mountain-100' THEN 'M200' WHEN 'Road-150' THEN 'R250' WHEN 'Road-650' THEN 'R750' 
 15: WHEN 'Touring-1000' THEN 'T1000' ELSE Left(Model,1) + Right(Model,3) 
 16: end+' '+[Region] ,(Convert(Integer, [CalendarYear]) * 100) + Convert(Integer, [Month])
 17: ,CalendarYear,[Month],[dbo].[udfBuildISO8601Date] ([CalendarYear], [Month], 25);

Im nachfolgenden Statement sind Funktionsnamen großgeschrieben, alle Feldnamen mit Delimiter notiert und Datentypen kleingeschrieben. Alle Elemente sind konsistent ausgerichtet:

  1: SELECT 
  2:     CASE [Model] 
  3:        WHEN 'Mountain-100' THEN 'M200' 
  4:        WHEN 'Road-150'     THEN 'R250' 
  5:        WHEN 'Road-650'     THEN 'R750' 
  6:        WHEN 'Touring-1000' THEN 'T1000' 
  7:        ELSE LEFT([Model], 1) + RIGHT([Model], 3) 
  8:     END + ' ' + [Region] AS [ModelRegion] 
  9:    ,(CONVERT(int, [CalendarYear]) * 100) + CONVERT(int, [Month]) AS [TimeIndex] 
 10:    ,SUM([Quantity])  AS [Quantity] 
 11:    ,SUM([Amount])    AS [Amount]
 12:    ,[CalendarYear]
 13:    ,[Month]
 14:    ,[dbo].[udfBuildISO8601Date]([CalendarYear], [Month], 25) AS [ReportingDate]
 15: FROM 
 16:    [dbo].[vDMPrep] 
 17: WHERE 
 18:    [Model] IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 'Road-650', 'Road-750', 'Touring-1000') 
 19: GROUP BY 
 20:     CASE [Model] 
 21:        WHEN 'Mountain-100' THEN 'M200' 
 22:        WHEN 'Road-150'     THEN 'R250' 
 23:        WHEN 'Road-650'     THEN 'R750' 
 24:        WHEN 'Touring-1000' THEN 'T1000' 
 25:        ELSE LEFT([Model], 1) + RIGHT([Model], 3) 
 26:     END + ' ' + [Region]
 27:    ,(CONVERT(int, [CalendarYear]) * 100) + CONVERT(int, [Month])
 28:    ,[CalendarYear]
 29:    ,[Month]
 30:    ,[dbo].[udfBuildISO8601Date]([CalendarYear], [Month], 25);

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

Die folgenden Sektionen behandeln die wichtigsten Bestandteile einer Namenskonvention. Die Liste ist nicht vollständig, sondern als Anregung gedacht. Dabei gilt: Einiges ist technischer Fakt (etwa das Verhalten von QUOTED_IDENTIFIER oder das Case-Folding in Postgres), das meiste ist begründete Konvention — eine Wahl, die ein Team auch anders treffen kann, solange es sie konsequent trifft. Ein zweiter Teil schließt mit Best Practices für die Strukturierung von SQL-

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:

learn.microsoft.com/de-de/sql/relational-databases/databases/database-identifiers

Für die Praxis ist diese Definition allerdings zu weit gefasst. Eine tragfähige Konvention zieht engere Grenzen, als das Datenbanksystem verlangt.

Bevor man sich auf eine Schreibweise festlegt, lohnt ein Blick auf das Verhalten des Datenbanksystems gegenüber Bezeichnern. SQL Server unterscheidet bei case-insensitiver Collation nicht zwischen Groß- und Kleinschreibung: FactInternetSales und factinternetsales zeigen auf dieselbe Tabelle. Das ist der übliche Fall, denn die Installations-Defaults sind case-insensitiv — grundsätzlich ist die Collation aber pro Instanz, Datenbank und sogar Spalte wählbar. Case-Insensitivität gibt einem die Freiheit, eine optisch betonende Notation wie CamelCase zu wählen, ohne dass das Statement bricht. Postgres dagegen faltet Bezeichner ohne Quotes immer auf lowercase: FactInternetSales wird intern zu factinternetsales. CamelCase lässt sich zwar auch ohne Quotes schreiben, verliert dabei aber seine Großschreibung. Erhalten bleibt sie nur in "double quotes", was beim Schreiben jedes Statements zur Last wird. In der Postgres-Welt ist snake_case deshalb die übliche Konvention.

Hinter diesem Blog stehen zehn Jahre SQL-Server-Praxis mit CamelCase als fester Konvention. Seit dem Umstieg auf Postgres vor drei Jahren hat sich dort snake_case fest etabliert. Die Tiefen der Case-Sensitivity-Unterschiede zwischen den beiden Engines verdienen einen eigenen Artikel, der noch folgt. Die Beispiele hier bleiben bei der SQL-Server-typischen CamelCase-Notation, die für AdventureWorksDW2017 ohnehin gegeben ist.

Der Unterstrich _ ist als trennendes Zeichen zwischen Wort-Bestandteilen weit verbreitet und darf Bestandteil eines regulären Bezeichners sein. In der SQL-Server-Welt lässt er sich dennoch weitgehend vermeiden: Die CamelCase-Notation erfüllt denselben Zweck und lässt Bezeichner kompakter erscheinen.

Laut Definition zulässige Sonderzeichen wie @# und $ gehören nach der Konvention dieses Blogs dagegen nicht in Bezeichner. Technisch sind sie im Inneren eines regulären Bezeichners sogar ohne Begrenzer erlaubt — die Sonderbedeutung tragen sie nur an führender Position: Ein führendes @ kennzeichnet eine Variable, ein führendes # eine temporäre Tabelle. Genau diese Doppelrolle ist das Problem. Wer die Zeichen in Spalten- oder Tabellen-Bezeichnern verwendet, erzeugt visuelle Unruhe und lädt zu Verwechslungen mit den Sonderbedeutungen ein:

  1: SELECT 
  2:     [EnglishDayNameOfWeek] AS [English@DayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek] AS [Spanish#DayNameOfWeek]
  4:    ,[FrenchDayNameOfWeek]  AS [FrenchDayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

Laut Definition dürfen reguläre Bezeichner sogar Unicode-Buchstaben aus beliebigen Sprachen enthalten. Die Empfehlung dieses Blogs beschränkt die Wahl dennoch auf Buchstaben des lateinischen Alphabetes [a-zA-Z] sowie die Ziffern [0-9], sofern sich die Verwendung von Ziffern nicht vermeiden lässt. Damit reduzieren sich die Regeln für einen guten Bezeichner auf zwei:

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

Doppelte Anführungszeichen sind die Form, die der SQL-Standard für Bezeichnungsbegrenzer vorsieht. Microsoft erlaubt, abweichend vom Standard, zusätzlich eckige Klammern. Dieser Blog bevorzugt die eckigen Klammern als proprietäre Ausprägung des Bezeichnungsbegrenzers.

Bezeichnungsbegrenzer grenzen Bezeichner visuell deutlich von anderen Sprachelementen eines SQL-Statements ab und tragen damit wesentlich zur Lesbarkeit bei. Deshalb setzt die Konvention dieses Blogs Bezeichnungsbegrenzer grundsätzlich, unabhängig davon, ob ein Bezeichner regulär ist oder nicht:

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

Das ist eine bewusste Hauskonvention, keine allgemeine Best Practice: Bei regulären Bezeichnern sind die Begrenzer laut Microsoft-Dokumentation optional, und viele Teams quoten nur dort, wo es nötig ist. Wer sich für das durchgängige Quoten entscheidet, gewinnt die visuelle Abgrenzung — um den Preis von mehr Zeichen pro Bezeichner.

Das folgende Statement entspricht exakt dem zweiten Statement aus dem Überblick. Die Bezeichner sind jedoch nicht mit Bezeichnungsbegrenzern notiert:

  1: SELECT 
  2:     CASE Model 
  3:        WHEN 'Mountain-100' THEN 'M200' 
  4:        WHEN 'Road-150'     THEN 'R250' 
  5:        WHEN 'Road-650'     THEN 'R750' 
  6:        WHEN 'Touring-1000' THEN 'T1000' 
  7:        ELSE LEFT(Model, 1) + RIGHT(Model, 3) 
  8:     END + ' ' + Region AS ModelRegion 
  9:    ,(CONVERT(int, CalendarYear) * 100) + CONVERT(int, Month) AS TimeIndex 
 10:    ,SUM(Quantity)  AS Quantity 
 11:    ,SUM(Amount)    AS Amount
 12:    ,CalendarYear
 13:    ,Month
 14:    ,dbo.udfBuildISO8601Date(CalendarYear, Month, 25) AS ReportingDate
 15: FROM 
 16:    dbo.vDMPrep 
 17: WHERE 
 18:    Model IN ('Mountain-100', 'Mountain-200', 'Road-150', 'Road-250', 'Road-650', 'Road-750', 'Touring-1000') 
 19: GROUP BY 
 20:     CASE Model 
 21:        WHEN 'Mountain-100' THEN 'M200' 
 22:        WHEN 'Road-150'     THEN 'R250' 
 23:        WHEN 'Road-650'     THEN 'R750' 
 24:        WHEN 'Touring-1000' THEN 'T1000' 
 25:        ELSE LEFT(Model, 1) + RIGHT(Model, 3) 
 26:     END + ' ' + Region
 27:    ,(CONVERT(int, CalendarYear) * 100) + CONVERT(int, Month)
 28:    ,CalendarYear
 29:    ,Month
 30:    ,dbo.udfBuildISO8601Date(CalendarYear, Month, 25);

Zahlreiche automatisiert erstellte Skripte aus Microsoft-Werkzeugen verwenden als Bezeichnungsbegrenzer die eckigen Klammern. Eine dokumentierte Empfehlung ist das nicht, wohl aber ein beobachtbares Muster: SQL Server Management Studio (SSMS) setzt eckige Klammern bei automatisch generierten SELECT- und DDL-Statements ein (über das Kontextmenü zu einer Tabelle). Microsoft ist hier leider nicht sehr konsequent und verzichtet bei der Erstellung von Views über den Wizard sowie im 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

Der SSMS-View-Designer: oben ein Tabellen-Diagramm aus vier Boxen (f, p, d, c) mit Spalten-Listen und Join-Linien, in der Mitte ein Spalten-Grid mit Einträgen wie „EnglishProductCategoryName" und „COALESCE (p.ModelName, …)", unten das automatisch generierte SELECT-Statement ohne Bezeichnungsbegrenzer.

Edit Feature

Das SSMS-Edit-Feature: oben ein automatisch generiertes SELECT TOP (200) über DimDate ohne Bezeichnungsbegrenzer, unten ein Daten-Grid mit fünf Beispielzeilen (DateKey 20050101–20050105) mit englischen, spanischen und französischen Wochentagsnamen.

Für Anführungszeichen als Bezeichnungsbegrenzer gilt übrigens eine Einschränkung: Sie funktionieren nur, wenn die SQL-Server-Einstellung `QUOTED_IDENTIFIER` auf `ON` gesetzt ist (der Default in SSMS und den gängigen Client-Bibliotheken):

SET QUOTED_IDENTIFIER ON;

Steht die Einstellung auf `OFF`, interpretiert SQL Server doppelte Anführungszeichen als String-Literale, und nicht reguläre Bezeichner lassen sich nur noch mit eckigen Klammern notieren. Die eckigen Klammern funktionieren unabhängig von dieser Einstellung — ein praktischer Vorteil der proprietären Form. `ON` ist dabei mehr als eine Stilfrage: Mehrere SQL-Server-Features setzen die Einstellung zwingend voraus, darunter indizierte Views, Indizes auf berechneten Spalten und gefilterte Indizes.

Details in der Online-Dokumentation:

learn.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql

Der Spacer

Gemeint ist hier der vertikale Spacer, also die Leerzeile. 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. Daraus ergibt sich eine besondere Herausforderung beim Schreiben guter SQL-Statements. Ein wesentliches Kriterium für die schnelle Erfassung von Struktur und Aufgabe eines Statements ist nicht nur eine klare Gliederung und Formatierung, 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 geöffnet sind. Im Normalfall sind es eher 25 bis 30 Zeilen.

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

Der SQL-Standard sieht das Semikolon als Abschluss einer SQL-Anweisung vor. 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 Semikolons 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. Formal gehört das Semikolon dabei zum vorhergehenden Statement — ;WITH ist ein Kompatibilitäts-Idiom, keine eigene Syntax.

Andere Engines sind hier strenger — genauer gesagt ihre Werkzeuge und Prozedursprachen. Ein einzelnes Statement, das ein Treiber direkt an Postgres übergibt, funktioniert auch ohne abschließendes Semikolon. In psql und in Mehr-Statement-Skripten (etwa pg_dump-Ausgaben) markiert das Semikolon dagegen das Statement-Ende, und in PL/pgSQL-Funktionsrümpfen ist es als Statement-Terminator Teil der Syntax. Bei Oracle gilt dasselbe Muster: In PL/SQL-Blöcken (BEGIN … END;) ist das Semikolon zwingend, in Clients wie SQL*Plus hängt seine Rolle vom Ausführungskontext ab.

Die Verwendung des Semikolons zeugt in jedem Fall von Sorgfalt und fördert die Lesbarkeit. Portabel macht das Semikolon ein Statement allerdings nicht — SQL-Dialekte unterscheiden sich an ganz anderen Stellen, von Datentypen über Funktionen bis zur Prozedursprache. Es beseitigt aber eine unnötige syntaktische Abhängigkeit vom jeweiligen Client und Ausführungskontext.

Das Komma

Feldlisten sind in SQL durch ein Komma zu trennen. Es gibt 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 der Blockauswahl (in SSMS auch „Spalten-Editor“ oder „Spaltenauswahl“, in VS Code und DataGrip „Box Selection“). Mehr zur Blockauswahl steht im Artikel Funktionale Ästhetik von SQL.

Komma vor dem Feldnamen

  1: SELECT 
  2:     [EnglishDayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek]
  4: FROM 
  5:    [dbo].[DimDate];

Wenn als weiteres Feld FrenchDayNameOfWeek hinzukommen soll, fällt es leichter, das Feld am Ende hinzuzufügen — nur der Text ,[FrenchDayNameOfWeek] ist hinter [SpanishDayNameOfWeek] einzufügen:

  1: SELECT 
  2:     [EnglishDayNameOfWeek]
  3:    ,[SpanishDayNameOfWeek]
  4:    ,[FrenchDayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

Soll das Feld als erstes eingefügt werden, sind zwei Änderungen nötig: vor [EnglishDayNameOfWeek] kommt die Zeile [FrenchDayNameOfWeek], und vor [EnglishDayNameOfWeek] wird ein Komma eingefügt:

  1: SELECT 
  2:     [FrenchDayNameOfWeek]
  3:    ,[EnglishDayNameOfWeek]
  4:    ,[SpanishDayNameOfWeek]
  5: FROM 
  6:    [dbo].[DimDate];

Komma nach dem Feldnamen

Wenn das Komma hinter den Feldnamen geschrieben wird, stellt 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 [ProductName] ein Feldname ist oder eine andere Bedeutung hat. Der Leser muss die Zeile oberhalb lesen, um festzustellen, dass [ProductName] als Alias zu [EnglishProductName] gehört.

  1: SELECT 
  2:     [EnglishProductName] AS
  3:     [ProductName],
  4:     [Size],
  5:     [Color],
  6:     [ListPrice],
  7:     [DealerPrice]
  8: FROM 
  9:    [dbo].[DimProduct];

Anders stellt es sich dar, wenn das Komma vor die Feldnamen geschrieben wird. In diesem Fall ist sofort ersichtlich, dass [ProductName] kein eigenständiger Feldname ist, da ihm kein Komma vorangestellt ist:

  1: SELECT 
  2:     [EnglishProductName] AS
  3:     [ProductName]
  4:    ,[Size]
  5:    ,[Color]
  6:    ,[ListPrice]
  7:    ,[DealerPrice]
  8: FROM 
  9:    [dbo].[DimProduct];

Das Komma und die Blockauswahl

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

  1: SELECT 
  2:     [EnglishProductName]
  3:    ,[Size]
  4:    ,[Color]
  5:    ,[ListPrice]
  6:    ,[DealerPrice]
  7: FROM 
  8:    [dbo].[DimProduct];

Jedes Komma muss um mindestens eine Position nach rechts verschoben werden, davor sind das Schlüsselwort AS und der Alias einzufügen. Das gilt für jede Zeile der Feldliste, einzeln und von Hand:

  1: SELECT 
  2:     [EnglishProductName] AS [AliasEnglishProductName]
  3:    ,[Size] AS [AliasSize]
  4:    ,[Color] AS [AliasColor]
  5:    ,[ListPrice] AS [AliasListPrice]
  6:    ,[DealerPrice] AS [AliasDealerPrice]
  7: FROM 
  8:    [dbo].[DimProduct];

Derselbe Endzustand lässt sich mit erheblich weniger Aufwand erreichen, wenn das SQL-Statement so formatiert ist, dass es sich mit der Blockauswahl bearbeiten lässt:

SSMS-Editor mit dem DimProduct-SELECT in blockauswahl-tauglicher Form: rechts neben jeder Spalte stehen die ausgerichteten Aliase AS [AliasEnglishProductName], AS [AliasSize], AS [AliasColor] und weitere. Das Präfix Alias ist über alle fünf Zeilen hinweg als blau hinterlegter Block-Selektions-Bereich markiert — eine einmalige Eingabe wirkt gleichzeitig in allen Zeilen.

Es sind keine Kommata zu verschieben. Mit der Blockauswahl wird AS nur einmal getippt, die Feldnamen werden als Basis für die Alias-Vergabe genutzt und als Block hinter AS kopiert, um sie anschließend — ebenfalls über die Blockauswahl — um den Präfix Alias zu erweitern. Das Beste an dieser Vorgehensweise: Der Aufwand ist weitgehend unabhängig von der Anzahl der zu bearbeitenden Zeilen.

Die effiziente Verwendung der Blockauswahl ist erst mit vorangestellten Kommata möglich. Genau das ist das Killer-Argument für das vorangestellte Komma.

Caveat zum SSMS-Bezug. Diese Argumentation bezieht sich überwiegend auf SSMS und dessen klassische Blockauswahl. DataGrip und sicherlich andere moderne Editoren bieten mächtigere Refactoring-Werkzeuge — Multi-Cursor mit beliebigen Positionen, automatische Alias-Vergabe per Refactoring-Befehl, semantisches Suchen-und-Ersetzen über die ganze Codebasis. Wer in dieser Welt arbeitet, spart den gleichen Aufwand auch ohne vorangestelltes Komma. Das Blockauswahl-Argument greift dort nur eingeschränkt. Die Lesbarkeits-Argumente (siehe Sub-Sektion „Das Komma und die Lesbarkeit“) tragen dagegen unabhängig vom Editor.

Funktionsnamen

Funktionsnamen werden in SSMS durch rosafarbene Buchstaben hervorgehoben und sind damit leichter 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. Ein verbindlicher Standard ist diese Dokumentations-Schreibweise nicht, aber eine bequeme Orientierung — die Konvention dieses Blogs folgt der Großschreibung.

Tabellen-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äfixes Fact). Da IS ein reserviertes Wort ist, muss dieser Alias zwingend mit Bezeichnungsbegrenzern umschlossen werden. Das Beispiel zeigt zugleich eine Schwäche mnemonischer Kürzel: Sie können mit reservierten Wörtern kollidieren und erzwingen dann zusätzliches Quoting. Für andere Tabellen könnten folgende Aliase abgeleitet werden:

TabelleAlias
DimCustomerCUST
DimProductP
DimProductCategoryPC
DimProductSubcategoryPSC

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

  1: SELECT
  2:     CUST.[LastName]
  3:    ,CUST.[FirstName]
  4:    ,P.[EnglishProductName]
  5:    ,PC.[EnglishProductCategoryName]
  6:    ,PSC.[EnglishProductSubCategoryName]
  7:    ,[IS].[OrderDate]
  8: FROM
  9:    [dbo].[FactInternetSales] [IS]
 10:    LEFT JOIN [dbo].[DimCustomer] CUST
 11:    ON
 12:      [IS].[CustomerKey] = CUST.[CustomerKey]
 13:    LEFT JOIN [dbo].[DimProduct] P
 14:    ON
 15:      [IS].[ProductKey] = P.[ProductKey]
 16:    LEFT JOIN [dbo].[DimProductSubcategory] PSC
 17:    ON
 18:      P.[ProductSubcategoryKey] = PSC.[ProductSubcategoryKey]
 19:    LEFT JOIN [dbo].[DimProductCategory] PC
 20:    ON
 21:      PC.[ProductCategoryKey] = PSC.[ProductCategoryKey];

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. Ab ungefähr dem fünften Alias — das ist eine Faustregel, keine harte Grenze — dürfte die Herleitung des Alias aus Tabellennamen die Lesbarkeit 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-TabelleD für Dimension etc.), gefolgt von einer zweistelligen 1-basierten Indizierung, ergeben sich Aliase wie T01T02D01F01, …

Unter Verwendung dieser Aliase erscheint das obige Statement lesbarer:

  1: SELECT
  2:     T02.[LastName]
  3:    ,T02.[FirstName]
  4:    ,T03.[EnglishProductName]
  5:    ,T05.[EnglishProductCategoryName]
  6:    ,T04.[EnglishProductSubCategoryName]
  7:    ,T01.[OrderDate]
  8: FROM
  9:    [dbo].[FactInternetSales] T01
 10:    LEFT JOIN [dbo].[DimCustomer] T02
 11:    ON
 12:      T01.[CustomerKey] = T02.[CustomerKey]
 13:    LEFT JOIN [dbo].[DimProduct] T03
 14:    ON
 15:      T01.[ProductKey] = T03.[ProductKey]
 16:    LEFT JOIN [dbo].[DimProductSubcategory] T04
 17:    ON
 18:      T03.[ProductSubcategoryKey] = T04.[ProductSubcategoryKey]
 19:    LEFT JOIN [dbo].[DimProductCategory] T05
 20:    ON
 21:      T05.[ProductCategoryKey] = T04.[ProductCategoryKey];

Die Feldnamen innerhalb der SELECT-Liste sind sauber ausgerichtet.

Das eigentliche Killer-Argument ist auch hier: Die Notation gleich langer Aliase ermöglicht erst den Einsatz der Blockauswahl.

Neben diesem Killer-Argument gibt es aber noch einen anderen gewichtigen Grund, überhaupt Aliase zu verwenden — er gilt für sprechende wie systematische gleichermaßen: Erst durch die Verwendung von Aliasen kann das Feature Intellisense (in SSMS) effizient genutzt werden. Notiert der Entwickler einen Alias, gefolgt von einem Punkt, dann öffnet sich ein Kontextmenü, das nur die zu dem Alias gehörenden Feldnamen auflistet.

Das Kontextmenü lässt sich — wenn der Cursor hinter dem Punkt steht — auch über die Tastenkombination Ctrl+Leertaste manuell aufrufen. Es funktioniert ebenfalls ohne vorangestellten Alias. Allerdings bietet SSMS dann mehr oder minder den gesamten Sprachschatz von T-SQL zur Auswahl an.

Das SSMS-Intellisense-Kontextmenü öffnet sich nach dem Tippen von T01. — es zeigt nur die Spalten der zum Alias gehörenden Tabelle FactInternetSales (CarrierTrackingNumber hervorgehoben, CurrencyKey, CustomerKey, CustomerPONumber, DiscountAmount, DueDate, DueDateKey, ExtendedAmount, Freight, …). Rechts ein Tooltip mit dem Datentyp der hervorgehobenen Spalte: „Spalte CarrierTrackingNumber(nvarchar, null)". Tastenkürzel zum manuellen Aufruf: Ctrl+Leertaste.

Aliase machen SQL-Code leserlicher und damit wartbar. Bei Statements über mehrere Tabellen sind sie praktisch unverzichtbar. Die Konvention dieses Blogs setzt sie darüber hinaus auch bei einfachen Ein-Tabellen-Statements — der Einheitlichkeit und des Intellisense-Komforts wegen.

Qualifizierte Feldnamen

Werden in einem Statement mehrere Tabellen referenziert, tritt schnell der Fall ein, dass gleichnamige Feldnamen aus verschiedenen Tabellen nicht mehr eindeutig sind. Im folgenden Beispiel sollen zu einem Fakt FactResellerSales Name und Telefonnummer sowohl des Mitarbeiters als auch des Vertriebspartners ausgegeben werden:

SSMS-Editor mit dem FactResellerSales-SELECT: in der Feldliste tauchen [Phone] AS [Employee_Phone] und [Phone] AS [Reseller_Phone] zweimal ohne Tabellen-Alias-Präfix auf. Die FROM-Klausel verwendet die T01/T02/T03-Aliase, die im SELECT aber nicht referenziert werden — das Statement ist mehrdeutig und nicht ausführbar.

Das Statement ist nicht ausführbar, da die Spalte Phone in beiden Dimensionen DimEmployee und DimReseller vorhanden ist. SQL Server bricht mit zwei Fehlern ab:

  1: Meldung 209, Ebene 16, Status 1, Zeile 4
  2: Ambiguous column name 'Phone'.
  3: Meldung 209, Ebene 16, Status 1, Zeile 6
  4: Ambiguous column name 'Phone'.

Die Angabe eines Tabellen-Alias vor dem Feldnamen ist hier zwingend erforderlich:

T01.[Phone] AS [Employee_Phone]

oder — sofern keine Tabellen-Aliase verwendet werden — ist der Tabellenname selbst dem Feld voranzustellen:

[DimEmployee].[Phone] AS [Employee_Phone]

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

Ein vollständig qualifizierter Objektname besteht in SQL Server aus bis zu vier Teilen: Server, Datenbank, Schema und Objekt. Eine Spaltenreferenz ergänzt das Feld als fünftes Element:

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

Ein Beispiel mit voll qualifizierten Tabellennamen, die den Datenbanknamen mit aufnehmen:

SSMS-Editor mit demselben FactResellerSales-SELECT wie zuvor, jetzt aber mit voll qualifizierten Tabellennamen [AdventureWorksDW2017].[dbo].[FactResellerSales] T01, [AdventureWorksDW2017].[dbo].[DimEmployee] T02 und [AdventureWorksDW2017].[dbo].[DimReseller] T03 in der FROM-/JOIN-Klausel — die Datenbank ist im Tabellennamen verankert und bindet das Statement an diese Datenbank.

Eine über das Schema hinausgehende Qualifizierung verhindert die Portierbarkeit der Anwendung in eine Datenbank, die nicht denselben Namen AdventureWorksDW2017 trägt. Solche Fälle gibt es in der Praxis tatsächlich: ETL-Strecken, deren Tabellen bis zum Datenbanknamen voll qualifiziert waren ([Datenbank].[Schema].[Tabelle].[Feld]), ließen sich nach Fertigstellung nicht in der Produktion deployen.

Engine-Hinweis: Postgres. Das Fünf-Teile-Schema Server.Datenbank.Schema.Tabelle.Feld gilt so nur in SQL Server. Postgres erlaubt standardmäßig keine datenbankübergreifenden Abfragen: Ein Cluster enthält zwar mehrere Datenbanken, eine Connection ist aber genau einer zugeordnet — das Datenbank.-Element existiert in der Praxis nicht. Wer dennoch über Datenbankgrenzen hinweg lesen oder schreiben muss, braucht die Extensions postgres_fdw (Foreign Data Wrapper) oder dblink. Beides ist Zusatz-Setup und kein Default. Praktisch bedeutet das: Innerhalb einer Postgres-Datenbank ist Schema.Tabelle.Feld (drei Teile) die relevante Qualifizierung, in SQL Server eine von mehreren Varianten. Die Empfehlung bleibt in beiden Welten dieselbe: nie über die Schema-Ebene hinaus qualifizieren.

Fazit

Eine Namenskonvention ist der Einstieg in wartbaren SQL-Code: reguläre Bezeichner aus Buchstaben und hilfsweise Ziffern, konsequent gesetzte Bezeichnungsbegrenzer, großgeschriebene Funktionsnamen. Die kleinen Trenner entfalten dabei die größte Wirkung — das vorangestellte Komma macht Feldlisten lesbar und blockauswahl-tauglich, das Semikolon macht Statements eindeutig gegenüber Client und Ausführungskontext, sparsame Leerzeilen halten den Gesamtkontext auf einem Bildschirm.

Systematische Aliase wie T01 spielen ihre Stärke in breiten Statements aus und richten die Feldliste sauber aus. Qualifizierte Feldnamen gehören bis zur Schema-Ebene, nicht darüber hinaus. Die Wahl der einzelnen Regel ist dabei zweitrangig: Wartbar wird SQL durch eine konsistente Konvention, die Struktur sichtbar macht und Änderungen erleichtert. Teil 2 — Strukturierung und Formatierung setzt darauf auf und behandelt die Strukturierung längerer Statements.

FAQ

Warum vorangestelltes Komma — ist das nicht ungewohnt?

Beim Lesen verliert das Komma seinen trennenden Charakter, wenn es hinter dem Feldnamen steht (siehe Sektion „Das Komma und die Lesbarkeit“). Beim Schreiben spart vorangestelltes Komma den meisten Aufwand, sobald die Blockauswahl ins Spiel kommt. Moderne Auto-Formatter machen die Komma-Position konfigurierbar. sqlfluff (T-SQL und Postgres) setzt standardmäßig nachgestellte Kommata und wechselt erst per Layout-Konfiguration (line_position = leading) auf vorangestellte. Auch pgFormatter (Postgres) hat das nachgestellte Komma als Default und bietet für vorangestellte die Option --comma-start.

Brauche ich eckige Klammern auch in Postgres?

Nein. Postgres unterstützt keine [brackets]. Delimiter sind dort doppelte Anführungszeichen: "EnglishProductName". Ein QUOTED_IDENTIFIER-Setting wie in T-SQL gibt es nicht. Identifier-Quoting ist in Postgres immer aktiv. Bezeichner ohne Quotes faltet Postgres auf Kleinschreibung (MyColumn wird zu mycolumn). Bezeichner in Quotes behalten ihre Schreibweise und müssen bei jeder Referenz exakt so zitiert werden. Wenn die Codebasis langfristig Multi-Engine sein soll, ist "double quotes" ANSI-SQL-konform und funktioniert in beiden Welten — die [brackets] sind SQL-Server-spezifisch.

Systematische T01-Aliase vs. sprechende Aliase — was ist besser?

Das ist ein Trade-off: Sprechende Aliase (CUSTPPC) sind mnemonisch und tragen so lange, wie ein Statement nur drei bis vier Tabellen hat. Sobald 5+ Tabellen ins Spiel kommen — typisch bei ETL-Strecken oder breiten Berichtsabfragen — wird die Mnemonik schwieriger und die Block-Ausrichtung leidet unter den unterschiedlichen Längen. Systematische T01-Aliase skalieren, bleiben gleich lang und ermöglichen die Blockauswahl ohne Aufwand. Der Preis ist semantisch: T05.[CustomerKey] verrät die Tabelle nicht. Wer sie wissen will, springt zur FROM-Klausel. Systematische Aliase optimieren also die visuelle Struktur und die mechanische Bearbeitung, sprechende Aliase die inhaltliche Orientierung. Als Faustregel dieses Blogs gilt: bis vier Tabellen sprechend, ab fünf systematisch.

Was, wenn mein Team eine andere Konvention hat?

Konsequenz schlägt Reinheit. Wenn das Team nachgestelltes Komma oder sprechende Aliase fest etabliert hat, ist das konsequente Anwenden dieser Konvention wichtiger als die Wahl der „besseren“ Variante.
In der Praxis ist das einer der schwierigsten Punkte überhaupt. In jedem größeren Team hat jede Entwicklerin und jeder Entwickler eine sehr persönliche Vorstellung davon, was „gut aussieht“ und was nicht. Genau diese individuelle Geschmacksfrage steht der teamweiten Einheitlichkeit im Weg — die Erfahrung aus vielen Projekten zeigt das immer wieder. Umso wichtiger ist es, dass die Namenskonvention, die das Team sich einmal gegeben hat, konsequent mitarbeiterübergreifend angewendet wird. Nur so bleiben SQL-Statements über alle Beteiligten hinweg lesbar und wartbar.

Multi-Cursor — ersetzt das die Blockauswahl?

In modernen Editoren wie VS Code, DataGrip und DBeaver gibt es echten Multi-Cursor (Ctrl+Alt+Pfeil-runter oder Alt+Klick), der über die rechteckige Blockauswahl hinausgeht: Er erlaubt beliebige Cursor-Positionen statt nur eines rechteckigen Spalten-Ausschnitts. SSMS bietet nur die klassische Blockauswahl (Alt+Shift+Pfeil). Azure Data Studio, früher ebenfalls in dieser Reihe, hat Microsoft zum 28. Februar 2026 eingestellt — der empfohlene Nachfolger ist VS Code mit der mssql-Erweiterung. Konzeptuell deckt Multi-Cursor das gleiche Bedürfnis ab und geht darüber hinaus. Die Block-Tauglichkeit der Formatierung bleibt trotzdem die Voraussetzung. Wer in mehreren Editoren arbeitet, formatiert besser einmal sauber statt einmal pro Tool.