Bei SQL-Schlüsselwörtern wie z.B. Symbol Bedeutung | "oder" - Auswahl von Elementen möglich [ ] Das Klammerpaar schließt optinale Element ein * Element müssen nicht und können mehrmals verwendet werden { } Zusammenfassen von Elementen, damit die Symbole |, [], * verwendet werden können Die Syntax zum Erzeugen einer Tabelle lautet wie folgt: CREATE TABLE tableName ( columnDefinition [, columnDefinition]* [, tableConstraint]* ) Eine Spaltendefinition lässt sich aus folgender Ableitungsregel bestimmen: (1) columnDefinition ::= columnName dataType [columnConstraint]* [{DEFAULT {constantExpression | NULL}} | generatedColumnSpec] Für jede Spalte wird ein Name und ein Datentyp angegeben. Der Spaltenname darf nur einmal innerhalb der Tabelle auftreten und es muss mindestens eine Spalte definiert werden. Mehrere Spaltendefinitionen werden durch Kommata voneinander getrennt. Die Datentypangabe hinter den Spaltennamen legt den SQL-Datentyp der
Tabellenspalte fest. Es gibt zum Teil größere Unterschiede zwischen den SQL-Typen,
die von den einzelnen DBMS unterstützt werden. So lautet z.B. ein SQL-Typname beim
DBMS Derby SQL-Typ <---> JDBC-Typ <---> Java-Typ Viele SQL-Typnamen des DBMS Derby stimmen mit den JDBC-Typnamen überein. Die folgende Anordnung zeigt die Zuordnungen der einzelnen Datentypen (die genannten SQL-Typen sind eine größere Auswahl der vom DBMS Derby unterstützten Datentypen): SQL-Typ (Derby) JDBC-Typ Java-Typ SMALLINT SMALLINT short INTEGER INTEGER int BIGINT BIGINT long REAL REAL float DOUBLE DOUBLE double DECIMAL DECIMAL java.math.BigDecimal CHAR CHAR String VARCHAR VARCHAR String DATE DATE java.sql.Date TIME TIME java.sql.Time TIMESTAMP TIMESTAMP java.sql.Timestamp CHAR FOR BIT DATA BINARY byte[] VARCHAR FOR BIT DATA VARBINARY byte[] BLOB BLOB java.sql.Blob CLOB CLOB java.sql.Clob Beim einigen SQL-Typen sind zusätzliche Angaben möglich oder nötig, so ist
z.B. bei (2) dataType ::= SMALLINT | { INTEGER | INT } | BIGINT | REAL | DOUBLE | { DECIMAL | DEC } [(precision [, scale ])] | CHAR[ACTER] [(length)] | { VARCHAR | CHAR VARYING | CHARACTER VARYING }(length) | DATE | TIME | TIMESTAMP | { CHAR | CHARACTER }[(length)] FOR BIT DATA | { VARCHAR | CHAR VARYING | CHARACTER VARYING } (length) FOR BIT DATA | { BLOB | BINARY LARGE OBJECT } ( length [{K |M |G }])) | { CLOB | CHARACTER LARGE OBJECT } (length [{{K |M |G}])) Ein einfaches Beispiel für eine Tabellenerzeugung könnte wie folgt aussehen: CREATE TABLE personen ( id INTEGER, name VARCHAR(50), vorname VARCHAR(50), passbild BLOB(100 K), geschlecht CHAR(1) )
Erläuterung zum SQL-Typ Ein stmt.executeUpdate("CREATE TABLE test (b CHAR(3) FOR BIT DATA)"); stmt.executeUpdate("INSERT INTO test VALUES (x'cafe')"); Dabei entpricht der Beispiel-Bitfolge ResultSet rs = stmt.executeQuery("SELECT * FROM test"); rs.next(); byte[] b = rs.getBytes(1); for (int j = 0; j < b.length; j++) { int i = (b[j] & 0x7f) + (b[j] < 0 ? 128 : 0); String s = Integer.toBinaryString(i); StringBuffer sb = new StringBuffer("00000000"); sb = sb.replace(8 - s.length(), 8, s); System.out.print(sb + " "); } Zunächst wird mit 11001010 11111110 00100000 Die Bitfolge
Erläuterung zum SQL-Typ Der SQL-Typ java.sql Interface Blob Methode: InputStream getBinaryStream() byte[] getBytes(long pos, int length) long length() Die Methode java -classpath .;c:/user/db-derby/lib/derby.jar BLOBExample Im Beispiellisting wird eine kleine
Bilddatei
im JPEG-Format verwendet. Die Grafikdatei wird aus einem Ordner
heraus aufgerufen ( Listing 6.2. /* * BLOBExample.java * */ import java.sql.*; import java.awt.*; import java.awt.image.*; import java.io.*; import javax.imageio.*; import javax.swing.*; public class BLOBExample extends JPanel { int id; String name; String vorname; BufferedImage bi; String geschlecht; public void createAndShowGUI(JPanel panel) { JFrame.setDefaultLookAndFeelDecorated(true); JFrame frame = new JFrame("BLOBExample"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.add(panel); frame.setSize(400, 170); frame.setVisible(true); } public void sqlMethod() { try { Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); } catch (ClassNotFoundException e) { System.out.println(e.toString()); System.exit(1); } try { Connection con = DriverManager.getConnection("jdbc:derby:derbyDB;create=true"); Statement stmt = con.createStatement(); // Tabelle personen bereits vorhanden? DatabaseMetaData meta = con.getMetaData(); ResultSet rs = meta.getTables(null, "APP", null, null); while (rs.next()) { if (rs.getString("TABLE_NAME").toLowerCase().equals("personen")) { stmt.executeUpdate("DROP TABLE personen"); } } String strCreateTable = "CREATE TABLE personen (" + "id INTEGER," + "name VARCHAR(50)," + "vorname VARCHAR(50)," + "passbild BLOB(100 K)," + "geschlecht CHAR(1)" + ")"; stmt.executeUpdate(strCreateTable); System.out.println("Tabelle personen angelegt"); File f = new File("images/lena100x100.jpg"); int flength = (int)f.length(); FileInputStream fin = null; try { fin = new FileInputStream(f); } catch (FileNotFoundException e) { System.out.println(e.toString()); } PreparedStatement ps = con.prepareStatement( "INSERT INTO personen VALUES (?, ?, ?, ?, ?)"); ps.setInt(1, 1); ps.setString(2, "jpg"); ps.setString(3, "lena"); ps.setBinaryStream(4, fin, flength); ps.setString(5, "w"); ps.executeUpdate(); rs = stmt.executeQuery("SELECT * FROM personen"); rs.next(); id = rs.getInt(1); name = rs.getString(2); vorname = rs.getString(3); Blob bl = rs.getBlob(4); InputStream in = bl.getBinaryStream(); try { bi = ImageIO.read(in); } catch (IOException e) { System.out.println(e.toString()); } geschlecht = rs.getString(5); stmt.executeUpdate("DROP TABLE personen"); System.out.println("Tabelle personen geloescht"); rs.close(); ps.close(); stmt.close(); con.close(); } catch (SQLException e) { while (e != null) { System.out.println(e.toString()); System.out.println("ErrorCode: " + e.getErrorCode()); System.out.println("SQLState: " + e.getSQLState()); e = e.getNextException(); } } } public static void main(String[] args) { final BLOBExample be = new BLOBExample(); be.sqlMethod(); Runnable runner = new Runnable() { public void run() { be.createAndShowGUI(be); } }; SwingUtilities.invokeLater(runner); } public void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D)g; g2.drawImage(bi, null, 10, 10); g2.drawString("id: " + Integer.toString(id), 150, 20); g2.drawString("Name: " + name, 150, 40); g2.drawString("Vorname: " + vorname, 150, 60); g2.drawString("Geschlecht: " + geschlecht, 150, 80); } } Das Beispiel erzeugt ein Ausgabefenster: Innerhalb der Tabellendefinition können Randbedingungen (engl. constraints)
aufgenommen werden. Es sind zwei Arten von Randbedingungen möglich:
Spaltenrandbedingungen sind einer Spalte fest zugeordnet und Tabellenrandbedingungen
können auch für mehrere Spalten gelten. Der Constraint-Zusatz ist ein optionaler Teil
der (3) columnConstraint ::= NOT NULL | {[CONSTRAINT constraintName] { CHECK (searchCondition) | { UNIQUE | PRIMARY KEY | REFERENCES tableName [...] } }} Eine Tabellenrandbedingung genügt der folgenden Ableitungsregel: (4) tableConstraint ::= [CONSTRAINT constraintName] { CHECK (searchCondition) | { UNIQUE (columnName [,columnName]*) | PRIMARY KEY (columnName [,columnName]*) | FOREIGN KEY (columnName [,columnName]*) REFERENCES tableName [...] } }
Eine vorname VARCHAR(50) NOT NULL Diese Spaltenrandbedingung erfordert, dass die Spalte mit einem definierten
Wert belegt wird - eine Belegung mit dem Schlüsselwort
Bezeichnung einer Randbedingung (constraint) Eine Randbedingung kann mit einem Namen versehen werden. Dies kann sinnvoll sein, wenn eine Randbedingung verletzt wird, da bei einer entsprechenden SQL-Fehlerbehandlung der Name der Randbedingung mit angegeben werden kann.
Bei den folgenden SQL-Anweisungen wurde eine
CREATE TABLE personen ( id INTEGER, name VARCHAR(50), vorname VARCHAR(50), passbild BLOB(100 K), geschlecht CHAR(1) CHECK (geschlecht IN ('m', 'w')) ) INISERT INTO personen VALUES (1, 'im Glueck', 'Hans', null , 'x') Bei Verwendung der obigen SQL-Anweisungen innerhalb eines Java-Programms mit entsprechender Fehlerbehandlung würde z.B. die folgende Fehlermeldung ausgegeben (Derby): SQL Exception: The check constraint 'SQL060510092203260' was violated while performing an INSERT or UPDATE on table 'APP.PERSONEN'. Das Präfix geschlecht CHAR(1) CONSTRAINT gesch_ck CHECK (geschlecht IN ('m', 'w')) bei der Spaltendefinition verwendet lautet die Fehlermeldung: SQL Exception: The check constraint 'GESCH_CK' was violated while performing an INSERT or UPDATE on table 'APP.PERSONEN'.
Tabellenspalten, die mit dem Schlüsselwort CREATE TABLE personen ( id INTEGER, name VARCHAR(50) NOT NULL UNIQUE, vorname VARCHAR(50) NOT NULL, passbild BLOB(100 K), geschlecht CHAR(1) ) INSERT INTO personen VALUES (1, 'im Glueck', 'Hans', null , 'm') INSERT INTO personen VALUES (2, 'Dampf', 'Hans', null , 'm') INSERT INTO personen VALUES (3, 'im Glueck', 'Harald', null , 'm') //Fehler! Die ersten beiden Einfügeanweisungen werden noch problemlos ausgeführt, da
die zweite Tabellenspalte mit unterschiedlichen Inhalten gefüllt wird. Bei der
dritten SQL Exception: The statement was aborted because it would have caused a duplicate key value in a unique or primary key constraint or unique index identified by 'SQL060510002431670' defined on 'PERSONEN'. Es bietet sich im Beispiel an, eine Personentabelle zuzulassen, die gleiche
Personennamen zulässt, wenn sich die jeweiligen Personenvornamen unterscheiden.
Bei einer derartig strukturierten Tabelle könnt eine
CREATE TABLE personen ( id INTEGER, name VARCHAR(50) NOT NULL, vorname VARCHAR(50) NOT NULL, passbild BLOB(100 K), geschlecht CHAR(1), UNIQUE (name, vorname) ) INSERT INTO personen VALUES (1, 'im Glueck', 'Hans', null , 'm') INSERT INTO personen VALUES (2, 'Dampf', 'Hans', null , 'm') INSERT INTO personen VALUES (3, 'im Glueck', 'Harald', null , 'm') INSERT INTO personen VALUES (4, 'im Glueck', 'Hans', null , 'm') //Fehler! Hier würden die ersten drei Einfügeanweisungen ausgeführt und lediglich bei der letzten Einfügeanweisung wäre eine Fehlermeldung zu erwarten.
Eine Primärschlüsselrandbedingung ermöglicht eine eindeutige Identifikation
einer jeden Tabellenzeile. Wird die CREATE TABLE personen ( id INTEGER NOT NULL PRIMARY KEY, ... )
Eine Fremdschlüsselrandbedingung schafft Abhängigkeiten zwischen mehreren
Tabellen (Tabellenspalten). Diese Randbedingung spezifiziert eine einzelne Spalte
(Spaltenrandbedingung) oder mehrere Spalten (Tabellenrandbedingung) einer
referenzierenden Tabelle als Fremschlüssel. Eine Tabelle, von dem eine Referenz
ausgeht (referenzierende Tabelle) wird auch als Kindtabelle und die Tabelle auf
die referenziert wird auch als Elterntabelle bezeichnet. Zum Beispiel soll die
Elterntabelle CREATE TABLE autoren ( name VARCHAR(50) NOT NULL PRIMARY KEY, voname VARCHAR(50), isbn VARCHAR(50) ) INSERT INTO autoren VALUES ('im Glueck', 'Hans', 'ISBN 1-23456-789-0') Als nächstes soll die Kindtabelle CREATE TABLE personen ( id INTEGER NOT NULL PRIMARY KEY, name VARCHAR(50) REFERENCES autoren(name), vorname VARCHAR(50), passbild BLOB(100 K), geschlecht CHAR(1) ) INSERT INTO personen VALUES (1, 'im Glueck', 'Hans', null , 'm') Die Schlüsselbezeichnung java -classpath .;c:/user/db-derby/lib/derby.jar ForeignKeyExample Listing 6.3. /* ForeignKeyExample.java */ import java.sql.*; public class ForeignKeyExample { public static void main(String[] args) { try { Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); } catch (ClassNotFoundException e) { System.out.println(e.toString()); System.exit(1); } try { Connection con = DriverManager.getConnection( "jdbc:derby:derbyDB;create=true"); Statement stmt = con.createStatement(); // Tabellen personen, autoren bereits vorhanden? DatabaseMetaData meta = con.getMetaData(); ResultSet rs = meta.getTables(null, "APP", null, null); while (rs.next()) { if (rs.getString("TABLE_NAME").toLowerCase().equals("personen")) { stmt.executeUpdate("DROP TABLE personen"); } if (rs.getString("TABLE_NAME").toLowerCase().equals("autoren")) { stmt.executeUpdate("DROP TABLE autoren"); } } String strCreateTable = "CREATE TABLE autoren (" + "name VARCHAR(50) NOT NULL," + "vorname VARCHAR(50) NOT NULL," + "isbn VARCHAR(50)," + "PRIMARY KEY(name, vorname)" + ")"; stmt.executeUpdate(strCreateTable); stmt.executeUpdate("INSERT INTO autoren VALUES ('im Glueck', 'Hans', " + "'ISBN 1-23456-789-0')"); strCreateTable = "CREATE TABLE personen (" + "id INTEGER," + "name VARCHAR(50)," + "vorname VARCHAR(50)," + "passbild BLOB(100 K)," + "geschlecht CHAR(1)," + "FOREIGN KEY(name, vorname) REFERENCES autoren(name, vorname)" + ")"; stmt.executeUpdate(strCreateTable); stmt.executeUpdate("INSERT INTO personen VALUES (1, 'im Glueck', " + "'Hans', null , 'm')"); System.out.println("Tabellen angelegt"); stmt.executeUpdate("DROP TABLE personen"); stmt.executeUpdate("DROP TABLE autoren"); System.out.println("Tabellen geloescht."); rs.close(); stmt.close(); con.close(); } catch (SQLException e) { while (e != null) { System.out.println(e.toString()); System.out.println("ErrorCode: " + e.getErrorCode()); System.out.println("SQLState: " + e.getSQLState()); e = e.getNextException(); } } } } Für einzelne Tabellespalten können Vorgabewerte innerhalb der
(5) generatedColumnSpec :== GENERATED {ALWAYS | BY DEFAULT} AS IDENTITY [ (START WITH integerConstant [,INCREMENT BY integerConstant]) ] Das nächste kurze Beispiel verwendet fest vorgegebene (mittels
java -classpath .;c:/user/db-derby/lib/derby.jar GeneratedColumnValueExample Listing 6.4. /* * GeneratedColumnValueExample.java * */ import java.sql.*; public class GeneratedColumnValueExample { public static void main(String[] args) { try { Class.forName("org.apache.derby.jdbc.EmbeddedDriver"); } catch (ClassNotFoundException e) { System.out.println(e.toString()); System.exit(1); } try { Connection con = DriverManager.getConnection("jdbc:derby:derbyDB;create=true"); Statement stmt = con.createStatement(); // Tabelle personen bereits vorhanden? DatabaseMetaData meta = con.getMetaData(); ResultSet rs = meta.getTables(null, "APP", null, null); while (rs.next()) { if (rs.getString("TABLE_NAME").toLowerCase().equals("personen")) { stmt.executeUpdate("DROP TABLE personen"); } } String strCreateTable = "CREATE TABLE personen (" + "id INTEGER GENERATED BY DEFAULT AS IDENTITY " + "(START WITH 5, INCREMENT BY 5)," + "name VARCHAR(50) DEFAULT 'im Glueck'," + "vorname VARCHAR(50)," + "passbild BLOB(100 K) DEFAULT NULL," + "geschlecht CHAR(1)" + ")"; stmt.executeUpdate(strCreateTable); System.out.println("Tabelle angelegt"); stmt.executeUpdate("INSERT INTO personen (vorname) values ('Hans')"); stmt.executeUpdate("INSERT INTO personen (id, vorname) values (99, 'Harald')"); stmt.executeUpdate("INSERT INTO personen (name, vorname) values ('Dampf', 'Hans')"); rs = stmt.executeQuery("SELECT * FROM personen"); while (rs.next()) { System.out.println(rs.getInt(1) + ", " + rs.getString(2) + ", " + rs.getString(3) + ", " + rs.getBlob(4) + ", " + rs.getString(5)); } stmt.executeUpdate("DROP TABLE personen"); System.out.println("Tabelle geloescht."); rs.close(); stmt.close(); con.close(); } catch (SQLException e) { while (e != null) { System.out.println(e.toString()); System.out.println("ErrorCode: " + e.getErrorCode()); System.out.println("SQLState: " + e.getSQLState()); e = e.getNextException(); } } } } Bei der Konsolenausgabe erscheinen die drei folgenden Zeilen: 5, im Glueck, Hans, null, null 99, im Glueck, Harald, null, null 10, Dampf, Hans, null, null Der Wert für die erste Tabellenspalte wir automatisch generiert und verwendet,
falls bei einer Einfügeanweisung der ersten Spalte nicht explizit ein Wert zugewiesen
wird (siehe zweite Tabellenreihe). Der Zahlenwert startet mit 5 und erhöht sich
bei jeder weiteren Einfügung einer Zeile um 5). Die zweite Spalte hat einen Default-Wert
|
|