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 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, Azure Data Studio, VS Code und DBeaver. Die SQL-Beispiele referenzieren AdventureWorksDW2017.
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, Datentypen kleingeschrieben — das Ganze 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 — keine vollständige Liste, sondern eine Anregung. Ein zweiter Teil schließt mit Best Practices für die Strukturierung von SQL-Statements an.
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
Die Definition eines regulären Bezeichners ist jedoch meines Erachtens zu weit gefasst.
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 — dem Default — nicht zwischen Groß- und Kleinschreibung: FactInternetSales und factinternetsales zeigen auf dieselbe Tabelle. Das 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 wäre damit nur über "double quotes" sichtbar — was beim Schreiben jedes Statements zur Last wird. In der Postgres-Welt ist snake_case deshalb Standard.
Persönlich habe ich zehn Jahre überwiegend mit SQL Server gearbeitet und war Verfechter von CamelCase. Seit dem Umstieg auf Postgres vor drei Jahren hat sich snake_case fest etabliert. Die Tiefen der Case-Sensitivity-Unterschiede zwischen den beiden Engines verdienen einen eigenen Artikel — der folgt. Für die Beispiele hier bleibe ich bei der SQL-Server-typischen CamelCase-Notation, die für AdventureWorksDW2017 ohnehin gegeben ist.
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 in der SQL-Server-Welt 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 @, # und „ vermeide ich komplett. Sie sind Buchstaben aus dem lateinischen Alphabet zu ähnlich und machen einen Bezeichner unleserlich. Zusätzlich sind sie in T-SQL syntaktisch reserviert: @ leitet eine Variable ein, # markiert eine temporäre Tabelle — beide Zeichen als Bestandteil eines regulären Spalten- oder Tabellen-Bezeichners zu verwenden, ist auf mehreren Ebenen riskant:
1: SELECT
2: [EnglishDayNameOfWeek] AS [English@DayNameOfWeek]
3: ,[SpanishDayNameOfWeek] AS [Spanish#DayNameOfWeek]
4: ,[FrenchDayNameOfWeek] AS [FrenchDayNameOfWeek]
5: FROM
6: [dbo].[DimDate];
Ich beschränke mich daher bei der Wahl eines Bezeichners auf Buchstaben 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 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);
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 sie auf OFF gesetzt, sind nicht reguläre Bezeichner unzulässig:
learn.microsoft.com/de-de/sql/t-sql/statements/set-quoted-identifier-transact-sql
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 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.
Andere Engines sind hier strenger. Postgres verlangt das Semikolon als Statement-Separator: in psql, in Migrationen mit pg_dump-Scripts oder in PL/pgSQL-Funktionsrümpfen ist es nicht optional, sondern Syntax. Auch Oracle behandelt das Semikolon als Statement-Terminator — in sqlplus und in PL/SQL-Blöcken (BEGIN … END;) ist es zwingend.
Die Verwendung des Semikolons zeugt in jedem Fall nicht nur von Sorgfalt — sie fördert die Lesbarkeit und erleichtert die Migration einer SQL-Anwendung in eine Datenbank eines anderen Providers.
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, Azure Data Studio und DataGrip „Box Selection“; siehe auch 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, 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 [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 [Alias] kein 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. Und das jeweils für jede der vier Datenzeilen — Zeile für Zeile, 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 — AS einmal tippen, Feldnamen im Block hinter AS kopieren, Block-Prefix Alias voranstellen, fertig:
![SSMS-Editor mit dem DimProduct-SELECT in Block-Auswahl-tauglicher Form: rechts neben jeder Spalte stehen die ausgerichteten Aliase `AS [AliasEnglishProductName]`, `AS [AliasSize]`, `AS [AliasColor]` …; 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.](https://sql.marcus-belz.de/wp-content/uploads/2018/05/post00070014.png)
Es sind keine Kommas 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 Kommas möglich — das absolute Killer-Argument für vorangestellte Kommas.
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 Block-Auswahl-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 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?
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ä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:
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. 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:
- Aliase sollen auf eine fixe Anzahl Zeichen begrenzt sein
- 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, ergeben sich Aliase wie T01, T02, D01, F01, …
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: 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 Kontext-Menü, 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.l an.

Aliase machen SQL-Code leserlicher und damit wartbar. Daher sollten auch bei einfacheren Statements immer Aliase verwendet werden.
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.](https://sql.marcus-belz.de/wp-content/uploads/2018/05/post00070018.png)
Das Statement ist nicht ausführbar, da die Spalte Phone in beiden herangejointen 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 voll qualifizierter Feldname besteht insgesamt aus 5 Elementen:
[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 macht das Statement an diese Datenbank gebunden.](https://sql.marcus-belz.de/wp-content/uploads/2018/05/post00070019.png)
Eine über das Schema hinausgehende Qualifizierung verhindert die Portierbarkeit der Anwendung in eine Datenbank, die nicht denselben Namen AdventureWorksDW2017 trägt. Ich habe schon ETL-Strecken gesehen, in denen Tabellen bis zum Datenbanknamen voll qualifiziert waren — [Datenbank].[Schema].[Tabelle].[Feld] — und die Schnittstelle war nach Fertigstellung nicht in der Produktion deploybar.
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 — eine Connection ist genau einer Datenbank 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. Praktische Konsequenz: in Postgres ist Schema.Tabelle.Feld (drei Teile) die maximale Qualifizierungstiefe, in SQL Server eine Variante — die Empfehlung bleibt in beiden Welten dieselbe: nie über die Schema-Ebene hinaus qualifizieren.
FAQ
Warum vorangestelltes Komma — ist das nicht ungewohnt?
Beim Lesen verliert es 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 (sqlfluff für T-SQL und Postgres, pgFormatter für Postgres) machen die Komma-Position konfigurierbar — comma_style = leading ist Standard für vorangestellte Kommata.
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 werden in Postgres case-insensitive zu lowercase gefaltet (MyColumn wird zu mycolumn), Bezeichner in Quotes bleiben case-sensitive. 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?
Trade-off: sprechende Aliase (CUST, P, PC) 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. Faustregel: bis 4 Tabellen sprechend, ab 5 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, das mir begegnet ist, hat jede Entwicklerin und jeder Entwickler eine sehr persönliche Vorstellung davon, was „gut aussieht“ und was nicht — und genau diese individuelle Geschmacksfrage steht der teamweiten Einheitlichkeit im Weg. Das ist in allen Unternehmen, in denen ich gearbeitet habe, ein großes Problem gewesen. Umso wichtiger ist es, dass die Namenskonvention, die das Team sich einmal gegeben hat, dann auch konsequent mitarbeiterübergreifend angewendet wird — denn nur so bleiben SQL-Statements über alle Beteiligten hinweg lesbar und wartbar.
Multi-Cursor — ersetzt das die Blockauswahl?
In modernen Editoren wie VS Code, Azure Data Studio, DataGrip und DBeaver gibt es echten Multi-Cursor (Ctrl+Alt+Pfeil-runter oder Alt+Klick), der über die rechteckige Blockauswahl hinausgeht — beliebige Cursor-Positionen, nicht nur Spalten-Ausschnitt. SSMS bietet nur die klassische Blockauswahl (Alt+Shift+Pfeil). Konzeptuell deckt Multi-Cursor das gleiche Bedürfnis ab und mehr; die Block-Tauglichkeit der Formatierung ist trotzdem die Voraussetzung. Wer in mehreren Editoren arbeitet, formatiert besser einmal sauber statt einmal pro Tool.