» SelfLinux » Anwendungs-Software » Datenbanken » PostgreSQL » Abschnitt 4 SelfLinux-0.12.1
zurück   Startseite Kapitelanfang Inhaltsverzeichnis GFDL   weiter

SelfLinux-Logo
Dokument PostgreSQL  Autor
 Formatierung
 GFDL
 

4 Benutzung

Dieser Abschnitt ersetzt keine SQL Referenz und kein PostgreSQL Handbuch. Es wird nur exemplarisch auf einige Details eingegangen. Dabei stehen PostgreSQL-spezifische Eigenschaften im Vordergrund.


4.1 psql

Der bereits kurz erwähnt interaktive Kommandointerpreter ist sicherlich das wichtigste Programm.


4.1.1 Kommandozeilenoptionen

psql versteht etliche Optionen:

-d Datenbank Zu dieser Datenbank verbinden
-h Servername Über TCP/IP zu diesem Server verbinden
-p Port Diesen Port verwenden (Voreinstellung 5432)
-U Benutzer Als Benutzer anmelden
-c Kommando Dieses Kommando ausführen
-f Datei Diese SQL Datei ausführen
-o Datei Ausgaben die Datei schreiben
-s Einzelschrittmodus: jedes SQL Kommando bestätigen
-E zeigt das ausgeführte SQL-Kommando bei internen Befehlen (z.B. \d) an.

Nach den Optionen gibt man eine Datenbank an, sofern man nicht -d verwendet. Dahinter kann man noch einen Benutzernamen schreiben, sofern man nicht -U verwendet.

Um als Superuser postgres zur Datenbank test zu verbinden, schreibt man also beispielsweise:

root@linux # psql -U postgres -d test

Je nach Einstellung der Authentifizierung wird nun nach einen Passwort gefragt. Es erscheint das Datenbankprompt.

Hat man PostgreSQL mit der readline-Unterstützung übersetzt, kann man ebenso wie in der Bash die Tabulator-Taste drücken, um Befehle und Objekte zu erweitern


4.1.2 Interaktion

Am Prompt kann man SQL Befehle eingeben:

