PL/pgSQL

https://www.postgresql.org/docs/current/static/plpgsql.html

Prozedurale DB-Sprache für PostgreSQL

Funktionen

CREATE [OR REPLACE] FUNCTION name (arg argtype)
[RETURNS rettype | RETURNS TABLE (colname coltype)] AS $$
...
$$ LANGUAGE plpgsql;

Funktionen

SELECT sum(2,3);
CREATE FUNCTION sum (integer, integer) RETURNS integer AS $$
BEGIN
$1 + $2
END
$$ LANGUAGE plpgsql;
CREATE FUNCTION sum (IN a integer, IN b integer, OUT c integer) AS $$
BEGIN
c = a + b
END
$$ LANGUAGE plpgsql;

Eingabe IN, OUT, INOUT, VARIADIC (zugriff mit $i möglich)

Ausgabe RETURN NEXT exp; concatinated tupel an Ergebnis - oder auch void

Variablen Typen

name [CONSTANT] type [NOT NULL] [{DEFAULT | =} expression];

Typ

matrknr integer;
input1 ALIAS FOR $1;
rang CONSTANT varchar(2) = 'C4';

Copying Types von anderen Variable oder Tabellenspalte

matrnr Studenten.MatrNr%TYPE;
-- MatrNr Studenten.Matrnr%TYPE;  ist schlechter Stil
aktuell vorl%TYPE;

Row Types von Tabelle (nicht veränderbar)

student Studenten%ROWTYPE;

Record Types von Tabelle (veränderbar, wiederverwendbar)

ergebnis RECORD;
SELECT * INTO ergebnis FROM Studenten;
SELECT * INTO ergebnis FROM Professoren;

Befehle

Befehl ohne Ergebnis

PERFORM statement

INSERT, UPDATE, ... brauchen kein prefix beim Aufrufen

Andere Befehle zB SELECT brauchen PERFORM davor.

Befehle mit einer Zeile als Ergebnis

SELECT expr INTO [STRICT] target FROM ...;
INSERT expr RETURNING expr INTO [STRICT] target;

INTO lädt einzeiliges Ergebnis in Typ RECORD oder ROWTYPE .

Mit STRICT muss genau eine Zeile zurückgeliefert werden (ansonsten wird die erste gewählt).

String parsen und executen

EXECUTE cmdString [INTO [STRICT] var] [USING expr]; 
EXECUTE format('SELECT count(*) FROM %I WHERE Sem > $1', tabname) INTO c; 

Kontrollstrukturen

BLOCK

[<<label>>] -- name für Block
[DECLARE declarations] -- lokale Variablen
BEGIN
statements
[EXCEPTION exceptionhandling]
END [label];

IF THEN ELSE

IF expr THEN
statements
[ELSIF expr THEN
statements]
[ELSE
statements]
END IF;

CASE

CASE note
WHEN 1,2,3,4 THEN txt = 'Positiv';
WHEN 5 THEN txt = 'Negativ';
END CASE;
CASE
WHEN note = 1,2,3,4 THEN txt = 'Positiv';
WHEN note = 5 THEN txt = 'Negativ';
END CASE;

WHILE LOOP, FOR LOOP

[<<label>>]
WHILE expr LOOP
statements
END LOOP [label];
[<<label>>]
FOR expr IN [REVERSE] expressions [BY expr] LOOP
statements
END LOOP [label];

verlassen oder skippen

EXIT/CONTINUE [label] [WHEN expr];

FOR EACH

[<<label>>]
FOR target IN query LOOP
statements
END LOOP [label];

FOR s IN SELECT * FROM Studenten LOOP
INSERT INTO hoeren VALUES (s.MatrNr, 184686)
END
CREATE OR REPLACE FUNCTION suche(matrnr numeric(10)) RETURNS void AS $$
DECLARE
name varchar(30);
semester numeric(2);
BEGIN
SELECT s.name, s.semester INTO name, semester
FROM students s WHERE s.matrnr = matrnr;
IF (name IS NULL) THEN
RAISE NOTICE 'nothing found';
ELSE
RAISE NOTICE 'Name: %, Semester: %', name, semester;
END IF;
END
$$

Exception Handling

Statements vor rollback ausgeführt - Zugriff auf lokalen Variablen möglich

BEGIN
statements
EXCEPTION
WHEN conds THEN statements
WHEN conds THEN statements ...
END;

-- Note auf 1 setzen oder neu erstellen
LOOP
-- updaten
UPDATE pruefen SET Note=1 WHERE MatrNr=mn AND VorlNr=vn AND PersNr=pn;
RETURN WHEN FOUND;
-- neues tupel erstellen
BEGIN
INSERT INTO pruefen VALUES (mn, vn, pn, 1);
RETURN;
EXCEPTION WHEN uniquie_violation THEN -- hier leer lassen
END;END LOOP;

Cursors

name refcursor;
name [[NO] SCROLL] CURSOR [(args)] FOR query;

FOR query , SCROLL und NO SCROLL wenn nicht gebunden

-- nur deklarieren
curs1 refcursor;-- für Tabelle
curs2 CURSOR FOR SELECT * FROM Studenten;-- für Query
curs3 CURSOR (mn integer) FOR
SELECT *
FROM Studenten
WHERE MatrNr=mn;
OPEN curs1 FOR SELECT * FROM Professoren;
OPEN curs2;
OPEN curs3(42);

Befehle

OPEN öffnen vor verwendung

FETCH liest nächste Zeile, sonst NULL

FOUND sagt quasi hasNext()

MOVE erlaubt bewegung ohne zu lesen

CLOSE schließen nach Verwendung

Fetchen

FETCH [direction {FROM | IN}] cursor INTO target;

FETCH curs1 INTO rowvar;
FETCH LAST FROM curs2 INTO stud;
FETCH PRIOR FROM curs2 INTO stud;
FETCH RELATIVE 2 FROM curs2 INTO stud;
FETCH curs3 INTO mn, name, sem;

Insert/Delete

Vor der Abfrage bei cursor FOR UPDATE dazuschreiben.

UPDATE table SET ... WHERE CURRENT OF cursor;
DELETE FROM table ... WHERE CURRENT OF cursor;

Vorlesungen(VorlNr, SWS):
INSERT INTO Vorlesungen VALUES (26120, 3), (27550, 4):CREATE FUNCTION reassign() RETURNS void AS $$
DECLARE
c CURSOR FOR SELECT * FROM Vorlesungen FOR UPDATE;
c2 refcursor;
count integer = 0;
row RECORD;
BEGIN
-- ausgeben und VorlNr mit Count ändern
FOR r IN c LOOP
RAISE NOTICE '(%, %)', r.VorlNr, r.SWS;
UPDATE Vorlesungen SET VorlNR=count WHERE CURRENT OF c
count = count+1;
END LOOP;
-- erneut ausgeben
OPEN c2 FOR SELECT * FROM Vorlesungen;
LOOP
FETCH c2 INTO row;
RAISE NOTICE '(%, %)', row.VorlNr, row.SWS;
EXIT WHEN NOT FOUND;
END LOOP;END;
$$ LANGUAGE plpsql;

Ausgabe: (26120, 3) , (27550, 4) , (0, 3) , (1, 4)