Wer ein 200-Zeilen-SELECT mit rekursiver CTE schreibt, versteht es beim Schreiben vollständig — und drei Wochen später kein Wort mehr davon. Inline-Kommentare sind das Sicherheitsnetz dagegen. Das Problem: schlecht gesetzt, zerstören sie genau die Lesbarkeit, die sie retten sollen.
Was dieser Artikel zeigt:
- Die zwei Arten von Inline-Kommentaren in T-SQL — zeilenbasiert (
--) und Block (/* */) — und wann welche. - Das Anti-Pattern: Kommentare, die das Statement zerreißen.
- Die parallele Inline-Dokumentation — ein rechtsbündiger Kommentar-Block, der das Statement kompakt lässt.
- Eine Vorlage, die sich als Ausgangspunkt für eigene Statements kopieren lässt.
- Warum dasselbe Muster in Postgres funktioniert — und wo der eine Unterschied liegt.
- Warum Kommentieren gerade bei AI-generiertem SQL wichtiger wird, nicht unwichtiger.
Voraussetzung: Die Beispiele laufen gegen AdventureWorksDW2017 (Tabelle [dbo].[DimEmployee], rekursive CTE über ParentEmployeeKey). SSMS dient als Beispiel-Editor — die Prinzipien gelten für jeden SQL-Editor.
Warum Inline-Dokumentation?
Über den Sinn von Dokumentation herrscht weitgehend Einigkeit — und trotzdem fällt sie als Erstes dem Projektdruck zum Opfer. „Schau doch in den Code“ ist keine Dokumentation: Nicht jeder kann SQL lesen, und Dokumentation richtet sich auch an den Fachbereich, das Projektmanagement und das Management. Die Inline-Dokumentation ist dabei das Netz mit doppeltem Boden — fehlt auch sie, ist ein Statement nicht mehr wartbar, sobald das Wissen aus den Köpfen der Entwickler verschwunden ist. Und dieses Wissen ist flüchtig: Schon nach wenigen Wochen fällt es selbst der Autorin schwer, ein komplexes Statement nachzuvollziehen; im Mehrentwickler-Projekt verschärft sich das zusätzlich.
Zwei Arten von Inline-Kommentaren
T-SQL kennt zwei Arten von Inline-Kommentaren: zeilenbasierte Kommentare und Block-Kommentare.
Zeilenbasierte Kommentare werden mit zwei aufeinanderfolgenden Bindestrichen eingeleitet; alles rechts davon gilt als Kommentar. Die Bindestriche dürfen an jeder Stelle einer Zeile stehen:
1: -- Inline-Kommentar
Erstreckt sich der Text über mehrere Zeilen, muss vor jeder Zeile die einleitende Sequenz stehen — bei vielen Zeilen mit etwas Aufwand verbunden.
Block-Kommentare beginnen mit einem Schrägstrich gefolgt von einem Stern und enden mit der umgekehrten Sequenz. Sie umschließen beliebige Bereiche:
1: /*
2: Block-Kommentar
3: */
Welche Variante besser ist, darüber wird — wie so oft — gestritten. Ich bevorzuge, trotz des Mehraufwands für die einleitende Sequenz, die zeilenbasierte Form: Gerade bei mehrzeiligen Texten grenzt das vorangestellte -- jeden Kommentar klar vom restlichen Statement ab; zusätzlich lässt sich die Sequenz als strukturierendes Element in einem komplexen Statement einsetzen.
Den Mehraufwand für die Bindestriche hält die Blockauswahl (in SSMS „Spaltenauswahl“, in VS Code und Azure Data Studio „Box Selection“) gering: Der Cursor wird über mehrere Zeilen aufgespannt, eine Eingabe wirkt auf alle Zeilen gleichzeitig — so lassen sich mehrere Zeilen auf einmal aus- oder wieder einkommentieren (siehe Die funktionale Ästhetik von SQL). Hilfreich sind außerdem die Tastenkombinationen Ctrl+K, Ctrl+C (Kommentieren, C für Comment) und Ctrl+K, Ctrl+U (Auskommentieren aufheben, U für Uncomment) — beide wirken auf die gesamte aktuelle Selektion.
Beispiel 1: uneinheitliche Inline-Dokumentation
Das folgende Beispiel zeigt eine Inline-Dokumentation, wie man sie häufig antrifft: keine einheitliche Verwendung der Kommentar-Funktionen, keine einheitliche Einrückung, jeder Kommentar belegt eine ganze Zeile. Trotz sauber formatiertem Statement ist es — wegen der unstrukturierten Dokumentation — schwer zu lesen. Die Komplexität steckt in einer rekursiven Common Table Expression (CTE) und in der Dokumentation dieses Features. Das Statement läuft in der Datenbank AdventureWorksDW2017.
1: WITH
2: CTE_Employee AS
3: (
4: -- This SELECT statement returns the anchor element of the recursive query. The
5: -- anchor element is the top level Employee of Adventure Works, the CEO.
6: SELECT
7: [EmployeeKey]
8: ,[FirstName]
9: ,[LastName]
10: ,[Title]
11: ,[ParentEmployeeKey]
12: ,[VacationHours]
13: ,[SickLeaveHours]
14: -- >> The field Level is used to calculate the hierarchy level of an employee.
15: -- The CEO is on Level 1. All top level managers are on Level 2.
16: -- Regional Managers, Technical supervisors etc. are on Level 3
17: ,1 AS [Level]
18: FROM
19: [dbo].[DimEmployee]
20: WHERE
21: [ParentEmployeeKey] IS NULL
22: -- >> The operator UNION ALL is the only operator allowed between the
23: -- anchor and the first recursive member.
24: UNION ALL
25: SELECT
26: T01.[EmployeeKey]
27: ,T01.[FirstName]
28: ,T01.[LastName]
29: ,T01.[Title]
30: ,T01.[ParentEmployeeKey]
31: ,T01.[VacationHours]
32: ,T01.[SickLeaveHours]
33: -- >> Increases the level for each recursion
34: ,T02.[Level] + 1 AS [Level]
35: FROM
36: [dbo].[DimEmployee] T01
37: INNER JOIN CTE_Employee T02
38: ON
39: T01.[ParentEmployeeKey] = T02.[EmployeeKey]
40: )
41: /* # If the recursive member query definition returns the same values for both
42: the parent and child columns, an infinite loop is created. To avoid an
43: infinite loop you can limit the number of recursions. By default SQL
44: Server limits the recursions to 100. The maximum number is limited to
45: 32767 recursions.
46: # The number of recursions can be limited with the option MAXRECURSION
47: # If the number of recursion exceeds the specified value for MAXRECURSION
48: SQL Server will throw an exception
49: # The option cannot be used within a CTE */
50: SELECT
51: [EmployeeKey]
52: ,[FirstName]
53: ,[LastName]
54: ,[Title]
55: ,[ParentEmployeeKey]
56: ,[Level]
57: ,[VacationHours]
58: ,[SickLeaveHours]
59: -- >> Classifies the vacation hours by Level (3 levels)
60: ,NTILE(3)
61: OVER (PARTITION BY [Level]
62: ORDER BY [VacationHours]
63: ) AS [VacationHours_NTILE]
64: -- >> Orders the vacation hours by Level
65: ,DENSE_RANK()
66: OVER (PARTITION BY [Level]
67: ORDER BY [VacationHours]
68: ) AS [VacationHours_DENSE_RANK]
69: -- >> Classifies the sick leave hours by Level (3 levels)
70: ,NTILE(3)
71: OVER (PARTITION BY [Level]
72: ORDER BY [SickLeaveHours]
73: ) AS [SickLeaveHours_NTILE]
74: -- >> Orders the sick leave hours by Level
75: ,DENSE_RANK()
76: OVER (PARTITION BY [Level]
77: ORDER BY [SickLeaveHours]
78: ) AS [SickLeaveHours_DENSE_RANK]
79: FROM
80: CTE_Employee
81: -- >> Limits the maximum number of recursions to 5 recursions
82: -- OPTION (MAXRECURSION 5)
83: --WHERE
84: -- [Level] = 2
85: ORDER BY
86: [Level] ASC;Beispiel 2: parallele Inline-Dokumentation
Dasselbe Statement, überarbeitet mit einer parallelen Inline-Dokumentation: Rechts neben dem Statement liegt ein eigener Block für die Dokumentation, eingerichtet über die zeilenbasierte Kommentar-Sequenz (--). Die Sequenzen werden über alle Zeilen linksbündig ausgerichtet.
1: WITH
2: CTE_Employee AS -- --------------------------------------------------------------------------------
3: ( --
4: SELECT -- This SELECT statement returns the anchor element of the recursive query. The
5: [EmployeeKey] -- anchor element is the top level Employee of Adventure Works, the CEO.
6: ,[FirstName] --
7: ,[LastName] --
8: ,[Title] --
9: ,[ParentEmployeeKey] --
10: ,[VacationHours] --
11: ,[SickLeaveHours] --
12: ,1 AS [Level] -- >> The field Level is used to calculate the hierarchy level of an employee.
13: FROM -- The CEO is on Level 1. All top level managers are on Level 2.
14: [dbo].[DimEmployee] -- Regional Managers, Technical supervisors etc. are on Level 3
15: WHERE --
16: [ParentEmployeeKey] IS NULL --
17: UNION ALL -- >> The operator UNION ALL is the only operator allowed between the
18: SELECT -- anchor and the first recursive member.
19: T01.[EmployeeKey] --
20: ,T01.[FirstName] --
21: ,T01.[LastName] --
22: ,T01.[Title] --
23: ,T01.[ParentEmployeeKey] --
24: ,T01.[VacationHours] --
25: ,T01.[SickLeaveHours] --
26: ,T02.[Level] + 1 AS [Level] -- >> Increases the level for each recursion
27: FROM --
28: [dbo].[DimEmployee] T01 --
29: INNER JOIN CTE_Employee T02 --
30: ON --
31: T01.[ParentEmployeeKey] = T02.[EmployeeKey] --
32: ) --
33: SELECT -- # If the recursive member query definition returns the same values for both
34: [EmployeeKey] -- the parent and child columns, an infinite loop is created. To avoid an
35: ,[FirstName] -- infinite loop you can limit the number of recursions. By default SQL
36: ,[LastName] -- Server limits the recursions to 100. The maximum number is limited to
37: ,[Title] -- 32767 recursions.
38: ,[ParentEmployeeKey] -- # The number of recursions can be limited with the option MAXRECURSION
39: ,[Level] -- # If the number of recursion exceeds the specified value for MAXRECURSION
40: ,[VacationHours] -- SQL Server will throw an exception
41: ,[SickLeaveHours] -- # The option cannot be used within a CTE
42: -- # More information on recursive CTEs you can find in the Online Documentation
43: --
44: ,NTILE(3) -- >> Classifies the vacation hours by Level (3 levels)
45: OVER (PARTITION BY [Level] -- - PARTITION clause
46: ORDER BY [VacationHours] -- - ORDER BY clause
47: ) AS [VacationHours_NTILE] --
48: ,DENSE_RANK() -- >> Orders the vacation hours by Level
49: OVER (PARTITION BY [Level] -- - PARTITION clause
50: ORDER BY [VacationHours] -- - ORDER BY clause
51: ) AS [VacationHours_DENSE_RANK] --
52: ,NTILE(3) -- >> Classifies the sick leave hours by Level (3 levels)
53: OVER (PARTITION BY [Level] -- - PARTITION clause
54: ORDER BY [SickLeaveHours] -- - ORDER BY clause
55: ) AS [SickLeaveHours_NTILE] --
56: ,DENSE_RANK() -- >> Orders the sick leave hours by Level
57: OVER (PARTITION BY [Level] -- - PARTITION clause
58: ORDER BY [SickLeaveHours] -- - ORDER BY clause
59: ) AS [SickLeaveHours_DENSE_RANK] --
60: FROM --
61: CTE_Employee --
62: -- OPTION (MAXRECURSION 5) -- >> Limits the maximum number of recursions to 5 recursions
63: ORDER BY --
64: [Level] ASC; --Diese Variante hat mehrere Eigenschaften:
- Das SQL-Statement bleibt kompakt und gut lesbar.
- Die Inline-Kommentare unterbrechen das Statement nicht und behindern weder Lesbarkeit noch Verständlichkeit.
- Besteht ein direkter Bezug zwischen dem Code und dem Kommentar in derselben Zeile, lässt sich dieser Bezug durch eine vorangestellte Zeichenfolge kenntlich machen (z. B.
>>). - Längere Kommentare sollten über Spiegelpunkte gegliedert und nicht als Fließtext eingefügt werden; als Spiegelpunkt-Zeichen eignet sich das Hash-Zeichen (
#). - Ein Kommentar sollte nicht zu lang werden, damit der Leser nicht übermäßig horizontal navigieren muss. Längere Kommentare umbrechen und linksbündig zur vorigen Zeile einrücken — vertikale Navigation (Mausrad,
Bild auf– undBild ab-Taste) ist deutlich leichter als horizontale.
Beispiel 3: eine Vorlage für die eigene Inline-Dokumentation
Im letzten Beispiel sind die wesentlichen Eigenschaften der parallelen Inline-Dokumentation aus Beispiel 2 selbst als Kommentar eingefügt — eine Vorlage, die sich als Ausgangspunkt für eigene Statements kopieren lässt.
1: -- --------------------------------------------------------------------------------
2: -- Section Header
3: -- --------------------------------------------------------------------------------
4: -- # Use a section header to describe the overall intention of the following
5: -- SQL statement.
6: -- # Use bullet points to structure the inline documentation.
7: -- > If necessary, you can use bullet points for sub-items, too
8: -- > ...
9: -- # In case of complex transformations add links to online document
10: -- https://onedrive.live.com/?id=root&cid=ABCDEF0123456789
11: -- --------------------------------------------------------------------------------
12: SELECT -- --------------------------------------------------------------------------------
13: [EmployeeKey] -- # Inline documentation that occupies complete lines and that interrupts more or
14: ,[FirstName] -- less the readability of a SQL statement can affect the comprehensibility of
15: ,[LastName] -- the statement.
16: ,[Title] -- # An essential criterion for the understanding of an SQL statement is not only a
17: ,[ParentEmployeeKey] -- clear structure and formatting of the statement, but also whether the
18: ,[VacationHours] -- statement is compact enough to get the major task on a short glance at the
19: ,[SickLeaveHours] -- statement.
20: -- # Use bullet points, too, for the parallel inline documentation
21: -- # It is easier to navigate vertically through a document than horizontally.
22: -- Keeping that in mind, limit the maximum length of inline documentation
23: -- to e.g. 80 characters as in this example.
24: -- # It may help to insert a separating line with 80 characters as an
25: -- orientation for the maximum length
26: -- 1-----------------------------------------------------------------------------80
27: ,NTILE(3) -- >> If the inline documentation refers exactly to the line of code on the left
28: OVER (PARTITION BY [Level] -- side, you should mark the documentation with for example the characters '>>'
29: ORDER BY [VacationHours] --
30: ) AS [VacationHours_NTILE] --
31: -- # If the inline documentation needs more lines than the SQL statement, just add
32: -- these lines and leave the left part of the documentation blank. Blank lines
33: -- do not affect the readability that much as inline documentation, that occupies
34: -- complete lines.
35: ,DENSE_RANK() -- >> This documentation would refer to the command DENSE_RANK()
36: OVER (PARTITION BY [Level] -- >> This documentation would explain the PARTITION clause
37: ORDER BY [VacationHours] -- >> ...and this documentation the ORDER BY statement
38: ) AS [VacationHours_DENSE_RANK] --
39: ,NTILE(3) --
40: OVER (PARTITION BY [Level] --
41: ORDER BY [SickLeaveHours] --
42: ) AS [SickLeaveHours_NTILE] --
43: ,DENSE_RANK() --
44: OVER (PARTITION BY [Level] --
45: ORDER BY [SickLeaveHours] --
46: ) AS [SickLeaveHours_DENSE_RANK] --
47: FROM --
48: [dbo].[DimEmployee]; --Funktioniert das auch in Postgres?
Ja — und zwar fast unverändert. Die beiden Kommentar-Syntaxen sind in PostgreSQL identisch zu T-SQL: -- leitet einen zeilenbasierten Kommentar ein, /* … */ umschließt einen Block. Die parallele Inline-Dokumentation ist rein textuell und damit komplett engine-unabhängig — der rechtsbündige Kommentar-Block aus Beispiel 2 lässt sich 1:1 auf ein Postgres-Statement übertragen.
Ein Detail, das viele anders erwarten, verhält sich in beiden Engines sogar gleich: Block-Kommentare dürfen verschachtelt werden (wie es der SQL-Standard vorsieht). Sowohl SQL Server als auch PostgreSQL behandeln jedes innere /* als eigenen Kommentar, der sein eigenes */ braucht — praktisch, um größere Code-Blöcke auszukommentieren, die bereits Block-Kommentare enthalten. Die parallele Inline-Dokumentation lässt sich also bedenkenlos in beiden Welten einsetzen. Die Kommentar-Shortcuts unterscheiden sich übrigens nach Editor, nicht nach Engine — dazu gleich mehr.
Kommentieren ist Verstehen — gerade bei AI-generiertem SQL
Inline-Kommentare sind nicht nur Dokumentation für später, sie sind ein Werkzeug zum Verstehen im Moment. Wer einen Kommentar wie „>> begrenzt die Rekursion auf Ebene 2″ formuliert, muss das Statement vollständig gelesen und die Beziehungen zwischen den Tabellen mental aufgebaut haben. Der Akt des Kommentierens erzwingt das Verständnis — ähnlich wie das manuelle Formatieren.
Im Zeitalter von Copilot und Cursor ist das doppelt relevant. Ein KI-Assistent liefert in Sekunden ein syntaktisch korrektes Statement — aber ohne fachliche Begründung, warum es genau so aussieht. Das Risiko ist nicht falscher Code, sondern technisch korrektes SQL, das die fachliche Frage trotzdem nicht beantwortet. Eine parallele Inline-Dokumentation zwingt dazu, generiertes SQL Zeile für Zeile nachzuvollziehen und die fachliche Absicht festzuhalten — bevor es in Produktion geht. Der Kommentar-Block wird so zum Review-Protokoll des generierten Codes.
Moderne Editoren: Multi-Cursor und Kommentar-Shortcuts
Die Blockauswahl aus Beispiel 2 hat in modernen Editoren einen nahen Verwandten: den Multi-Cursor. Statt eines über mehrere Zeilen aufgespannten Rechtecks setzt man mehrere unabhängige Cursor (in VS Code und Azure Data Studio per Alt+Klick oder Ctrl+Alt+Pfeil-runter) und tippt die einleitende ---Sequenz an allen Positionen gleichzeitig. Für rechteckige Bereiche bleibt die klassische Blockauswahl die erste Wahl — in SSMS und Visual Studio über Shift+Alt+Pfeil, in VS Code per Shift+Alt-Mausziehen.
Die Kommentar-Shortcuts hängen am Editor, nicht an der Datenbank:
- SSMS:
Ctrl+K, Ctrl+C(kommentieren) /Ctrl+K, Ctrl+U(auskommentieren aufheben) - VS Code / Azure Data Studio:
Ctrl+/(umschalten) - DataGrip:
Ctrl+/(zeilenbasiert) /Ctrl+Shift+/(Block) - DBeaver:
Ctrl+/(umschalten)
Die parallele Inline-Dokumentation selbst ist von alldem unabhängig — sie ist eine Konvention, kein Feature. Sie funktioniert in jedem Editor, der eine Form von Mehrzeilen-Bearbeitung kennt.
FAQ
Für die parallele Inline-Dokumentation die zeilenbasierte Form (--): Jede Zeile ist als Kommentar erkennbar, auch ohne Syntax-Highlighting, und die Sequenz dient als strukturierendes Element. Block-Kommentare (/* … */) eignen sich für längere zusammenhängende Erklärungen am Statement-Anfang — verschachteln lassen sie sich in SQL Server wie in Postgres.
Über die Blockauswahl bzw. den Multi-Cursor die ---Sequenz an allen Zeilen gleichzeitig setzen — oder den Editor-Shortcut nutzen: Ctrl+K, Ctrl+C in SSMS, Ctrl+/ in VS Code, Azure Data Studio, DataGrip und DBeaver.
Ja, unverändert. Die Kommentar-Syntax ist identisch — beide Engines erlauben sogar verschachtelte Block-Kommentare — und das Muster ist rein textuell.
Gerade dann. Generierter Code ist schnell, aber undurchschaut. Eine parallele Inline-Dokumentation zwingt dazu, jede Zeile fachlich nachzuvollziehen — der beste Schutz gegen technisch korrektes SQL, das die eigentliche Frage nicht beantwortet.
Kurz genug, dass keine horizontale Navigation nötig wird — als Orientierung dienen etwa 80 Zeichen pro Zeile. Längere Kommentare umbrechen und linksbündig zur vorigen Zeile einrücken; vertikales Scrollen ist deutlich leichter als horizontales.