Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage
<< | < | > | >> | API | Kapitel 42 - Datenbankzugriffe mit JDBC

## 42.4 Weiterführende Themen

- 42.4 Weiterführende Themen
  - 42.4.1 Metadaten
  - 42.4.2 Escape-Kommandos
  - 42.4.3 Transaktionen
  - 42.4.4 JDBC-Datentypen
  - 42.4.5 Umgang mit JDBC-Objekten
  - 42.4.6 Prepared Statements
  - 42.4.7 SQL-Kurzreferenz
    - Ändern von Datenstrukturen
    - Ändern von Daten
    - Lesen von Daten

In diesem Abschnitt wollen wir eine Reihe von Themen ansprechen, die bei der bisherigen Darstellung zu kurz gekommen sind. Aufgrund des beschränkten Platzes werden wir jedes Thema allerdings nur kurz ansprechen und verweisen für genauere Informationen auf die JDBC-Beschreibung (sie ist Bestandteil der seit dem JDK 1.2 ausgelieferten Online-Dokumentation) und auf weiterführende Literatur zum Thema JDBC.

### 42.4.1 Metadaten

In Abschnitt 42.3.3 sind wir bereits kurz auf die Verwendung von Metadaten eingegangen. Neben den Datenbankmetadaten gibt es die Methode `getMetaData` der Klasse `ResultSet`:

    ResultSetMetaData getMetaData()
| java.sql.ResultSet

Sie liefert ein Objekt vom Typ `ResultSetMetaData`, das Meta-Informationen über die Ergebnismenge zur Verfügung stellt. Wichtige Methoden sind:

    int getColumnCount()

    String getColumnName(int column)

    String getTableName(int column)

    int getColumnType(int column)
| java.sql.ResultSetMetaData

Mit `getColumnCount` kann die Anzahl der Spalten in der Ergebnismenge abgefragt werden. `getColumnName` und `getTableName` liefern den Namen der Spalte bzw. den Namen der Tabelle, zu der diese Spalte in der Ergebnismenge gehört, wenn man ihren numerischen Index angibt. Mit `getColumnType` kann der Datentyp einer Spalte abgefragt werden. Als Ergebnis wird eine der statischen Konstanten aus der Klasse `java.sql.Types` zurückgegeben.

### 42.4.2 Escape-Kommandos

Mit den Escape-Kommandos wurde ein Feature eingeführt, das die Portierbarkeit von Datenbankanwendungen verbessern soll. In Anlehnung an ODBC fordert die JDBC-Spezifikation dazu, daß die JDBC-Treiber in der Lage sein müssen, besondere Zeichenfolgen in SQL-Anweisungen zu erkennen und in die spezifische Darstellung der jeweiligen Datenbank zu übersetzen. Auf diese Weise können beispielsweise Datums- und Zeitliterale portabel übergeben oder eingebaute Funktionen aufgerufen werden. Die Escape-Kommandos haben folgende Syntax:

    "{" <Kommandoname> [<Argumente>] "}"

Am Anfang steht eine geschweifte Klammer, dann folgen der Name des Escape-Kommandos und mögliche Argumente, und am Ende wird das Kommando durch eine weitere geschweifte Klammer abgeschlossen.

Um beispielsweise unabhängig von seiner konkreten Darstellung einen Datumswert einzufügen, kann das Escape-Kommando "d" verwendet werden. Es erwartet als Argument eine SQL-Zeichenkette im Format "yyyy-mm-dd" und erzeugt daraus das zur jeweiligen Datenbank passende Datumsliteral. In Listing 42.5 haben wir dieses Kommando verwendet, um das Änderungsdatum der Datei in die Tabelle file zu schreiben.

### 42.4.3 Transaktionen

Die drei Methoden `commit`, `rollback` und `setAutoCommit` des `Connection`-Objekts steuern das Transaktionsverhalten der Datenbank:

    void commit()

    void rollback()

    void setAutoCommit(boolean autoCommit)
| java.sql.Connection

Nach dem Aufbauen einer JDBC-Verbindung ist die Datenbank (gemäß JDBC-Spezifikation) zunächst im Auto-Commit-Modus. Dabei gilt jede einzelne Anweisung als separate Transaktion, die nach Ende des Kommandos automatisch bestätigt wird. Durch Aufruf von `setAutoCommit` und Übergabe von `false` kann das geändert werden. Danach müssen alle Transaktionen explizit durch Aufruf von `commit` bestätigt bzw. durch `rollback` zurückgesetzt werden. Nach dem Abschluß einer Transaktion beginnt automatisch die nächste.

