Für Programmierer ist die Datenbanksprache SQL quasi schon eine Pflicht. Die Syntax der Programmiersprache ist einfach aufgebaut, aber trotzdem gibt es Tipps, die man beachten sollte. ZDNet stellt ein paar Tricks vor, die beim Arbeiten mit SQL-Datenbanken helfen.
SQL ist überall, SQL ist einfach zu erlernen und SQL-Lösungen lassen sich problemlos implementieren. Egal, ob man nun SQL häufig oder nur gelegentlich verwendet, diese Tricks und Ratschläge für die Arbeit mit der Datenbanksprache helfen auf jeden Fall, Fehler zu vermeiden und die Performance zu verbessern.
Viele SQL-Versionen sind anbieterspezifisch. Die folgenden Tipps gelten für Jet- und Transact-SQL, aber sie können auf andere SQL-Produkte adaptiert werden.
1. Arbeiten mit Jet-SQL im Access-SQL-Fenster
Access-Benutzer erstellen jedes Mal, wenn sie eine Abfrage generieren, auch gleichzeitig SQL-Anweisungen, sie wissen es nur nicht. Sie benutzen das Query-Design-Fenster, eine grafische Darstellung des eigentlichen SQL. Die wenigsten Benutzer sind in der Lage, die geeigneten SQL-Anweisungen zu erstellen, die sie benötigen, weshalb die grafische Oberfläche sehr hilfreich ist. Doch auch für Programmierer bietet sie einige Vorteile. Wenn es einem zum Beispiel schwer gelingt, ohne Hilfsmittel einen fehlerfreien SQL-Code zu erstellen, kann man auf das SQL-Fenster zurückgreifen:
- Nach dem Erstellen einer Abfrage mithilfe des Query-Design-Fensters muss der Code aus dem SQL-Fenster in ein VBA-Modul kopiert werden.
- Wenn eine im Code durchgeführte Abfrage eine wenig aussagekräftige Fehlermeldung in VBA erzeugt, kopiert man die SQL-Anweisung in das SQL-Fenster und führt sie aus. Access liefert meist bessere Informationen über den Fehler als VBA.
Nicht jede SQL-Anweisung kann direkt aus dem Modul in das SQL-Fenster kopiert werden. Wenn die Anweisung Verkettungen oder Variablen enthält, sollte man eine Debug.Print-Anweisung direkt nach der Definitionsanweisung einfügen. Falls ein Debugging für die Anweisung durchgeführt werden soll, kopiert man die evaluierte Anweisung aus dem Immediate-Fenster. Die folgende Anweisung im Code funktioniert beispielsweise wegen der Variablen und der Verkettung im SQL-Fenster nicht:
Ist dagegen eine Debug.Print-Anweisung dahinter gesetzt, lassen sich die evaluierten Ergebnisse aus dem Immediate-Fenster kopieren:
2. Reservierte Wörter in SQL
SQL reserviert bestimmte Wörter als Schlüsselwörter. Sie sollten nicht zur Bezeichnung von Spalten, Tabellen, Variablen oder Objekten verwendet werden. Kommen diese Wörter außerhalb des von der Engine vorgesehenen Kontexts zum Einsatz, verwirrt dies die Engine und verursacht Fehler oder sogar ohne Fehlermeldung falsche Ergebnisse.
3. Der Unterschied zwischen ALL, DISTINCT und DISTINCTROW
Die SQL-Anweisung SELECT unterstützt drei Prädikate: ALL, DISTINCT und DISTINCTROW. ALL ist der Standard und gibt alle Einträge aus, welche die jeweils eingegebenen Bedingungen erfüllen. DISTINCT beschränkt die Ergebnisse auf in einer oder mehreren Spalten nur einmal vorkommende Werte. Zum Beispiel würde die folgende Anweisung nur einen Eintrag für jeden LastName-Wert ausgeben:
Mit anderen Worten: Wenn ein Eintrag sowohl für John Smith als auch für Mary Smith vorhanden ist, gibt diese Anweisung nur einen Eintrag für Smith aus. DISTINCT funktioniert jedoch für alle Spalten, nicht nur für die unmittelbar folgende Spalte. Das bedeutet, dass diese Anweisung einen Eintrag für John und Mary ausgibt, da die kombinierten Spalten keinen doppelten Eintrag ergeben:
Wenn die SELECT-Klausel auf mehr als eine Spalte bezogen wird, muss die Kombination der Werte aus allen ausgewählten Spalten für einen bestimmten Eintrag einzigartig sein.
DISTINCT weist ein paar Besonderheiten auf, die man kennen sollte:
- Man sollte DISTINCT nicht zusammen mit einem Asterisk (*) verwenden. Die Spalten müssen mit ihrem Namen angeben werden.
- Abfragen unter Verwendung von DISTINCT können nicht aktualisiert werden, was auch sinnvoll ist.
Während DISTINCT mit Spalten funktioniert, bezieht sich DISTINCTROW auf Einträge. (Transact-SQL unterstützt DISTINCTROW nicht.) Dieses Prädikat weist ebenfalls einige Einschränkungen auf, die das Beheben eines Fehlers erschweren können:
- Die Engine ignoriert DISTINCTROW, wenn die Abfrage nur eine Tabelle enthält.
- Die Engine ignoriert DISTINCTROW, wenn man auf alle Spalten aus allen zugrundeliegenden Tabellen verweist.
5. Varianten der Aggregate
Sowohl SQL als auch VBA unterstützen Aggregat-Funktionen, allerdings nicht die gleichen Aggregate. Obwohl die Aggregate nicht wirklich austauschbar sind, können häufig beide Typen verwendet werden. Tabelle A vergleicht die beiden Typen im Kontext von Jet und Transact-SQL.
Tabelle A
| T-SQL | Jet | VBA | Explanation | Considers Null |
| Avg | Avg | DAvg | Returns the mean or average of the specified domain | No |
| Count | Count | DCount | Counts the number of non-Null values in the specified column | No |
| Count(*) | Count(*) | DCount(*) | Counts the number of rows | Yes |
| Sum | Sum | DSum | Totals the values in the specified column | No |
| Min | Min | DMin | Returns the smallest value | No |
| Max | Max | DMax | Returns the largest value | No |
| First | Returns the value from the first row in the specified column | Yes | ||
| Last | Returns the value from the last row in the specified column | Yes | ||
| StDev | StDev | DStDev | Returns sample standard deviation | No |
| StDevP | StDevP | DStDevP | Returns population standard deviation | No |
| Var | Var | DVar | Returns sample variance | No |
| VarP | VarP | DVarP | Returns population deviation | No |
VBA-Domänenaggregate sollten vermieden werden, wenn auch ein SQL-Aggregat genügt. Wer aber zwingend mit VBA-Domänenaggregaten arbeiten muss, sollte einen Index für die zugrundeliegende Spalte verwenden, um die Performance zu optimieren. Dabei ist zu bedenken, dass der SQL-Befehl GROUP BY die Nullwerte nicht beseitigt, doch evaluieren die meisten SQL-Aggregate diese ohnehin nicht. Wer die Nullwerte berücksichtigen will, muss sie in den Ausdruck übernehmen. Die SQL-Klausel GROUP BY definiert Teilmengen von Daten. Dabei ist vor allem wichtig, dass man bei Verwendung einer GROUP BY-Klausel nur diejenigen Spalten einbeziehen sollte, welche die Teilmenge definieren oder Daten für die Teilmenge zusammenfassen. Mit anderen Worten, GROUP BY kann keine zusätzlichen Daten umfassen. Wer beispielsweise die Anzahl der an einem bestimmten Datum platzierten Aufträge erfahren möchte, kann eine Anweisung ähnlich der folgenden verwenden:
Diese Abfrage würde einen Eintrag für jedes Datum ausgeben. In jedem Eintrag würden das Datum und die Anzahl der Aufträge für dieses Datum angezeigt. Man kann keine weiteren Spalten einbeziehen.
GROUP BY ist vielseitig. Man muss keine Spalte in der SELECT-Klausel angeben, um eine Gruppierung für sie durchzuführen. Zum Beispiel könnte das OrderDate aus der Abfrage oben weggelassen und nur die Zählung für das jeweilige Datum ausgegeben werden (obgleich diese Ergebnisse nicht sehr sinnvoll wären). Wenn die GROUP BY-Spalte in der Quelle enthalten ist, benötigt SQL sie nicht in der SELECT-Klausel. Wird dagegen in der SELECT-Klausel auf eine Spalte verwiesen, muss diese auch in die GROUP BY-Klausel oder in eine Aggregat-Funktion übernommen werden. Die folgende Anweisung funktioniert beispielsweise nicht, weil die Spalte Freight (Fracht) nicht Teil eines Aggregats oder der GROUP BY-Klausel ist:
In Wirklichkeit ist es nicht sehr sinnvoll, eine Spalte auf diese Weise einzubeziehen. Wenn man die Frachtdaten im Kontext einer GROUP BY-Abfrage wünscht, macht eine Zusammenfassung der Frachtwerte in der Gruppe Sinn. Siehe:
Jet kann keine Memo- oder OLE Object-Spalten gruppieren. Außerdem lässt sich eine GROUP BY-Klausel nicht in eine UPDATE-Anweisung übernehmen, was auch sinnvoll ist, da SQL dann nicht feststellen könnte, welche Einträge aktualisiert werden sollen.
7. Ändern der Tabellenstruktur
Es gibt verschiedene SQL-Anweisungen, um die Gestaltung einer Tabelle zu verändern. Hierbei sollte man immer Vorsicht walten lassen, da sonst Daten vernichtet werden könnten. Wird beispielsweise den Datentyp einer Spalte geändert, könnte die Engine vorhandene Daten kürzen oder löschen, um den Datentyp der neuen Spalte unterzubringen. Beim Ändern von Tabellen sollte man die folgenden Einschränkungen beachten:
- Man kann die Daten einer bestehenden Spalte nicht auf COUNTER ändern, wenn die Spalte bereits Daten enthält.
- Eine Spalte kann nicht umbenannt werden. Dazu muss man die Spalte mit DROP COLUMN entfernen und dann unter Verwendung des neuen Namens neu erstellen. Um die Daten zu bewahren, muss eine neue Spalte erstellt werden, in die dann die Daten aus der ursprünglichen Spalte mit UPDATE kopiert werden. Zu guter Letzt ist die ursprüngliche Spalte zu löschen.
- Vor dem Ändern einer Tabelle mit ALTER TABLE muss sie geschlossen sein. Andernfalls gibt die Engine einen Fehler aus. Der folgende VBA-Code schließt eine geöffnete Tabelle und verhindert den Fehler, der generiert wird, wenn die Tabelle geöffnet ist:
- Spalten für die eine Einschränkung vorliegt, lassen sich nicht löschen. Zuerst muss die Einschränkung mit DROP CONSTRAINT entfernt werden.
- Alle CHECK CONSTRAINT-Befehle entfernen, bevor man die Tabellen löscht, die von diesen betroffen sind.
- Zum Ändern einer bestehenden Beziehung muss sie gelöscht und neu erstellt werden.
Die SQL-Anweisung INSERT INTO ermöglicht das Einfügen neuer Daten in eine bestehende Tabelle. Korrekt eingesetzt funktioniert INSERT INTO sehr gut, doch muss man dabei einen entscheidenden Punkt berücksichtigen: Obwohl die Tabellengestaltung nicht exakt übereinstimmen muss, müssen die jeweils auf beiden Seiten angegebenen Spalten übereinstimmen. Kurz gesagt: Die Tabelle, die neue Daten erhält, muss die gleichen Spalten aufweisen wie die übernommenen Daten.
Es lässt sich auch jede Spalte einzeln festlegen, indem die VALUES-Klausel wie folgt benutzt wird:
Mit dieser Syntax wird allerdings jeweils nur eine einzelne Zeile hinzugefügt, was die Anwendung einschränkt. Man kann die Spaltenverweise nur dann weglassen, wenn es für jede Spalte in der Zielstruktur einen Wert gibt. Beim Einfügen der Spaltenverweise, muss deren Reihenfolge der Reihenfolge in der Tabellendefinition entsprechen. Man muss die Primärschlüsselspalte einbeziehen, sofern es sich bei dem Schlüssel nicht um eine automatisch nummerierte Spalte handelt.
9. Mit UPDATE Werte löschen
Die SQL-Anweisung DELETE löscht ganze Einträge. Die Anweisung gibt zwar keinen Fehler aus, wenn eine Liste mit Spalten angegeben ist, aber sie ignoriert die Liste. Zum Beispiel würden die folgenden Anweisungen alle Daten in einer Tabelle mit dem Namen Employees (Angestellte) löschen:
Obwohl das letzte Syntaxelement Salary (Gehalt) festlegt, löscht SQL alle Daten und nicht nur die Gehaltswerte. Um bestimmte Werte zu löschen, muss man die SQL-Anweisung UPDATE in folgender Weise verwenden:
Allerdings wird auch hierbei ein Fehler ausgegeben, wenn die Spalteneigenschaften im Konflikt stehen. Wenn beispielsweise eine Spalte Daten erfordert, weist sie Null ab. Der SQL-Befehl SELECT INTO erstellt eine neue Tabelle durch das Kopieren von Zeilen aus einer vorhandenen Tabelle, wenn er in folgender Weise eingesetzt wird:
Diese Anweisung kopiert die Tabelle jedoch nicht ganz exakt. So werden der Primärschlüssel, Indexe oder Spalten- und Tabelleneigenschaften (die über die Standardeinstellungen hinausgehen) der ursprünglichen Tabelle nicht kopiert. Außerdem werden die Beschriftungseinstellungen ignoriert und die ursprünglichen Spaltennamen verwendet.
Wer eine SELECT INTO-Anweisung einfügt, sollte immer daran denken, dass diese Anweisung eine vorhandene Tabelle ersetzt, wenn eine Tabelle mit dem gleichen Namen (newtable) in ihrem Geltungsbereich enthalten ist. Glücklicherweise warnt SQL den Benutzer aber vorher. In manchen Fällen löscht die Engine aber die vorhandene Tabelle, bevor sie die neue Tabelle tatsächlich erstellt. Damit kann man bei Problemen nicht mehr auf die ursprüngliche Tabelle zurückgreifen, weil diese verschwunden ist. Vor der Verwendung von SELECT INTO empfiehlt es sich daher, eine Kopie der bestehenden newtable, sofern vorhanden, zu machen. Außerdem gibt SQL bei geöffneter newtable einen Fehler aus.
Es ist nicht notwendig Daten in die neue Tabelle zu kopieren. Man kann eine neue leere Tabelle erstellen, indem man eine WHERE-Klausel wie folgt hinzufügt:
SQL erstellt newtable, kopiert aber keine Daten hinein, da keiner der Einträge die FALSE-Bedingung erfüllen kann.
11. Der Unterschied zwischen WHERE und HAVING
Die Klauseln WHERE und HAVING führen ähnliche Funktionen aus, sind jedoch nicht austauschbar. WHERE schränkt die durch die SELECT-Klausel ausgegebenen Daten ein; daher wäre ein GROUP BY irrelevant. Die Engine vergleicht Daten und beseitigt Einträge, welche die WHERE-Klausel nicht erfüllen, bevor sie die Einträge gruppiert. Die HAVING-Klausel entfernt dagegen Daten, die den Gruppierungskriterien nicht entsprechen.
Wer sich nicht sicher ist, welche Klausel er benutzen soll, sollte sich in Erinnerung rufen, dass die WHERE-Klausel vor der GROUP BY-Klausel platziert ist und die Engine die WHERE-Klausel vor der Gruppierung der Einträge anwendet.
12. Zusammenfassen mit UNION
Mit dem in SQL enthaltenen UNION-Operator kann man Einträge aus verschiedenen Quellen in folgender Weise kombinieren:
Wichtig dabei ist, dass die Spaltenreihenfolge in beiden SELECT-Anweisungen übereinstimmen muss. Die Spaltennamen müssen nicht übereinstimmen, aber jede Liste muss die gleiche Anzahl an Spalten enthalten und deren Datentypen müssen kompatibel sein. Wenn die Datentypen nicht zusammenpassen, wählt die Engine mitunter den am ehesten kompatiblen Typ aus. Das kann funktionieren oder auch nicht.
Standardmäßig sortiert UNION die Einträge nach den Werten in der ersten Spalte, da UNION ein implizites DISTINCT-Prädikat zur Auslassung doppelter Einträge anwendet. Will man alle Einträge einbeziehen, auch die doppelten, sollte man UNION ALL verwenden, bei dem die implizierte Sortierung entfällt. Wenn man weiß, dass keine doppelten Einträge vorhanden sind, aber viele Einträge vorliegen, kann man mithilfe von UNION ALL die Performance verbessern, da die Engine den für die Sortierung erforderlichen Vergleich (zum Auffinden doppelter Einträge) überspringt.