Komplexe SQL-Statements kommentieren — parallele Inline-Dokumentation, die die Lesbarkeit nicht zerstört

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– und Bild 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

Block- oder zeilenbasierte Kommentare — was nehme ich?

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.

Wie kommentiere ich viele Zeilen auf einmal?

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

Gilt die parallele Inline-Dokumentation auch in Postgres?

Ja, unverändert. Die Kommentar-Syntax ist identisch — beide Engines erlauben sogar verschachtelte Block-Kommentare — und das Muster ist rein textuell.

Soll ich AI-generiertes SQL überhaupt kommentieren?

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.

Wie lang darf ein Inline-Kommentar sein?

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.