Wichtig ist auch der Transaction Isolation Level, mit dem der Grad der Parallelität von Datenbanktransaktionen gesteuert wird. Je höher der Level, desto weniger Konsistenzprobleme können durch gleichzeitigen Zugriff mehrerer Transaktionen auf dieselben Daten entstehen. Um so geringer ist aber auch der Durchsatz bei einer großen Anzahl von gleichzeitigen Zugriffen. Transaction Isolation Levels werden von der Datenbank üblicherweise mit Hilfe von gemeinsamen und exklusiven Sperren realisiert. JDBC unterstützt die folgenden Levels:

- `Connection.TRANSACTION_NONE`
- `Connection.TRANSACTION_READ_UNCOMMITTED`
- `Connection.TRANSACTION_READ_COMMITTED`
- `Connection.TRANSACTION_REPEATABLE_READ`
- `Connection.TRANSACTION_SERIALIZABLE`

Mit Hilfe der beiden Methoden `getTransactionIsolation` und `setTransactionIsolation` des `Connection`-Objekts kann der aktuelle Transaction Isolation Level abgefragt bzw. verändert werden:

    int getTransactionIsolation()

    void setTransactionIsolation(int level)
| java.sql.Connection

Mit der Methode `supportsTransactionIsolationLevel` des `DatabaseMetaData`-Objekts kann abgefragt werden, ob eine Datenbank einen bestimmten Transaction Isolation Level unterstützt oder nicht.

### 42.4.4 JDBC-Datentypen

In den meisten Fällen braucht man keine exakte Kenntnis des Datentyps einer Tabellenspalte, wenn man diese abfragt. Die oben beschriebenen `get`-Methoden des `ResultSet`-Objekts führen geeignete Konvertierungen durch. Soll dagegen mit `CREATE TABLE` eine neue Datenbank definiert werden, muß zu jeder Spalte der genaue Datentyp angegeben werden. Leider unterscheiden sich die Datenbanken bezüglich der unterstützten Typen erheblich, und die `CREATE TABLE`-Anweisung ist wenig portabel. Die Klasse `java.sql.Types` listet alle JDBC-Typen auf und gibt für jeden eine symbolische Konstante an. Mit der Methode `getTypeInfo` der Klasse `DatabaseMetaData` kann ein `ResultSet` mit allen Typen der zugrunde liegenden Datenbank und ihren spezifischen Eigenschaften beschafft werden. In Tabelle 42.4 findet sich eine Übersicht der wichtigsten SQL-Datentypen.

### 42.4.5 Umgang mit JDBC-Objekten

Wie zuvor erwähnt, sind die JDBC-Objekte des Typs `Connection` und `Statement` möglicherweise kostspielig bezüglich ihres Rechenzeit- oder Speicherverbrauchs. Es empfiehlt sich daher, nicht unnötig viele von ihnen anzulegen.

Während das bei `Connection`-Objekten einfach ist, kann es bei `Statement`-Objekten unter Umständen problematisch werden. Wird beispielsweise in einer Schleife mit vielen Durchläufen immer wieder eine Methode aufgerufen, die eine Datenbankabfrage durchführt, so stellt sich die Frage, woher sie das dafür erforderliche `Statement`-Objekt nehmen soll. Wird es jedesmal lokal angelegt, kann schnell der Speicher knapp werden. Wird es dagegen als statische oder als Klassenvariable angelegt, kann es zu Konflikten mit konkurrierenden Methoden kommen (die üblichen Probleme globaler Variablen).

Eine gut funktionierende Lösung für dieses Problem besteht darin, `Statement`-Objekte auf der `Connection` zu cachen, also zwischenzuspeichern. Das kann etwa mit einer Queue erfolgen, in die nicht mehr benötigte `Statement`-Objekte eingestellt werden. Anstelle eines Aufrufs von `createStatement` wird dann zunächst in der Queue nachgesehen, ob ein recyclebares Objekt vorhanden ist, und dieses gegebenenfalls wiederverwendet. Es sind dann zu keinem Zeitpunkt mehr `Statement`-Objekte angelegt, als parallel benötigt werden. Natürlich dürfen nur Objekte in die Queue gestellt werden, die nicht mehr benötigt werden; ihr `ResultSet` sollte also vorher möglichst geschlossen werden. Das `Statement`-Objekt selbst darf nicht geschlossen werden, wenn es noch verwendet werden soll.

