Auto-Increment-Spalten in Datenbanken Holger Jakobs – [email protected], [email protected] 2012-06-25 Inhaltsverzeichnis 1 Oracle 2 2 PostgreSQL 3 3 MS Access 2000 4 Jede Tabelle sollte einen Primärschlüssel (primary key) haben, der ein Tupel eindeutig identifiziert. Für manche Dinge bietet sich ein solcher Schlüssel geradezu an, z. B. die ISBN1 (International Standard Book Number) für Buchtitel, ISSN2 (International Standard Serial Number) für Periodika oder die EAN3 (European Article Number) für Artikel des Handels. Manche Datenbank-Frontends, z. B. OpenOffice.org Base, verweigern die Änderung von Daten bei Tabellen ohne Primärschlüssel. Bei anderen Dingen bietet sich nicht unmittelbar ein solcher Schlüssel an, so dass man oft einen sogenannten „künstlichen Schlüssel“ vergibt. Dieser kann aus einer laufenden Nummer bestehen. Damit man diese nicht selbst mühsam hochzählen muss, kann man dies der Datenbank überlassen. Leider gibt der SQL-Standard diesbezüglich nichts her, so dass die einzelnen Datenbanksysteme hier proprietäre Lösungen bieten – wenn überhaupt. Natürlich könnte man auf die Idee kommen, eine select-Abfrage von max(nr) zu machen und selbst 1 zu addieren, um diesen Wert anschließend beim Einfügen des neuen Tupels zu verwenden. Dies hat den Charme, dass die neue Nummer bereits in einer Eingabemaske angezeigt werden kann, allerdings mit einem erheblichen Nachteil – denn es kann passieren, dass parallel laufende Transaktionen das Gleiche tun. Nur beim Einfügen des ersten Tupels wird es also gut gehen; bei allen anderen ist der Primärschlüssel dann schon vorhanden. Dann könnte man auf die Idee kommen, das Tupel unmittelbar nach dem Abfragen der höchsten Nummer einzufügen und sofort zum Ändern zu selektieren (select . . . for update. Aber auch hier gibt es eine „race condition“4 , denn auch dies können mehrere 1) 2) 3) 4) http://www.isbn.org http://www.issn.org http://www.ean-int.org siehe http://de.wikipedia.org/wiki/Race_Condition 1 1 ORACLE Transaktionen parallel versuchen. Die Wahrscheinlichkeit eines Fehlers ist hier gering, aber genau das ist das Problem: Es tritt erst beim Kunden auf, nicht beim Test. Weiterer Nachteil ist, dass das Tupel bei Abbruch der Eingabe wieder gelöscht werden muss, während es bei Verwendung von Auto-Increment nur erzeugt wird, wenn die Eingabe auch abgeschickt wird. Alle Lösungsansätze mit einem select max(nr) from tabelle oder ähnlich muss man als schlicht falsch zurückweisen. Daher bieten die Datenbanksysteme auch richtige Lösungen hierfür, was im Folgenden beispielhaft beschrieben wird. 1 Oracle Bei Oracle gibt es Sequenzen, die mittels create sequence erzeugt werden können. Allerdings ist es nicht möglich, durch einfache Angabe eines „Datentyps“ eine Sequenz zu erzeugen und zu benutzen. Um die jeweils nächste Zahl aus der Sequenz zu verwenden, kann man einen entsprechenden Aufruf manuell beim insert mit angeben, aber das wäre ja nicht automatisch und kann leicht vergessen werden. Besser ist es daher, einen Trigger zu generieren, der automatisch den gewünschten Wert einfügt. Mit einer einfachen default-Angabe klappt es bei Oracle leider nicht. create create nr name ); sequence mitarb_nr_seq; table mitarb ( integer primary key, varchar(20) -- Beispiel-Eingabe für Benutzung der Sequenz ohne Trigger insert into mitarb values (mitarb_nr_seq.nextval, 'Fritz Meier'); -- Trigger-Erzeugung zum automatischen Ergänzen des Wertes -- im neuen Tupel create trigger mitarb_nr_trig before insert on mitarb for each row when (new.nr is null) begin select mitarb_nr_seq.nextval into :new.nr from dual; end; / -- Slash ist nötig, um den Fehler "success with compilation error" -- zu vermeiden Die Prüfung auf null ist notwendig, denn es könnte ja sein, dass explizit ein Wert für das Attribut nr angegeben wurde, der dann nicht durch eine Zahl aus der Sequenz über- 2 2 POSTGRESQL schrieben werden soll. Möchte man das Einfügen expliziter Werte verhindern, kann man die when-Klausel weglassen. Bei Verwendung einer Sequenz benötigt man also drei Datenbank-Objekte: Sequenz, Tabelle und Trigger. Möchte man wissen, welcher Wert eingefügt wurde, so kann man den zuletzt eingefügten Wert ermitteln mit select sequenzname.currval from dual;, oder im Programm die Abfrage select sequenzname.nextval from dual; ausführen und den gelieferten Wert im anschließenden insert-Statement benutzen. 2 PostgreSQL In PostgreSQL gibt es 2 Möglichkeiten, das Gewünschte zu erreichen. Möchte man einen ab 1 zählenden Zähler haben, der immer um 1 erhöht wird, so genügt die Angabe serial als „Datentyp“. Es wird automatisch eine Sequenz erzeugt, die beim Löschen des Attributs aus der Datenbank gelöscht wird. Möchte man die Sequenz über die Lebensdauer des Attributs erhalten, dann muss man sie separat mittels create sequence sequenzname erzeugen. So erzeugte Sequenzen müssen bei Bedarf mit drop sequence sequenzname gelöscht werden. Beispiel für ein einfaches, automatisch hochzählendes Attribut als Primärschlüssel: create table mitarb ( nr serial primary key, name varchar(20) ); Auch bei spezielleren Wünschen, d. h. anderem Startwert oder anderer Schrittweite muss man mittels create sequence eine Sequenz erzeugen und den jeweils nächsten Wert aus dieser Sequenz als default-Wert für das gewünschte Attribut angeben. Beispiel: create create nr name ); sequence mitarb_nr_seq; table mitarb ( integer default nextval('mitarb_nr_seq') primary key, varchar(20) Natürlich kann man den Namen der Sequenz völlig frei wählen; der oben gezeigte Name entspricht dem von der Datenbank automatisch generierten bei Verwendung von serial. Bei Verwendung einer Sequenz benötigt man also zwei Datenbank-Objekte: Sequenz und Tabelle (mit default-Wert). Falls man erwartet, dass im Laufe der Zeit ein integer-Zähler nicht ausreicht, also überlaufen könnte, bietet sich der Typ bigserial an, der einen 64-Bit-Ganzzahltyp verwendet. Ob man die Spalte eindeutig macht oder zum Primärschlüssel erhebt, ist einem selbst überlassen, d. h. möchte man es tun, muss man die Klauseln unique bzw. primary key hinzufügen. 3 3 MS ACCESS 2000 Falls man anderen Benutzern das Recht zum Einfügen nicht direkt an der Tabelle, sondern nur über eine Sicht (View5 ) erlaubt hat, ist es notwendig, den Benutzern auch das UPDATE-Recht an der Sequenz ausdrücklich zu geben: grant update on sequenzname to benutzer/gruppe Möchte man wissen, welcher Wert eingefügt wurde, so kann man den zuletzt eingefügten Wert ermitteln mit select currval(’sequenzname’);. Alternativ kann man sich im Programm einen Wert besorgen mittels select nextval(’sequenzname’); und diesen beim anschließenden insert verwenden. 3 MS Access 2000 Bei MS Access kann man einen automatisch hochzählenden Datentyp auswählen. Der Felddatentyp heißt dort „Auto-Wert“. In einem create table-Statement verwendet man den „Datentypen“ autoincrement, wodurch tatsächlich ein langer Ganzzahlwert mit automatischem Hochzählen eingerichtet wird. Eine separate Sequenz als eigenes Datenbankobjekt wird bei Access nicht erzeugt. Daher kann man auch nicht mehrere verschiedene Tabellen haben, die sich aus derselben Quelle eindeutiger Nummern bedienen. Bei Verwendung einer Sequenz benötigt man also nur ein Datenbank-Objekt: Tabelle (mit autoincrement-Angabe). Möchte man wissen, welcher Wert eingefügt wurde, so kann man nach dem Einfügen des Tupels die Abfrage select @@identity; (ohne Angabe einer Tabelle) verwenden. $Id: auto_increment.tex,v bef207c043da 2008/11/24 10:46:08 bibjah $ 5) siehe auch Dokument „Views in SQL“ 4