test=# CREATE TEMPORARY TABLE temp
test-# ( feld1 int UNIQUE NOT NULL,
test(# feld2 varchar(100000) DEFAULT NULL );
NOTICE:  CREATE TABLE / UNIQUE will create implicit index
'temp_feld1_key' for table 'temp'
CREATE

Man sieht, das SQL Kommandos mit Semikolon abgeschlossen werden und dann automatisch ausgeführt werden. Das Prompt zeigt an, ob man in einer Klammer ist, eine kleine Hilfe. Das Beispielkommando hat nun eine einfach Testtabelle erzeugt. Diese kann man nun mit Daten füllen:

test=# INSERT INTO TEMP (feld1, feld2) VALUES (1234, 'hallo');
INSERT 1532564 1

Die Ausgabe enthält eine merkwürdige Nummer. Das ist der OID, der object identifier. Diese sollte man nicht weiter beachten (es handelt sich um eine Art automatisches Indexfeld, ist aber höchst unportabel, und wird nur intern benötigt).

Über psql kann man auch in Transaktionen arbeiten:

Die Tabelle enthält einen Datensatz:

test=# SELECT count(*) FROM temp;
count
-------
     1
(1 row)

Transkation beginnen:

test=# BEGIN;
BEGIN

Tabelle temp leermachen (alles löschen):

test=# DELETE FROM temp;
DELETE 1

Die Tabelle ist jetzt auch Sicht der Transaktion leer:

test=# SELECT count(*) FROM temp;
count
-------
     0
(1 row)

Transaktion abbrechen:

test=# ROLLBACK;
ROLLBACK

Es ist nichts geändert worden:

test=# SELECT * FROM temp;
feld1 | feld2
------+-------
1234  | hallo
(1 row)

Die Temporäre Tabelle verfällt automatisch, wenn man die Verbindung schließt.


4.1.3 Interne Kommandos

psql verfügt über eine Reihe sogenannter interner Kommandos. Diese beginnen mit einem \ (Backslash). Einige der wichtigesten internen Kommandos sind:

\? kurze Hilfe zu allen Backslash Kommandos
\d Objekt Objekt beschreiben. Ist Objekt beispielsweise eine Tabelle, so werden die Spalten und Typen angezeigt. Auch definierte Indizes werden aufgelistet. Wird Objekt nicht angegeben, werden alle Tabellen aufgelistet, die existieren (außer natürlich temporäre Tabellen).
\dKürzel Liste die zu Kürzel passenden Objekte: Tabellen (t), Indizes (i), Sequenzen (s), Views (v), Privilegien (p), Systemtabellen (S), große Objekte (l), Aggregatfunktionen (a), Kommentare (d; Objektname muss folgen), Funktionen (f), Operatoren (o) und Datentypen (T).
Durch ein Leerzeichen kann man noch ein Objekt angeben. \dp temp zeigt beispielsweise die Privilegien für die Tabelle temp an (was nur funktioniert, wenn es keine temporäre Tabelle ist).
\e Datei Öffnet das letzte Kommando oder Datei im Editor. Hilfreich, um lange Kommandos wie CREATE TABLE zu bearbeiten und zu speichern.
\l Listet alle Datenbanken auf.
\q Beendet psql
\x Erweiterte Ausgabe
\H HTML Ausgabe
\c Datenbank
\c - Benutzer
Verbindet zu einer neuen Datenbank oder zur aktuellen mit einem neuen Benutzer. Dies ist in etwa mit dem USE vergleichbar, das andere DBMS verwenden.

Es folgt ein Beispiel für das Ausgabeformat. Zunächst soll die Ausgabe der oben erwähnten Testtabelle nicht feld1 und feld2 beinhalten, sondern Nummer und Textfeld. Wenn man diese Bezeichner case-sensitiv haben möchte (Tabellen- und Feldnamen sind sonst case-insensitiv, das heißt, Groß-/Kleinschreibung wird nicht beachtet), muss man diese quoten:

test=# SELECT feld1 AS "Nummer", feld2 AS "Textfeld" FROM temp;
Nummer | Textfeld
--------+----------
   1234 | hallo
(1 row)

Nach \x sieht die Ausgabe so aus:

test=# SELECT feld1 AS "Nummer", feld2 AS "Textfeld" FROM temp;
-[ RECORD 1 ]---
Nummer   | 1234
Textfeld | hallo

Dies macht bei großen Tabellen Sinn, wenn nicht mehr alle Spalten nebeneinander auf den Bildschirm passen.


4.1.4 Verwendung

Neben der interaktiven Verwendung kann man psql dazu verwenden, SQL Skripte auszuführen, beispielsweise Skripte, die Datenbankschemata erzeugen. Man kann psql sogar dazu verwenden, Shell-Skripte mit rudimentärer Datenbankfunktionalität zu versehen; hier ist die Verwendung von Perl::DBI oder anderen Methoden jedoch oft einfacher und sauberer.


4.2 pgaccess

pgaccess ist eine graphisches Frontend, mit dem etliche Standardaufgaben erledigt werden können. Das Anlegen von Tabellen beispielsweise macht sich mit diesem Frontend wesentlich besser, also mit psql .

Über das Menü kann man zu einer Datenbank verbinden. Im folgenden Dialog können Server- und Datenbankname sowie ein Benutzerkonto angegeben werden.

Im Hauptfenster kann man rechts die anzuzeigende Objekte wählen. Hier kann man beispielsweise zwischen Tabellen, Views und Sequenzen auswählen. Im linken Teil werden dann die entsprechenden Objekte aufgelistet und können ausgewählt werden.

Nach einem Doppelklick auf eine Tabelle bekommt man ein Fenster, in dem der Inhalt dargestellt wird und geändert werden kann. Klickt man eine Tabelle nur einmal an, so kann man weitere Funktionen anwenden, beispielsweise Design. Hier öffnet sich ein Fenster, in dem man komfortabel Indizes hinzufügen kann oder neue Felder anhängen kann.

Seit Version 7.3 ist pgaccess nicht mehr Bestandteil der PostgreSQL- Distribution und muss separat von en http://www.pgaccess.org besorgt werden.


4.3 RedHat Database Admin

RedHat vertreibt eine eigene Version von PostgreSQL. Diese entspricht ungefähr der Version 7.2.3 und ist unter en http://www.redhat.com/software/database/ erhältlich. RedHat stellt alle Änderungen am DBMS und auch sein graphisches Administrationsfrontend unter die GPL. Dieses läuft auch mit einer konventionellen PostgreSQL Installation und ist unter en http://sources.redhat.com/rhdb/ zu finden. Es ist hübscher als pgaccess und bietet im Bereich der Verwaltung mehr Optionen als dieses, kann dafür aber nicht zur Definition von TCL-Formularen herangezogen werden.

Die neuen Funktionen der Version 7.3 (Schemata) werden allerdings noch nicht unterstützt.


4.4 phpPgAdmin

Dies ist ein Webfrontend und setzt einen Webbrowser voraus. Dieses Frontend verfügt über sehr viele nützliche Funktionen. Tabellendaten können als HTML Tabelle betrachtet und editiert werden, beliebige Abfragen können erstellt und ausgeführt werden.

Tabellen selbst können einfach und komfortabel bearbeitet werden, so können neue Felder hinzugefügt oder gelöscht werden. Weiterhin stehen Kopier- und Dumpfunktionen bereit. Auch Berechtigungen können komfortabel verwaltet werden. Die zur Verfügung stehenden Optionen sind sinnvoll in Auswahlfeldern aufgelistet. Bei Bedarf ist es auch möglich, eigene SQL Kommandos einzugeben und ausführen zu lassen.

Eine weitere schöne Funktion ist die Verlinkung zu jeweils passenden Seiten der PostgreSQL Dokumentation.

Wer Webfrontends mag, wird dieses Frontend wohl lieben. Es lohnt sich allemal, sich dieses zu installieren. Natürlich muss unbedingt darauf geachtet werden, den Zugang zu diesem Frontend zu schützen, da der Zugriff auf das Frontend Zugriff auf die Datenbank gestattet - und zwar als Superuser!


4.5 Transaktionen

Dieser Abschnitt geht kurz auf Transaktionen ein. Transaktionen sind notwendig, um Änderungen atomar, dass heißt, ganz oder gar nicht, durchführen zu können.

Im Folgenden wird oft der englische Ausdruck lock verwendet. Wörtlich übersetzt bedeutet er in etwa sperren. Hier ist gemeint, ein Objekt so zu benutzen, dass es niemand anders gleichzeitig benutzen kann. Lock wird später noch genauer erklärt.


4.5.1 Einführung

Das klassische Beispiel für den Bedarf ist das Buchungsbeispiel. Angenommen, es existieren zwei Kontotabellen. Möchte man nun eine Buchung gegen diese beiden Tabellen machen, muss in jede Tabelle ein neuer Datensatz angelegt werden. Dazu muss man zwei INSERT INTO SQL Kommandos ausführen lassen.

Nun könnte es ja passieren, dass eines der beiden Kommandos klappt, das andere jedoch nicht. In diesem Fall würden die Konten nicht mehr stimmen, da die Summen nicht mehr passen. Man hätte inkonsistente Daten und ein Problem.

Daher fasst man beide Kommandos zu einer Transaktion zusammen. Eine Transaktion klappt entweder ganz, oder gar nicht. Geht also eines der SQL Kommandos schief, so hat auch das andere automatisch keinen Effekt (es wird gegebenenfalls rückgängig gemacht).

Transaktionen sind für andere erst sichtbar, wenn sie abgeschlossen wurden. Das bedeutet im Beispiel, dass nach dem Ausführen der ersten Kommandos ein anderer Client diese Änderung überhaupt nicht sieht. Erst wenn das andere Kommando erfolgreich war und die Transaktion beendet wurde, werden die Änderungen sichtbar. Somit stimmen die Summen zu jedem Zeitpunkt.

Wenn innerhalb einer Transaktion Daten gelesen werden, und von einer anderen Transaktion in dieser Zeit geändert werden, so wird die Transaktion automatisch abgebrochen. Auch hier kann es nicht passieren, dass Daten versehentlich zurückgeschrieben werden, die inzwischen an anderer Stelle geändert wurden.


4.5.2 Multiversion Concurrency Control

Implementiert wird ein sogenanntes Multiversion Concurrency Control (MVCC). Das bedeutet, das Abfragen einer Transaktion die Daten so sehen, wie sie zu einem bestimmten Zeitpunkt waren, unabhängig davon, ob sie inzwischen von einer anderen Transaktion geändert wurden. Dies verhindert, dass eine Transaktion einen Teil Daten vor und einen anderen nach einer nebenläufig abgeschlossenen Transaktion lesen kann und verhindert so inkonsistentes Lesen: die Transaktionen werden von einander isoliert. Der Hauptunterschied zu Lock Verfahren ist, dass MVCC Locks für das Lesen nicht mit Locks für das Schreiben in Konflikt stehen. Somit blockiert das Schreiben nie das Lesen und das Lesen nie das Schreiben.

Eine wichtige Einschränkung gibt es: Transaktionen können in PostgreSQL nicht geschachtelt werden (es gibt also keine Untertransaktionen).


4.5.3 Transaktionslevel

PostgreSQL unterstützt zwei Transaktionslevel: read committed und serializable. Verwendet eine Transaktion read committed, so kann es vorkommen, dass sie Daten erneut liest, aber andere Daten erhält als beim ersten Lesen (nicht-wiederholbares Lesen, non-repeatble reads). Auch sogenanntes Phantom-Lesen (phantom reads) kann vorkommen. Vom Phantom-Lesen spricht man, wenn sich in einer Transaktion die Ergebnissätze von Suchbedingungen ändern können. Sogenanntes schmutziges Lesen (dirty reads), also das Lesen von Änderungen aus anderen, nicht abgeschlossenen Transaktionen kann jedoch nicht auftreten. Dieser Transaktionslevel ist die Voreinstellung. Er ist einfach anzuwenden, schnell und für die meisten Anwendungen ausreichend.

Verwendet eine Transaktion serializable, können diese beiden unerwünschten Effekte nicht auftreten. Man benutzt diesen Level für Anwendungen, die komplexe Abfragen und Änderungen durchführen.


4.5.4 Anwendung

Transaktionen werden durch das SQL Kommando BEGIN eingeleitet. Dies ist nicht standardkonform; ANSI fordert, das immer implizit eine Transaktion begonnen wird. PostgreSQL bietet jedoch wie viele andere DBMS auch eine sogenanntes auto commit Funktion an, dies ist auch das Standardverhalten. Jedes SQL Kommando wird dann so aufgefasst, als wäre es eine einzelne Transaktion (es wird also sozusagen ein implizites COMMIT nach jedem SQL Kommando ausgeführt). Möchte man nun eine aus mehreren Anweisungen bestehende Transaktion beginnen, schreibt man einfach BEGIN als erstes Kommando. Dies passt auch gut zum eingebettetem SQL, da die SQL Kommandos dadurch in einen schicken BEGIN - END Block eingeschlossen sind.

Grundsätzlich gibt es zwei Möglichkeiten, eine Transaktion zu beenden. Eine Anwendung kann eine Transaktion selbst abbrechen. Hierzu dient das Kommando ROLLBACK. Keine der Änderungen der Transaktion wird ausgeführt. Eine Anwendung kann die Transaktion auch positiv beenden. Dazu wird END oder COMMIT verwendet. Die Transaktion wird genau dann durchgeführt, wenn sie fehlerfrei war. In diesem Fall werden alle Änderungen (oder die eine komplexe Transaktionsänderung) übernommen (sichtbar). Trat in der Transaktion ein Fehler auf, so gibt es natürlich keine Möglichkeit, sie doch noch positiv zu beenden, da dies zu Inkonsistenzen führen würde. In solchen Fällen kann die Anwendung (je nach Art des Fehlers) die Transaktion wiederholen. Dies ist natürlich nicht sinnvoll, wenn beispielsweise ein Tabelle fehlt. Dann wird auch die Wiederholung fehlschlagen.

So ist also sichergestellt, dass Transaktionen nur vollständig (und vollständig erfolgreich), oder überhaupt nicht durchgeführt werden.

Hat man mit BEGIN eine Transaktion begonnen, so ist zunächst die Datenbankvoreinstellung des Transaktionslevel (read committed) aktiv. Solange die Transaktion noch nicht begonnen wurde, kann der Transaktionslevel noch geändert werden. Dazu wird das SQL Kommando SET TRANSACTION ISOLATION LEVEL verwendet. Als Parameter wird READ COMMITTED oder SERIALIZABLE angegeben. Damit ist der Transaktionslevel eingestellt. Ein Client kann auch einen eigene Voreinstellung setzen, wenn beispielsweise Transaktionen grundsätzlich serializable sein sollen. Das SQL Kommando lautet SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL und erwartet die gleichen Parameter wie das vorherige Kommando.


4.5.5 Mögliche Effekte

Verwendet man Transaktionen, so kann es natürlich vorkommen, dass eine Transaktion vom DBMS beendet wird, weil eine andere Transaktion Daten geändert hat; insbesondere, wenn serializable verwendet wird. In solchen Fällen wird die Transaktion in der Regel einfach von vorn beginnend vollständig wiederholt (die Anwendung führt diese also erneut aus).

Derartige Effekte minimiert man oft, in dem man Datensätze, von denen man schon weiß, dass man sie ändern muss, schon mal für selbiges vormerkt. Dies geschieht mit dem SQL Kommando SELECT FOR UPDATE. Nun weiß das DBMS, dass diese Datensätze der Transaktion gehören. Möchte eine andere Transaktion hier auch Daten ändern, so wartet diese automatisch, bis die erste Transaktion beendet wurde (also bestätigt oder abgebrochen). Dann erst wird die Aktion ausgeführt. Mit dem SQL Kommando LOCK TABLE können auch komplette Tabellen gesperrt werden. Verwendet man diese Mechanismen sorgfältig, vereinfacht sich die Handhabung; spätere Transaktionsabbrüche treten nicht auf, da die Daten ja bereits verwendet werden.

Es kann passieren, dass sich Transaktionen gegenseitig ausschließen. Würde beispielsweise Transaktion A die Tabelle A sperren und Transaktion B Tabelle B und anschließend Tabelle A sperrt, kommt es zu einer solchen Situation, wenn Transaktion A auch versucht, Tabelle B zu sperren. Transaktion B kann ja Tabelle A nicht sperren, weil diese schon von Transaktion A bereits gesperrt ist und blockiert, bis Transaktion A beendet wurde. Transaktion A wiederum wartet auf Transaktion B, um Tabelle B sperren zu können. Man spricht von einem Deadlock - beide Transaktionen haben sich gegenseitig blockiert.

PostgreSQL erkennt solche Fälle automatisch. Eine der beiden Transaktionen wird mit einem entsprechendem Deadlock-Fehler abgebrochen, woraufhin die andere durchgeführt werden kann. Auch hier wiederholt die Anwendung einfach die Transaktion. Da nun keine andere mehr läuft, wird es diesmal klappen.

Bei der Arbeit mit komplexen Transaktionen muss man damit rechnen, dass eine Transaktion durch solche oder ähnliche Gründe abgebrochen wird. In der Software ist also vorzusehen, Transaktionen wiederholen zu können. Da im Falle eines Transaktionsabbruches ja überhaupt keine Daten geändert werden, geht das unproblematisch. Man beginnt einfach von vorn.


4.5.6 Sperren für Tabellen

Sperre oder Lock bedeutet, dass der Inhaber oder Eigentümer dessen davor geschützt ist, dass jemand anders eine Sperre erzeugt, der dieser widerspricht. Es gibt verschiedene Arten von Sperren. Lese-Locks beispielsweise schließen sich nicht gegenseitig aus (es können ja problemlos mehrere Transaktionen die gleichen Daten lesen), jedoch schließt ein Lese-Lock einen Schreib-Lock aus. Schreib-Locks schließen sich und Lese-Locks aus. Letztere nennt man daher auch exklusiv, keine anderere Sperre kann neben einem Schreib-Lock ausgeführt sein.

Die folgende Aufstellung ist unvollständig.

AccessShareLock (lesender Zugriff) Lese-Lock, der automatisch auf Tabellen gesetzt wird, aus denen gelesen wurden. Schließt AccessExclusiveLock aus.
RowShareLock (lesender Zugriff auf Zeilen) Wird durch SELECT FOR UPDATE und LOCK TABLE IN ROW SHARE MODE gesetzt. Schließt ExclusiveLock und AccessExclusiveLock Modi aus.
RowExclusiveLock (exklusiver Zugriff auf Zeilen) Wird durch UPDATE, DELETE, INSERT und LOCK TABLE IN ROW EXCLUSIVE MODE gesetzt. Schließt ShareLock, ShareRowExclusiveLock, ExclusiveLock und AccessExclusiveLock Modi aus.
AccessExclusiveLock (exklusiver Zugriff) Gesetzt durch ALTER TABLE, DROP TABLE, VACUUM FULL und LOCK TABLE. Schließt alle Modi aus. Selbst SELECT in anderen Transaktionen blockiert in diesem Fall.

4.5.7 Sperren für Datensätze

Datensätze werden mit SELECT FOR UPDATE gesperrt. Dies schließt Änderungen an genau diesen Datensätzen aus. Wie bereits angedeutet, schließt dies kein Lesen aus (Schreiben blockiert kein Lesen).


4.5.8 Transaktionsbeispiel

Wie bereits gesagt, werden Transaktionen bei Fehlern automatisch abgebrochen. Alle Kommandos werden ignoriert:

Transaktion beginnen:

test=# BEGIN;
BEGIN

Es Kommando geht schief, zum Beispiel weil syntaktisch falsch:

test=# SYNTAX ERROR;
ERROR:  parser: parse error at or near "SYNTAX"

Die Transaktion ist abgebrochen worden. Alle Kommandos werden ab jetzt ignoriert:

test=# DELETE FROM temp;
NOTICE:  current transaction is aborted, queries ignored until
end of transaction block
*ABORT STATE*

Selbst wenn man versucht, die Transaktion positiv zu beenden, wird nichts geändert (die Transaktion wird also trozdem abgebrochen):

test=# COMMIT;
COMMIT

Die Antwort COMMIT heißt nicht, dass wirklich etwas committed wurde. Hier wurde ja ein Rollback durchgeführt. Dieses Verhalten ist bei Skripts sehr nützlich. Die Kommandos schreibt man einfach ein einen BEGIN; - END; Block (End ist das gleiche wie Commit). Bei einem Fehler wird keine Änderung ausgeführt - die Datenbank sieht genauso aus, wie vorher. Man kann das Skript korrigieren und erneut ausführen.

An dieser Stelle sei noch einmal daran erinnert, dass Strukturkommandos (wie CREATE und DROP) nicht den den Transaktionsregeln unterliegen.


4.5.9 Arbeiten mit Bedingungen

Es ist möglich, Bedingungen (CONSTRAINTS) an Tabellen zu definieren. Beispielsweise könnte man fordern, dass die Summe über alle Felder einer Tabelle null sein muss. Möchte man nun zu einem Datensatz drei addieren, muss man also von einem anderen drei abziehen. Doch kurz dazwischen ist die Bedingung ja verletzt, denn die Summe ist ja dann nicht mehr null, sondern drei!

Bedingungen können daher in Transaktionen aufgeschoben werden (DEFERRED). Das bedeutet, sie werden erst am Ende der Transaktion geprüft. Eine Bedingung kann dies aber auch verhindern. Bedingungen können so definiert werden, dass sie immer sofort geprüft werden. Bedingungen können aber auch so definiert werden, dass die Prüfung per Voreinstellung aufgeschoben wird, oder das die Bedingung explizit aufgeschoben werden kann.

Um Bedingungen aufzuschieben, die sofort geprüft werden sollen, aber auch aufgeschoben werden dürfen, verwendet man das SQL Kommando SET CONSTRAINTS ALL DEFERRED. Anstatt ALL kann man auch den Namen der Bedingung angeben (das wird auch oft gemacht). Anstatt DEFERRED kann auch IMMEDIATE eingestellt werden. Damit hat das den Gegenteiligen Effekt. Bedingungen, die per Voreinstellung aufgeschoben werden, werden dennoch sofort ausgeführt.

Schiebt man also eine Prüfung auf, so wird diese am (bisher positiven) Ende der Transaktion durchgeführt. Stellt sich nun heraus, dass die Bedingung verletzt ist, wird die Transaktion abgebrochen (und die Bedingung bleibt dadurch erfüllt).


4.6 Variablen und Zeitzonen

Es gibt einige Variablen, die das Verhalten des DBMS (für den entsprechenden Clienten) beeinflussen. Über Variablen wird beispielsweise gesteuert, wie Datumsangaben aussehen. Dies ist nicht standard konform (mit Ausnahme von TIME ZONE, hier wurde der Standard erweitert).

Variablen werden mit SET gesetzt und mit SHOW abgefragt. Mit SET wird eine Variable auf einen Wert gesetzt. Zwischen der Variable und dem Wert steht TO (oder ein Gleichheitszeichen).

Hier werden nur zwei wichtige Variablen erwähnt. Die Variable DATESTYLE setzt die Form der Datumsrepräsentation. Mögliche Werte sind German, ISO und andere.

Auch die Zeitzone kann man setzen. Hier verwendet man SET TIME ZONE. ANSI erlaubt als Parameter nur eine Zahl, beispielsweise SET TIME ZONE 2. Dies ist natürlich ungünstig, da die Sommer- und Winterzeit Unterscheidung von der Anwendung getroffen werden muss (Ist Berlin nun gerade -1 oder -2? Das hängt vom Datum ab!). PostgreSQL erlaubt jedoch auch SET TIME ZONE 'Europe/Berlin'.

An einem Beispiel wird gezeigt, wie man die aktuelle Uhrzeit mit Datum in Californien (Zeitzone PST8PDT) im ISO Format (amerikanische Notation) und in Berlin (Zone CET, Central European Time, deutsche Notation) ausgeben lassen kann.

test=# SET TIME ZONE 'PST8PDT'; SET DATESTYLE TO ISO; SELECT now();
SET VARIABLE
SET VARIABLE
              now
-------------------------------
2003-01-02 11:30:17.698728-08
(1 row)
test=# SET TIME ZONE 'CET'; SET DATESTYLE TO German; SELECT now();
SET VARIABLE
SET VARIABLE
              now
--------------------------------
02.01.2003 20:32:46.387261 CET
(1 row)

4.7 Datentypen

PostgreSQL unterstützt unter anderem die SQL92 Datentypen. Ingesammt werden viele Typen unterstützt und eigene können definiert werden. Beispiele sind int (Ganzzahlen), double precision (8 Byte Fließkomma), serial (Autoinkrementeller int), varchar (variable lange Zeichenketten), bytea (Binäre Zeichenkette, wie ANSI BLOB), timestamp (Datum und Uhrzeit), boolean (Wahrheitswert) und viele andere.

Typ-Umwandlungen werden durchgeführt, in dem man den Zieltyp durch zwei Doppelpunkte :: getrennt an den Typ anfügt: '123'::int.
Dies konvertiert die Zeichenkette 123 in einen Ganzzahltyp mit dem Wert einhundertdreiundzwanzig.


4.8 Operatoren

Neben den normalen Operatoren (OR, AND, +, -, *, || usw.) gibt viele weitere, beispielsweise Quadratwurzel (|/), LIKE und ILIKE (Patternmatching wie bei LIKE, aber case-insensitiv) auch reguläres Patternmatching (~, ~* und andere). Die Operatoren verhalten sich je nach Datentyp korrekt. Addiert man mit dem Operator + beispielsweise ein timestamp und ein intervall (also now() + intervall '2 hours'), kommt das erwartete Ergebnis heraus.


4.9 Vordefinierte FunktioneN

PostgreSQL stellt viele Funktionen bereit. Viele mathematische Funktionen sind verfügbar (sin(), cos(), abs(), random() usw). Daneben gibt es viele Zeichenkettenfunktionen (lower(), substring(), initcap(), translate(), encode(), um nur einige zu nennen). Auch die Zeit- und Datumsfunktionen sind sehr interessant und leistungsfähig. Beispielsweise gibt es current_timestamp (oder auch kurz now, eine klassische PostgreSQL-Erweiterung), extract (liefert Datumsteile, SELECT EXTRACT(MINUTE FROM TIMESTAMP '2001-02-16 20:38:40'); führt also zu 38) und age (berechnet die Differenz zwischen zwei Zeitstempeln).


4.10 Datenbanken

Das Erzeugen und Planen von Datenbanken findet sich im Abschnitt  Administration.


4.11 Tabellen

Wie in jedem anderen RDBMS werden natürlich auch Tabellen unterstützt. Diese werden mit CREATE TABLE erzeugt. Dieses Kommando ist gut ANSI konform. Es gibt temporäre Tabellen, die automatisch gelöscht werden. Tabellen und Spalten können Bedingungen besitzen, das sind beispielsweise Funktionen, die es verhindern können, sinnlose Daten einzutragen (2 stellige Postleitzahlen beispielsweise). Wie bereits in  Arbeiten mit Bedingungen genannt, können die Prüfungen gegebenenfalls auf das Transaktionsende verschoben werden.

Fremdschlüssel sind Sonderformen von Bedingungen und werden auch unterstützt. Hiermit kann man gewährleisten, dass in eine Tabellenspalte nur solche Werte eingetragen werden können, die bereits in der Spalte einer anderen Tabelle definiert sind. Hat man z.B. eine Tabelle mit Herstellern und eine mit Teilen, in welcher der Hersteller vermerkt wird, kann sichergestellt werden, dass kein ungültiger Hersteller in letzterer eingetragen wird). Bei Fremdschlüsseln kann beispielsweise eine Aktion angegeben werden, die ausgeführt werden soll, falls der Fremdschlüssel gelöscht wird: NO ACTION, RESTRICT (dann ist das ein Fehler), CASCADE (die den Schlüssel referenzierenden Datensätze auch automatisch löschen, Vorsicht, dass können dann evtl. eine ganze Menge sein!), SET NULL (Wert auf NULL setzen), SET DEFAULT (auf Voreinstellung setzen).