Ein einfache Implementierung wird in dem folgendem Listing vorgestellt. Das Objekt vom Typ `CachedConnection` wird mit einem `Connection`-Objekt instanziert. Die Methoden `getStatement` und `releaseStatement` dienen dazu, `Statement`-Objekte zu beschaffen bzw. wieder freizugeben. Die Klasse `LinkedQueue` wurde in Abschnitt 15.4 vorgestellt.

    001 /* CachedConnection.java */
    002
    003 import java.sql.*;
    004 import java.util.*;
    005
    006 public class CachedConnection
    007 {
    008   private Connection  con;
    009   private LinkedQueue cache;
    010   private int         stmtcnt;
    011
    012   public CachedConnection(Connection con)
    013   {
    014     this.con     = con;
    015     this.cache   = new LinkedQueue();
    016     this.stmtcnt = 0;
    017   }
    018
    019   public Statement getStatement()
    020   throws SQLException
    021   {
    022     if (cache.size() <= 0) {
    023       return con.createStatement();
    024     } else {
    025       return (Statement)cache.retrieve();
    026     }
    027   }
    028
    029   public void releaseStatement(Statement statement)
    030   {
    031     cache.add(statement);
    032   }
    033 }
| CachedConnection.java

Listing 42.11: Die Klasse CachedConnection

Es ist wichtig, die JDBC-Objekte auch dann zu schließen, wenn eine Ausnahme während der Bearbeitung aufgetreten ist. Andernfalls würden möglicherweise Ressourcen nicht freigegeben und das Programm würde so nach und nach mehr Speicher oder Rechenzeit verbrauchen. Am einfachsten kann dazu die `finally`-Klausel der `try`-`catch`-Anweisung verwendet werden.

|  |  |  | Eine weitere Eigenschaft der Klasse `ResultSet` verdient besondere Beachtung. Bei manchen JDBC-Treibern erlauben die zurückgegebenen `ResultSet`-Objekte das Lesen einer bestimmten Tabellenspalte nur einmal. Der zweite Versuch wird mit einer Fehlermeldung "No data" (oder ähnlich) quittiert. Manche Treiber erfordern sogar, daß die Spalten des `ResultSet` in der Reihenfolge ihrer Definition gelesen werden. In beiden Fällen ist es gefährlich, einen `ResultSet` als Parameter an eine Methode zu übergeben, denn die Methode weiß nicht, welche Spalten bereits vom Aufrufer gelesen wurden und umgekehrt. Eine Lösung könnte darin bestehen, einen `ResultSet` mit integriertem Cache zu entwickeln, der sich bereits gelesene Spaltenwerte merkt. Alternativ könnte man auch einen objekt-relationalen Ansatz versuchen, bei dem jeder gelesene Satz der Ergebnismenge direkt ein passendes Laufzeitobjekt erzeugt, das dann beliebig oft gelesen werden kann. Wir wollen auf beide Varianten an dieser Stelle nicht weiter eingehen. |  |
Warnung

### 42.4.6 Prepared Statements

Prepared Statements sind parametrisierte SQL-Anweisungen. Sie werden zunächst deklariert und zum Vorkompilieren an die Datenbank übergeben. Später können sie dann beliebig oft ausgeführt werden, indem die formalen Parameter durch aktuelle Werte ersetzt werden und die so parametrisierte Anweisung an die Datenbank übergeben wird. Der Vorteil von Prepared Statements ist, daß die Vorbereitungsarbeiten nur einmal erledigt werden müssen (Syntaxanalyse, Vorbereitung der Abfragestrategie und -optimierung) und die tatsächliche Abfrage dann wesentlich schneller ausgeführt werden kann. Das bringt Laufzeitvorteile bei der wiederholten Ausführung der vorkompilierten Anweisung.

JDBC stellt Prepared Statements mit dem Interface `PreparedStatement`, das aus `Statement` abgeleitet ist, zur Verfügung. Die Methode `prepareStatement` des `Connection`-Objekts liefert ein `PreparedStatement`:

    public PreparedStatement prepareStatement(String sql)
      throws SQLException
| java.sql.Connection

Als Argument wird ein String übergeben, der die gewünschte SQL-Anweisung enthält. Die formalen Parameter werden durch Fragezeichen dargestellt. Bei den meisten Datenbanken dürfen sowohl Änderungs- als auch Abfrageanweisungen vorkompiliert werden. Sie werden dann später mit `executeQuery` bzw. `executeUpdate` ausgeführt. Anders als im Basisinterface sind diese Methoden im Interface `PreparedStatement` parameterlos:

    public ResultSet executeQuery()
      throws SQLException

    public int executeUpdate()
      throws SQLException
