Die im Folgenden aufgelisteten Vorschläge sollen zu einem besseren T-SQL führen und Performance-Engpässe und schwer zu lokalisierende Fehler vermeiden. Viele der Empfehlungen gelten dabei sowohl für den Einsatz in gespeicherten Prozeduren, Triggern und benutzerdefinierten Funktionen als auch für Ad-hoc-Anweisungen. Im Einzelfall gilt aber immer, dass der konkrete Einsatzhintergrund über "Sinn und Unsinn" einer Empfehlung entscheidet. Und die Regeln müssen auch nicht hochtechnisch sein, bereits die Formatierung entscheidet darüber, ob ein T-SQL-Quelltext lesbar (und damit verständlich ist) oder nicht.
Gezielter Bruch
Da T-SQL kontextfrei ist, besteht die Möglichkeit eine Anweisung an vielen Stellen durch einen Umbruch lesbarer zu machen ohne dass die Ausführung davon beeinflusst wird. So ist z.B. der folgende Quelltext:
SELECT Feld1, Feld2, Feld3, Feld4, Feld5, Feld6, Feld7, Feld8FROM Tabelle1 WHERE Feld9=Feld10 ORDER BY Feld11 DESC
sicher nicht so gut zu lesen wie dieser:
SELECT Feld1, Feld2, Feld3, Feld4, Feld5, Feld6, Feld7, Feld8FROM Tabelle1WHERE Feld9=Feld10ORDER BY Feld11 DESC
Diese Version hat zusätzlich innerhalb einer gespeicherten Prozedur oder benutzerdefinierten Funktion den Vorteil, dass man einzelne Zeilen (z.B. das Where-Prädikat) bequem mit // auskommentieren kann.
Schlüsselwörter groß schreiben
T-SQL ist lesbarer, wenn eine einheitliche Groß- und Kleinschreibung zum Einsatz kommt. Eine einfache Regel lautet: „Alle T-SQL-Schlüsselwörter groß schreiben“. Dadurch wird es dem menschlichen Auge mit ein wenig Übung ermöglicht, sofort zu erkennen, ob es sich um ein Schlüsselwort oder einen benutzerdefinierten Namen handelt. Weniger T-SQL-erfahrene Entwickler erhalten damit eine zusätzliche Hilfestellung, die durch die Fähigkeit des Editors, bekannte Schlüsselwörter farblich zu kennzeichnen, verstärkt wird. Unter [1] befindet sich eine Liste aller reservierten Schlüsselwörter des T-SQL für SQL Server 2005.
Keine Schlüsselwörter, Leerzeichen oder Sonderzeichen verwenden
Auch wenn der SQL Server es an vielen Stellen zulässt, ist es kein guter Stil, Schlüsselwörter, Leerzeichen oder Umlaute als Spaltennamen zu verwenden. Das Gleiche gilt für reservierte Schlüsselwörter. Für den Leser des T-SQL-Quelltextes kann es zum einen schnell zu Fehlerinterpretationen kommen, zum anderen müssen solche Namen immer mit eckigen Klammern umschlossen werden, was den Quelltext nicht einfacher lesbar macht. Ein guter Indikator, auch bei weniger bekannten Schlüsselwörtern, ist wiederum die Fähigkeit des Editors, diese farblich zu markieren. Der grafische Entwurf einer Tabelle fügt bei solchen Namen die eckigen Klammern an. Passiert dies, sollte einfach ein anderer, sprechender Name gewählt werden.
Kein SELECT *
Die Verwendung von SELECT * sollte, wo immer möglich, vermieden werden. Effizienter ist es, die benötigten Spalten namentlich aufzulisten. Dies hat den Vorteil, dass nach einer Schema-Erweiterung an der Tabelle keine Spalten mitgeliefert werden, die eventuell für die konkrete Programmierung an dieser Stelle gar nicht benötigt werden. Dies trifft besonders für Tabellen zu, die binäre Daten (BULK) beinhalten oder repliziert werden. Im letzteren Fall wird die, für die Replikation benötigte Spalte ROWGUID angefügt, die in den meisten Fällen für die Programmierung nicht von Bedeutung ist. Ein weiterer Punkt ist, dass so die Möglichkeit besteht, die Spaltenreihenfolge in der Abfrage sicher anzugegeben und entsprechend in der .NETProgrammierung bei der Erstellung eines DataReader-Objektes als CommandBeviour SequentialAccess anzugeben:
myDataReader = mySQLCommand.ExecuteReader(CommandBehavior.SequentialAccess)
Dies ermöglicht gerade bei großen Datenmengen einen Geschwindigkeitszuwachs.
IdentitiyCol
An Stellen, an denen auf die Identitätsspalte einer Tabelle zugegriffen werden soll, sollte nicht deren Name angegeben werden, sondern der Alias IdentityCol. Damit weiß der „Leser“ sofort, dass die Identitätsspalte gemeint ist, auch wenn er diese mit dem Namen nicht in Verbindung bringt. Damit ist die Lesbarkeit erhöht und der SQL Server kann gegebenenfalls besser optimieren. IdentityCol wird wie ein Spaltenname verwendet, kann also auch auf einen Tabellen-Alias folgen.
Ordnung muss sein
Wird in der Weiterverarbeitung der Zeilen eine bestimmte Reihenfolge benötigt, so ist ein ORDER BY zwingend notwendig, da sonst nicht garantiert wird, dass die Daten in der erwarteten Reihenfolge zur Verfügung stehen. Im Umkehrschluss bedeutet dies allerdings auch, dass in Fällen, in denen die Reihenfolge unerheblich ist, der Mehraufwand an Verarbeitung, der ein ORDER BY mit sich bringt, vermeidbar ist. Wird eine Sortierung benötigt, sollte das ORDER BY immer mit einem Spaltenname und nicht mit einem Spaltenindex durchgeführt werden:
SELECT T.FELD1, T.FELD2 FROM Tabelle1 as T ORDER BY 1
Dies ist bei einer großen Anzahl von Spalten schlecht lesbar und ziemlich fehleranfällig, da ein später eingefügter Spaltenname die Reihenfolge der Zeilen ändert.
Alias
Für den Zugriff auf Tabellen sollte immer einen Alias verwendet werden, der so kurz wie möglich ist – im günstigsten Fall nur ein Zeichen und das erste Zeichen des Tabellennamens. Dieser Alias sollte dazu verwendet werden, Spaltennamen aus dieser Tabelle zu kennzeichnen:
SELECT T.Feld1 FROM Tabelle AS T
Besonders bei Abfragen über mehrere Tabellen erhöht dies die Übersichtlichkeit und vermeidet Mehrdeutigkeiten.
Groß- und Kleinschreibung bei Filter mittels = und LIKE
Bei der Standard-SQL-Installationen werden Vergleiche mittels = und LIKE ohne Beachtung der Groß- und Kleinschreibung durchgeführt. In Anwendungsfällen, in denen dies absolut unerwünscht ist, ist es ratsam, die entsprechenden Abfragen so zu erweitern, dass diese nicht mittels der Standardeinstellung durchgeführt werden. Dies kann z.B. so aussehen:
SELECT Feld1 * FROM Tabelle1 WHERE Feld2 =‘MeinSuchwort‘ COLLATE SQL_Latin1_General_CP1_CS_AS
Ähnliches ist auch möglich, wenn genau das Gegenteil benötigt wird:
SELECT Feld1 * FROM Tabelle1 WHERE Feld2 =‘MeinSuchwort‘ COLLATE Latin1_General_CI_AS
Beides stellt sicher, dass die Anwendung auch nach Wunsch läuft, wenn die Datenbank nicht hundertprozentig nach Vorgabe installiert wurde.
Benötigte Größe und Genauigkeit festlegen
Die Anzahl der Dezimalstellen sowie die Genauigkeit von Dezimaldatentypen sollten immer festgelegt werden, da andernfalls die Standardeinstellungen mit einer nicht festgelegter Genauigkeit und Ganzzahlen verwendet werden. Durch den Einsatz der CONVERT-Funktion kann bei Variablenzuweisung oder bei Wertevergleichen sichergestellt werden, dass immer die gewünschte Genauigkeit verwendet wird. Ähnliches gilt auch für die Länge von Zeichenketten. Sicherlich mag es im Zeitalter der Giga- und Terrabytes unwichtig erscheinen, ob eine Spalte als VARCHAR( 10) oder VARCHAR(100) definiert ist. Aber die Menge (also die Anzahl der Zeilen) macht’s. Ist zum Beispiel die Länge einer Zeichenkette immer (meistens) von gleicher Länge, so empfiehlt sich der Datentyp CHAR. Wenn kein Unicode benötigt wird, so sollte generell von NCHAR, NVARCHAR usw. Abstand genommen werden, da diese zwei Bytes pro Zeichen benötigen.
Systemkomponenten
Auch wenn sie sehr oft einen recht schnellen Weg zu einem Ziel versprechen, verbieten sich direkte Zugriffe auf Systemkomponenten. In vielen Fällen lässt sich der gleiche Effekt auch mit „erlaubten“ Mitteln erreichen. Für die Fälle in denen es nicht möglich ist, sollte die Gelegenheit genutzt werden darüber nachzudenken, ob das Gewünschte wirklich eine gute Idee ist. Um z.B. eine Liste von allen Tabellen in einer Datenbank zu erhalten, sollte nicht
SELECT [name] FROM sysobjects WHERE xtype=‘U‘
sondern
SELECT table_name from INFORMATION_SCHEMA.TABLES
verwendet werden. Eine Liste aller Sichten, die Metainformationen über eine Datenbank und den SQL Server liefert, finden Sie hier [2].
Vergleiche mit NULL
Der direkte Vergleich zwischen NULL und einem Wert sollte vermieden werden. In solchen Fällen muss IS NULL, IS NOT NULL oder die Funktion IsNull() verwendet werden. Auch wenn die folgende T-SQL-Abfrage korrekt erscheint, wird sie niemals das vermutete Ergebnis liefern: SELECT ‘NULL = NULL’ WHERE NULL=NULL Diese Abfrage hingegen verhält sich wie gewünscht. SELECT ‘NULL IS NULL‘ WHERE NULL IS NULL Da NULL häufig als Zeichen dafür verwendet wird, dass für einen Wert/ Parameter kein Wert vorhanden ist oder übergeben wurde, schleichen sich solche Fehler schnell ein und sind schwer auffindbar.
Fehler!
Fehlerbehandlung ist wichtig und sollte, wo immer es Sinn macht, durchgeführt werden. Leider gibt es auch hier einen Fallstrick, der schwer zu erkennen ist. Die @@ERROR-Systemfunktion liefert immer den letzten Fehler, der aufgetreten ist oder „0“, wenn keiner vorliegt. Folgender Block liefert daher leider nicht das erwartete Ergebnis:
ROLLBACK TRAN -- Soll einen Fehler erzeugenIF (@@ERROR > 0)PRINT ‚Letzter Fehler: ‚ + CAST(@@ERROR as varchar)
Als Ausgabe erscheint „Letzter Fehler: 0“. Das liegt daran, dass die letzte Anweisung vor dem PRINT die IF-Abfrage ist, und diese fehlerfrei ausgeführt wird. Der vorherige Fehler wird damit gelöscht. Daher liefert @@ERROR konsequenterweise auch „0“. Die Lösung ist es, den Fehlerwert sofort in einer Variablen zu speichern. Folgender Block leistet genau dies:
DECLARE @Error intROLLBACK TRAN -- Soll einen Fehler erzeugenSET @Error = @@ERRORif (@Error > 0)PRINT ‘Letzter Fehler: ‘ + CAST(@Error as varchar)
Auch wenn es auf dem ersten Blick unnötig aussieht, nur so geht es.
SET NOCOUNT ON/ OFF
In gespeicherten Funktionen und Triggern sollte am Anfang ein SET NOCOUNT ON und am Ende ein SET NOCOUNT OFF stehen. Dadurch wird vermieden, dass vom SQL Server für jede Abfrage die Anzahl der betroffenen Zeilen an den Client gesendet wird. Weil dies in relativ vielen Fällen eine doch eher unwichtige Information ist, kann sie unterdrückt werden. Im Trigger kann dies darüber hinaus zu sehr schwer auffindbaren Fehlern führen. Löscht beispielsweise ein INSERT-Trigger beim Einfügen einer Zeile zwei andere Zeilen, so ist dies der Wert, den ADO.NET als RowsAffected zurückliefert.
Gespeicherte Prozeduren
Gespeicherte Prozeduren bieten relativ drastische Möglichkeiten zur Verbesserung der Leistung, weil die Datenbank den, von der Prozedur verwendeten, Datenzugriffsplan optimieren und zur späteren Wiederverwendung zwischenspeichern kann. Außerdem muss keine (umfangreiche) Abfrage über das Netzwerk geschickt werden, sondern ausschließlich der Name der Prozedur und gegebenenfalls die Parameter. Der Name sollte dabei selbstsprechend sein, allerdings nicht mit SP_ beginnen, da sonst zuerst in der Master-Datenbank und erst danach in den Systemprozeduren der aktuellen Datenbank gesucht wird [3]. Werden einzelne Werte als Rückgabe benötigt, so sollte dies als OUTPUT-Parameter odermittels RETURN geschehen anstatt per SELECT.
Temporäre Tabellen
Obwohl temporäre Tabellen an vielen Stellen durch effizientere Techniken ersetzt werden können, sind sie trotzdem nicht ganz wertlos. An den Stellen, an denen die Entscheidung für temporäre Tabellen gefallen ist (zum Beispiel wenn wiederholt auf die gleichen, berechneten Wertemengen zugegriffen werden muss), sollte Folgendes beachtet werden: Die Erstellung am Anfang, die Löschung (DROP) am Ende. Sollten mehrere, unterschiedliche temporäre Tabellen nacheinander benötigt werden, sollten dies unterschiedliche Namen haben. Ein Recyceln des Namens führt beim Lesen der Quelltexte nur zu unnötigen Verwechslungen.
Kommentare
Egal in welcher Programmiersprache, egal auf welchem Kontinent: Nichts kann sinn- und maßvolle Kommentare ersetzen. Auch bei T-SQL. Zudem bietet sich der Kopfbereich einer gespeicherten Prozedur oder einer benutzerdefinierten Funktion als Angabe darüber an, wer wann was geändert hat und vielleicht auch noch warum.
Vorlagen
Besonders bei Projekten mit mehreren Entwicklern ist ein Mechanismus sinnvoll, der sicherstellt, dass alles wie aus einem Guss erscheint. Für diesen Zweck bieten sich, bei Verwendung des SQL Server Management Studios, Vorlagen für die unterschiedlichen Zwecke an. Durch den Vorlagen-Explorer lassen sich bestehende Vorlagen den eigenen Wünschen anpassen oder neue erstellen. Aufgerufen wird der Vorlagen-Explorer durch Ansicht | Vorlagen Explorer (oder Strg+Alt+T). Durch entsprechende Anpassungen erhalten so neue Komponenten einen einheitlichen Copyright-Vermerk, einen Firmen-/ Produktnamen oder was immer benötigt wird. Es ist mit ein wenig Disziplin und Umsicht möglich, ohne großen Aufwand einen T-SQL-Code zu schreiben, der bessere Leistung, bessere Lesbarkeit und weniger Fehlerpotential in sich birgt.
Noch ein kleiner Tipp zum Schluss: Die Erfahrung zeigt, dass T-SQL in der Regel recht präzise und kurz ist. Lassen sich Abschnitte im Quelltext finden, die zu umfangreich und kompliziert erscheinen, so ist es im Allgemeinen immer möglich, eine bessere Lösung zu finden, die schneller in der Ausführung und einfacherer in der Wartung ist.
Thorsten Kansy ist freier Entwickler, Consultant und Trainer rund um die Bereiche .NET-Entwicklung und SQL Server. Er ist seit 1992 in der IT-Branche tätig und war lange Jahre als CTO erfolgreich tätig. Sie erreichen ihn unter tkansy@ dotnetconsulting.eu.