Beispiele ähnlich denen aus der PostgreSQL Dokumentation
-- Eine Tabelle mit Primärschlüssel und einfachem Aufbau
CREATE TABLE films (
    code             CHARACTER(5) CONSTRAINT films_pkey PRIMARY KEY,
    title            CHARACTER VARYING(40) NOT NULL,
    distributors_id  DECIMAL(3) NOT NULL,
    date_prod        DATE,
    kind             CHAR(10),
    len              INTERVAL HOUR TO MINUTE
);
-- Beispieldatensatz
INSERT INTO films (code, title, distributors_id) VALUES ('FilmA', 'Der Film A', 123);

-- Eine Tabelle mit einem Autoinkrement und einer einfachen Bedingung
CREATE TABLE distributors (
     id     DECIMAL(3) PRIMARY KEY DEFAULT NEXTVAL('serial'),
     name   VARCHAR(40) NOT NULL CHECK (name <> '')
);


-- Ein Tabelle mit Bedingung (distributors_id muss größer als 100 sein, der Name 
-- darf nicht leer sein, sonst gibt es einen Fehler
-- Das Feld modtime wird automatisch auf "jetzt" gesetzt, wenn ein
-- Datensatz eingefügt wird.
CREATE OR REPLACE TABLE distributors (
    id               DECIMAL(3) UNIQUE,
    name             VARCHAR(40),
    modtime          TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    CONSTRAINT cst_valid_distributors_id CHECK (id > 100 AND name <> '')
);
-- Datensatz einfügen:
INSERT INTO distributors (id, name) VALUES (123, 'Name');
-- Nochmal geht schief, weil id eindeutig sein muss

-- Das geht auch schief:
-- INSERT INTO distributors (id, name) VALUES (001, 'Name');
--   denn: "ExecAppend: rejected due to CHECK constraint cst_valid_distributors_id"
--   (muss ja > 100 sein)


-- Eine Tabelle mit Fremdschlüssel und benannten Bedingungen.
-- "varchar" heißt einfach: kann beliebig lang werden (also fast,
--   bei ca 1000 MB ist Ende)
CREATE TABLE lager (
    id               SERIAL PRIMARY KEY,
    films_code       CHARACTER(5),
    distributors_id  DECIMAL(3),
    info             VARCHAR DEFAULT NULL,
    CONSTRAINT fk_lager_distributors_id FOREIGN KEY (distributors_id) REFERENCES distributors(id) 
        ON DELETE RESTRICT,
    CONSTRAINT fk_lager_films_code FOREIGN KEY (films_code) REFERENCES films(code) 
        ON DELETE RESTRICT DEFERRABLE 
);
-- Datensatz einfügen
INSERT INTO lager (id, films_code, info) VALUES (123, 'FilmA', 'hallo');

-- Das geht schief:
-- INSERT INTO lager (id, films_code, info) VALUES (124, 'FilmA', 'hallo');
--   denn: "fk_lager_distributors_id referential integrity violation 
--         - key referenced from lager not found in distributors"

-- Das geht auch schief:
-- DELETE FROM distributors;
--   denn: "fk_lager_distributors_id referential integrity violation 
--         - key in distributors still referenced from lager"

Tabellen können mit dem Kommando ALTER TABLE geändert werden. Diese Kommando hat viele Formen.

Einige Beispiele:

Beispiele: ALTER TABLE
-- Eine Spalte anfügen:
ALTER TABLE lager ADD COLUMN plz VARCHAR(8);

-- Eine Spalte ändern:
ALTER TABLE lager ALTER COLUMN plz SET DEFAULT 'unsortiert';

-- Eine Spalte umbennen:
ALTER TABLE lager RENAME COLUMN plz TO zipcode;

-- Bedingung hinzufügen (PLZ muss fünfstellig sein)
ALTER TABLE lager ADD CONSTRAINT cst_zipchk CHECK (char_length(zipcode) = 5);

-- Bedingung entfernen
ALTER TABLE lager DROP CONSTRAINT cst_zipchk RESTRICT;

-- Tabelle umbennnen
ALTER TABLE lager RENAME TO lagermitplz;
ALTER TABLE lagermitplz RENAME TO lager;

-- Eigentümer ändern
ALTER TABLE lager OWNER TO steffen;
	 

Ab Version 7.3 wird endlich auch SQL 92 ALTER TABLE DROP COLUMN unterstützt. Gibt es einen Index, eine Bedingung oder einen Fremdschlüssel der die zu löschende Spalte referenziert, muss die Option CASCADE mit angegeben werden.

Für ältere Versionen hat sich folgende Vorgehensweise bewährt: Man muss die Tabelle neu erzeugen. Diese Funktion wird übrigens von phpPgAdmin unterstützt (das heißt, es gibt einen DROP Knopf, der im Prinzip das tut). Im Folgenden wird ein Workaround gezeigt. Es werden hier gleich noch ein paar weitere Kommandos demonstriert.

Beispiele
-- Workaround für fehlendes:
-- ALTER TABLE lager DROP COLUMN zipcode;

-- Daten in Temp-Tabelle:

BEGIN;

-- Tabelle exklusiv schützen:
LOCK TABLE lager IN ACCESS EXCLUSIVE MODE;
-- LOCK TABLE lager; macht das gleiche (Voreinstellung ist ACCESS EXCLUSIVE)

CREATE TEMPORARY TABLE temp AS SELECT id, films_code, distributors_id, info FROM lager;

-- lager Tabelle neu erstellen
DROP TABLE lager;
CREATE TABLE lager (
    id               SERIAL PRIMARY KEY,
    films_code       CHARACTER(5),
    distributors_id  DECIMAL(3),
    info             VARCHAR DEFAULT NULL,
    CONSTRAINT fk_lager_distributors_id FOREIGN KEY (distributors_id) REFERENCES distributors(id) 
        ON DELETE RESTRICT,
    CONSTRAINT fk_lager_films_code FOREIGN KEY (films_code) REFERENCES films(code) 
        ON DELETE RESTRICT
        DEFERRABLE 
);
-- Achtung, die Berechtigungen und Bedingungen der Tabelle müssen 
--   noch gesetzt werden!

-- neue Tabelle füllen
INSERT INTO lager SELECT * FROM temp;

-- vielleicht noch prüfen
-- SELECT * FROM lager LIMIT 100;

DROP TABLE temp;
-- nicht unbedingt notwendig, passiert sonst bei Ende der 
--   Sitzung automatisch

-- Transaktion abschließen
END;
	 

Füllt man (beispielsweise neue) Tabellen mit sehr vielen Daten, so ist INSERT langsam. Die schnellste Möglichkeit ist das Füllen über COPY. Bei sehr vielen Datensätzen spart es auch Zeit, die Indizes zu löschen und anschließend neu zu erzeugen. Traut man den Daten, weil diese beispielsweise aus einem Backup kommen, so bringt es auch oft sehr viel Zeitersparnis, wenn man die Trigger und Bedingungen löscht und nach dem Füllen wieder neu anlegt.

Eine Erweiterung ist die Möglichkeit CREATE TABLE AS, die eine Tabelle aus einer SELECT-Abfrage erzeugt. Das ist äquivalent zu einer INSERT INTO Erweiterung, mit der auch Tabellen erzeugt werden können (beides ist nicht Standard-SQL). Um standardkonform zu sein, muss man zunächst ein CREATE TABLE machen und diese dann über INSERT ... SELECT füllen.


4.12 Views

Views sehen aus Sicht der Anwendung aus wie Tabellen. Manchmal werden sie sogar als virtuelle Tabellen bezeichnet. Es sind Sichten auf Tabellen. Eine View stellt eine Abfrage (ein SELECT Kommando) dar. Diese Abfrage kann beispielsweise nur einige der Spalten einer Tabelle enthalten. Die Abfrage kann auch über einen join mehrere Tabellen verbinden und so Werte aus verschiedenen Tabellen anzeigen.

Ein großer Vorteil von Views ergibt sich, wenn man sich an die Privilegien erinnert. Über Views kann man es erreichen, dass nur bestimmte Felder sichtbar sind. In diesem Fall definiert man einen View über die erlaubten Felder und gibt dem entsprechenden Benutzer Rechte auf den View - nicht aber auf die Tabelle.

Momentan können Views so erstmal nur zum Lesen von Daten, nicht jedoch zum Ändern benutzt werden. Möchte man Daten auch ändern können, so verwendet man eine PostgreSQL Erweiterung, eine Regel. Im später später folgenden Abschnitt zu Regeln wird dies exemplarisch erklärt.


4.13 Cursors

Das Cursorkonzept stammt aus eingebettetem SQL (ESQL). Eingebettet heißt, dass man SQL Anweisungen direkt in Programmquelltexte einbettet (diese Programmiertechnik wurde inzwischen durch Standards wie ODBC weitgehend abgelöst; ESQL wird jedoch auch heute noch verwendet und auch von PostgreSQL unterstützt). In PostgreSQL stehen Cursors unabhängig von der Verwendung von ESQL zur Verfügung. Man kann sie beispielsweise auch über psql interaktiv verwenden.

Einem aktiven Cursor ist eine Menge von Datensätzen assoziiert, die über eine Abfrage, also über ein SELECT Kommando, ausgewählt wurden. Man kann nun einzelne Datensätze oder Teilmengen der Datensatzmenge über den Cursor holen. Der Cursor merkt sich dabei die Position. Holt man beispielsweise dreimal einen Datensatz aus einem Cursor, so erhält man automatisch die ersten drei Datensätze. Der Cursor zählt sozusagen mit, was auch den Namen erklärt. Eine Besonderheit ist, dass man über Cursors (in PostgreSQL, das gilt nicht generell) auch rückwärts gehen kann, also dass man Datensätze mehrfach holen kann.

Cursors funktionieren in PostgreSQL nur in Transaktionen. Um einen Cursor zu verwenden, muss dieser zunächst deklariert werden. Man kann sich vorstellen, dass man einer Abfrage einen (temporären) Namen gibt. Dann kann man Datensätze (die Ergebnise der Abfrage) holen. Man kann den Cursor auch verschieben, beispielsweise, um Datensätze auszulassen oder erneut zu verarbeiten. Wird der Cursor nicht mehr benötigt, so wird er mit CLOSE geschlossen.

Besonderheiten in PostgreSQL sind, dass aus einem Cursor nicht über absolute Positionen gelesen werden kann und das Cursordaten nicht geändert werden können (es gibt kein DECLARE FOR UPDATE). Ein Cursor ist also immer READ ONLY. Durch die Transaktionsforderung ist er auch immer INSENSITIVE, auch wenn dies nicht explizit mit angeben wurde. Auch SCROLL ist nicht notwendig, da ein Cursor immer SCROLL kann. Es muss auch kein OPEN auf einen Cursor gemacht werden.

Ein einfaches Beispiel folgt.

cursor.sql
-- Die Tabelle sieht so aus:
test=> SELECT code, title FROM films WHERE distributors_id = 124;
 code  |      title
-------+------------------
 MM-dt | Mädchen, Mädchen
 IJ1   | Indiana Jones 1
 IJ2   | Indiana Jones 2
 IJ3   | Indiana Jones 3
(4 rows)

-- Transaktion starten
test=> BEGIN; 
BEGIN 

-- Einen Cursor für Indiana Jones deklarieren.
test=> DECLARE ijfilme INSENSITIVE CURSOR FOR 
test->   SELECT code, title FROM films 
test->   WHERE code LIKE 'IJ%'
test->   ORDER BY code
test-> FOR READ ONLY; 
DECLARE 

-- Ersten Datensatz holen
test=> FETCH NEXT FROM ijfilme;
 code  |      title
-------+-----------------
 IJ1   | Indiana Jones 1
(1 row)

-- Zweiten Datensatz holen (1 ist wie NEXT)
test=> FETCH 1 FROM ijfilme;
 code  |      title
-------+-----------------
 IJ2   | Indiana Jones 2
(1 row)
  
-- Einen Datensatz zurückgehen:
test=> FETCH -1 FROM ijfilme;
 code  |      title
-------+-----------------
 IJ1   | Indiana Jones 1
(1 row)


-- Die nächsten zwei Datensätze holen:
test=> FETCH 2 FROM ijfilme;
 code  |      title
-------+-----------------
 IJ2   | Indiana Jones 2
 IJ3   | Indiana Jones 3
(2 rows)

-- Hier ist Ende:
test=> FETCH 1 FROM ijfilme;
 code | title
------+-------
(0 rows)

-- weit Zurückspringen (an den Anfang)
test=> MOVE -100 FROM ijfilme;
MOVE 3

-- wieder am Anfang
test=> FETCH 1 FROM ijfilme;
 code  |      title
-------+-----------------
 IJ1   | Indiana Jones 1
(1 row)

-- Rest holen
test=> FETCH ALL FROM ijfilme;
 code  |      title
-------+-----------------
 IJ2   | Indiana Jones 2
 IJ3   | Indiana Jones 3
(2 rows)

-- Den letzten nochmal (wie -1)
test=> FETCH PRIOR FROM ijfilme;
 code  |      title
-------+-----------------
 IJ3   | Indiana Jones 3
(1 row)

-- Cursor schließen
test=> CLOSE ijfilme;
CLOSE

-- Transaktion abbrechen
test=> ROLLBACK;
ROLLBACK

4.14 Indizes

Ein Index dient dazu, Datensätze mit bestimmten Eigenschaften schnell zu finden. Hat man beispielsweise eine Tabelle films wie im Beispiel Tabellen und sucht den Film mit dem code FilmA, so müsste ja die gesamte Tabelle durchsucht werden (und dazu vor allem von Festplatte geladen werden), dann müsste jeder code geprüft werden, ob er denn dem gesuchten entspricht.

Hier verwendet man einen Index. Ein Index gilt für eine bestimmte Tabellenspalte, also beispielsweise für code. Er kann aber auch aus mehreren zusammengesetzten Spalten bestehen. Ein Index ist eine effiziente Speicherung aller code Werte und einem Verweis auf die Stelle, an der der zugehörige Datensatz gespeichert ist. Wie genau die Speicherung funktioniert, hängt vom Typ des Index ab. Es gibt beispielsweise HashIndizes und binäre Bäume.

Sucht man nun FilmA, so wird nur der Index geladen, der ja viel kleiner ist, als die ganze Tabelle. Es wird an der entsprechenden Stelle nachgesehen (bei einem Hash geht das bei einer Gleichoperation mit einem Zugriff), dann direkt die richtige Stelle (oder die richtigen Stellen) der Tabelle geladen. Das ist dann wesentlich schneller.

Indizes sind aber nicht immer günstig. Hat man beispielsweise viele Datensätze, beispielsweise alle, so muss eh sehr viel von der Tabelle geladen werden. Hier bremst es nur, zusätzlich den Index zu laden (der Abfrageplaner würde in solchen Fällen den Index aber automatisch nicht verwenden, weil er das auch weiß, mehr dazu später). Das gleiche Verhalten kann man auch bei kleinen Tabellen erwarten (wenn man beispielsweise 100 aus 1000 Datensätzen liest, ist ein Index oft nicht günstig und wird nicht verwendet). Ein Index verlangsamt auch Änderungen, da nicht nur die Tabelle, sondern auch der Index aktualisiert werden muss.

Ein Index kann auch Eindeutigkeit (UNIQUE) fordern. Genauer gesagt, wird Eindeutigkeit in Tabellen garantiert, in dem ein UNIQUE Index angelegt wird. Dies sollte man aber lieber durch ein sauberes ALTER TABLE ... ADD CONSTRAINT erledigen. Das dann ein Index verwendet wird, ist ein Implementierungsdetail von PostgreSQL.

Die bereits kurz erwähnten Speichertypen von Indizes sind: BTREE (Lehman-Yao B-Baum), RTREE (R-Baum mit Guttman's "quadratic split" Algorithmus), HASH (Litwin's lineares hashen) und GIST (Generalized Index Search Trees, verallgemeinerter Index Suchbaum).

BTREE kann bei den Operationen <, <=, =, >=, > verwendet werden. RTREE bei den Operationen <<, &<, &>, >>, @, ~=, && und ein HASH bei =.

Indexes kann man per Hand erzeugen. Dazu gibt es das nicht-standard SQL Kommando CREATE INDEX. Zum Löschen gibt es