| java.sql.PreparedStatement

Bevor eine dieser Methoden aufgerufen werden darf, ist es erforderlich, die vorkompilierte Anweisung zu parametrisieren. Dazu muß für jedes Fragezeichen eine passende `set`-Methode aufgerufen und das gewünschte Argument übergeben werden. Die `set`-Methoden gibt es für alle JDBC-Typen (siehe beispielsweise die analoge Liste der `get`-Methoden in Tabelle 42.1):

    public void setBoolean(int parameterIndex, boolean x)
      throws SQLException

    public void setByte(int parameterIndex, byte x)
      throws SQLException

    ...
| java.sql.PreparedStatement

Der erste Parameter gibt die Position des Arguments in der Argumentliste an. Das erste Fragezeichen hat den Index 1, das zweite den Index 2 usw. Der zweite Parameter liefert den jeweiligen Wert, der anstelle des Fragezeichens eingesetzt werden soll.

Als Beispiel wollen wir uns eine abgewandelte Form der in Abschnitt 42.3.5 vorgestellten Methode `countRecords` ansehen, bei der anstelle eines `Statement`-Objekts ein `PreparedStatement` verwendet wird:

    001 public static void countRecords()
    002 throws SQLException
    003 {
    004   PreparedStatement pstmt = con.prepareStatement(
    005     "SELECT count(*) FROM ?"
    006   );
    007   String[] aTables = {"dir", "file"};
    008   for (int i = 0; i < aTables.length; ++i) {
    009     pstmt.setString(1, aTables[i]);
    010     ResultSet rs = pstmt.executeQuery();
    011     if (!rs.next()) {
    012       throw new SQLException("SELECT COUNT(*): no result");
    013     }
    014     System.out.println(aTables[i] + ": " + rs.getInt(1));
    015   }
    016   pstmt.close();
    017 }

Listing 42.12: Verwenden eines PreparedStatement

Das `PreparedStatement` enthält hier den Namen der Tabelle als Parameter. In einer Schleife nehmen wir nun für die Tabellen "dir" und "file" jeweils eine Parametrisierung vor und führen dann die eigentliche Abfrage durch. Der Rückgabewert von `executeQuery` entspricht dem der Basisklasse, so daß der obige Code sich prinzipiell nicht von dem in Abschnitt 42.3.5 unterscheidet.

|  |  |  | Nicht alle Datenbanken erlauben es, Tabellennamen zu parametrisieren, sondern beschränken diese Fähigkeit auf Argumente von Such- oder Änderungsausdrücken. Tatsächlich läuft unser Beispiel zwar mit InstantDB, aber beispielsweise nicht mit MS Access 95. |  |
Warnung

### 42.4.7 SQL-Kurzreferenz

Dieser Abschnitt gibt eine kurze Übersicht der gebräuchlichsten SQL-Anweisungen in ihren grundlegenden Ausprägungen. Er ersetzt weder ein Tutorial noch eine Referenz und ist zu keinem der bekannten SQL-Standards vollständig kompatibel. Trotzdem mag er für einfache Experimente nützlich sein und helfen, die ersten JDBC-Anbindungen zum Laufen zu bringen. Für "ernsthafte" Datenbankanwendungen sollte zusätzliche Literatur konsultiert und dabei insbesondere auf die Spezialitäten der verwendeten Datenbank geachtet werden.

Die nachfolgenden Syntaxbeschreibungen sind an die bei SQL-Anweisungen übliche Backus-Naur-Form angelehnt:

- Terminalsymbole (Schlüsselwörter) sind großgeschrieben.
- Weitere Terminalsymbole sind die runden Klammern, das Komma und die in den Suchausdrücken verwendeten Operatoren.
- Nichtterminalsymbole sind kursiv geschrieben.
- Optionale Teile stehen in eckigen Klammern.
- Der senkrechte Strich trennt Alternativen. Stehen sie in eckigen Klammern, können sie auch ganz weggelassen werden. Stehen sie in geschweiften Klammern, muß genau eine der Alternativen verwendet werden.
- Drei Punkte zeigen an, daß die davorstehende Anweisungsfolge wiederholt werden kann.

#### Ändern von Datenstrukturen

Mit `CREATE TABLE` kann eine neue Tabelle angelegt werden. Mit `DROP TABLE` kann sie gelöscht und mit `ALTER TABLE` ihre Struktur geändert werden. Mit `CREATE INDEX` kann ein neuer Index angelegt, mit `DROP INDEX` wieder gelöscht werden.

    CREATE TABLE TabName
      (ColName DataType [DEFAULT ConstExpr]
      [ColName DataType [DEFAULT ConstExpr]]...)

    ALTER TABLE TabName
      ADD (ColName DataType
          [ColName DataType]...)

    CREATE [UNIQUE] INDEX IndexName
      ON TabName
      (ColName [ASC|DESC]
      [, ColName [ASC|DESC]]...)

    DROP TABLE TabName

    DROP INDEX IndexName

TabName, ColName und IndexName sind SQL-Bezeichner. ConstExpr ist ein konstanter Ausdruck, der einen Standardwert für eine Spalte vorgibt. DataType gibt den Datentyp der Spalte an, die gebräuchlichsten von ihnen können Tabelle 42.4 entnommen werden.

Bezeichnung | Bedeutung
CHAR(n) | Zeichenkette der (festen) Länge n.
VARCHAR(n) | Zeichenkette variabler Länge mit max. n Zeichen.
SMALLINT | 16-Bit-Ganzzahl mit Vorzeichen.
INTEGER | 32-Bit-Ganzzahl mit Vorzeichen.
REAL | Fließkommazahl mit etwa 7 signifikanten Stellen.
FLOAT | Fließkommazahl mit etwa 15 signifikanten Stellen. Auch als DOUBLE oder DOUBLE PRECISION bezeichnet.
DECIMAL(n,m) | Festkommazahl mit n Stellen, davon m Nachkommastellen. Ähnlich NUMERIC.
DATE | Datum (evtl. mit Uhrzeit). Verwandte Typen sind TIME und TIMESTAMP.

Tabelle 42.4: SQL-Datentypen

#### Ändern von Daten

Ein neuer Datensatz kann mit `INSERT INTO` angelegt werden. Soll ein bestehender Datensatz geändert werden, ist dazu `UPDATE` zu verwenden. Mit `DELETE FROM` kann er gelöscht werden.

    INSERT INTO TabName
      [( ColName [,ColName] )]
      VALUES (Expr [,Expr]...)

    UPDATE TabName
      SET ColName = {Expr|NULL}
        [,ColName = {Expr|NULL}]...
      [WHERE SearchCond]

    DELETE FROM TabName
      [WHERE SearchCond]

TabName und ColName sind die Bezeichner der gewünschten Tabelle bzw. Spalte. Expr kann eine literale Konstante oder ein passender Ausdruck sein. SearchCond ist eine Suchbedingung, mit der angegeben wird, auf welche Sätze die `UPDATE`- oder `DELETE FROM`-Anweisung angewendet werden soll. Wird sie ausgelassen, wirken die Änderungen auf alle Sätze. Wir kommen im nächsten Abschnitt auf die Syntax der Suchbedingung zurück. Wird bei der `INSERT INTO`-Anweisung die optionale Feldliste ausgelassen, müssen Ausdrücke für alle Felder angegeben werden.

#### Lesen von Daten

Das Lesen von Daten erfolgt mit der `SELECT`-Anweisung. Ihre festen Bestandteile sind die Liste der Spalten ColList und die Liste der Tabellen, die in der Abfrage verwendet werden sollen. Daneben gibt es eine Reihe von optionalen Bestandteilen:

    SELECT [ALL|DISTINCT] ColList
      FROM  TabName [,TabName]...
      [WHERE  SearchCond]
      [GROUP BY ColName [,ColName]...]
      [HAVING SearchCond]
      [UNION SubQuery]
      [ORDER BY ColName [ASC|DESC]
                [,ColName [ASC|DESC]]...]

Die Spaltenliste kann entweder einzelne Felder aufzählen, oder es können durch Angabe eines Sternchens "*" alle Spalten angegeben werden. Wurde mehr als eine Tabelle angegeben und sind die Spaltennamen nicht eindeutig, kann ein Spaltenname durch Voranstellen des Tabellennamens und eines Punkts qualifiziert werden. Zusätzlich können die Spaltennamen mit dem Schlüsselwort "AS" ein (möglicherweise handlicheres) Synonym erhalten. Die Syntax von ColList ist:

    ColExpr [AS ResultName]
    [,ColExpr AS ResultName]]...

Zusätzlich gibt es einige numerische Aggregatfunktionen, mit denen der Wert der als Argument angegebenen Spalte über alle Sätze der Ergebnismenge kumuliert werden kann:

Bezeichnung | Bedeutung
COUNT | Anzahl der Sätze
AVG | Durchschnitt
SUM | Summe
MIN | Kleinster Wert
MAX | Größter Wert

Tabelle 42.5: SQL-Aggregatfunktionen

Die `WHERE`-Klausel definiert die Suchbedingung. Wurde sie nicht angegeben, liefert die Anweisung alle vorhandenen Sätze. Der Suchausdruck SearchCond kann sehr unterschiedliche Formen annehmen. Zunächst kann eine Spalte mit Hilfe der relationalen Operatoren <, <=, >, >=, = und <> mit einer anderen Spalte oder einem Ausdruck verglichen werden. Die Teilausdrücke können mit den logischen Operatoren `AND`, `OR` und `NOT` verknüpft werden, die Auswertungsreihenfolge kann in der üblichen Weise durch Klammerung gesteuert werden.

Mit Hilfe des Schlüsselworts `LIKE` kann eine Ähnlichkeitssuche durchgeführt werden:

    Expr LIKE Pattern

Mit Hilfe der Wildcards "%" und "_" können auch unscharf definierte Begriffe gesucht werden. Jedes Vorkommen von "%" paßt auf eine beliebige Anzahl beliebiger Zeichen, jedes "_" steht für genau ein beliebiges Zeichen. Manche Datenbanken unterscheiden zwischen Groß- und Kleinschreibung, andere nicht.

Mit Hilfe der Klauseln `IS NULL` und `IS NOT NULL` kann getestet werden, ob der Inhalt einer Spalte den Wert NULL enthält oder nicht:

    ColName IS [NOT] NULL

Mit dem `BETWEEN`-Operator kann bequem festgestellt werden, ob ein Ausdruck innerhalb eines vorgegebenen Wertebereichs liegt oder nicht:

    Expr BETWEEN Expr AND Expr

Neben den einfachen Abfragen gibt es eine Reihe von Abfragen, die mit Subqueries (Unterabfragen) arbeiten:

    EXISTS (SubQuery)

    Expr [NOT] IN (SubQuery)

    Expr RelOp {ALL|ANY} (SubQuery)

Die Syntax von SubQuery entspricht der einer normalen `SELECT`-Anweisung. Sie definiert eine separat definierte Menge von Daten, die als Teilausdruck in einer Suchbedingung angegeben wird. Der `EXISTS`-Operator testet, ob die Unterabfrage mindestens ein Element enthält. Mit dem `IN`-Operator wird getestet, ob der angegebene Ausdruck in der Ergebnismenge enthalten ist. Die Ergebnismenge kann auch literal als komma-separierte Liste von Werten angegeben werden. Schließlich kann durch Angabe eines relationalen Operators getestet werden, ob der Ausdruck zu mindestens einem (`ANY`) oder allen (`ALL`) Sätzen der Unterabfrage in der angegebenen Beziehung steht. Bei den beiden letzten Unterabfragen sollte jeweils nur eine einzige Spalte angegeben werden.

Die `GROUP BY`-Klausel dient dazu, die Sätze der Ergebnismenge zu Gruppen zusammenzufassen, bei denen die Werte der angegebenen Spalten gleich sind. Sie wird typischerweise zusammen mit den oben erwähnten Aggregatfunktionen verwendet. Mit `HAVING` kann zusätzlich eine Bedingung angegeben werden, mit der die gruppierten Ergebnissätze "nachgefiltert" werden.

Mit dem `UNION`-Operator können die Ergebnismengen zweier `SELECT`-Anweisungen zusammengefaßt werden. Das wird typischerweise gemacht, wenn die gesuchten Ergebnissätze aus mehr als einer Tabelle stammen (andernfalls könnte der `OR`-Operator verwendet werden).

Die `ORDER BY`-Klausel kann angegeben werden, um die Reihenfolge der Sätze in der Ergebnismenge festzulegen. Die Sätze werden zunächst nach der ersten angegebenen Spalte sortiert, bei Wertegleichheit nach der zweiten, der dritten usw. Mit Hilfe der Schlüsselwörter `ASC` und `DESC` kann angegeben werden, ob die Werte auf- oder absteigend sortiert werden sollen.

Titel | Inhalt | Suchen | Index | DOC | Handbuch der Java-Programmierung, 3. Auflage, Addison Wesley, Version 3.0
<< | < | > | >> | API | © 1998-2002 Guido Krüger, http://www.javabuch.de