Zum Inhalt springen
aviral gupta

// B4.2 · ca. 30 Min. · Einstieg

RETURNING: die Zeilen, die Sie gerade geändert haben

Nach dieser Lektion holen Sie die id einer neuen Zeile direkt aus ihrem INSERT, sehen, was ein UPDATE oder DELETE geändert hat, und zeigen alte und neue Werte nebeneinander.

Lektion 2 von 5 in B4 Daten und Schema ändern

Danach können Sie

  • Die von INSERT, UPDATE und DELETE geänderten Zeilen mit RETURNING zurückholen, auch eine erzeugte id
  • Vorhersagen, was RETURNING standardmäßig zeigt: die eingefügte Zeile, die neuen Werte, die gelöschte Zeile
  • Alte und neue Werte mit old. und new. aus PostgreSQL 18 nebeneinander zeigen und sie mit WITH umbenennen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Die Datenbank nummeriert jede neue Ausleihe selbst. Wie erfahren Sie in derselben Anweisung die Nummer der Ausleihe, die Sie gerade angelegt haben?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es voraus, bevor Sie weiterlesen. Dune hat 2 Exemplare, The Hobbit 3, jedes andere Buch 1. Welche title und copies gibt das UPDATE zurück?

    UPDATE books
    SET copies = copies - 1
    WHERE copies > 1
    RETURNING title, copies
  3. Üben · Aufgabe 3 von 7

    Die Ausleihen haben die ids 1 bis 5. Ergänzen Sie die Spalte, damit das INSERT die Nummer zurückgibt, die PostgreSQL der neuen Ausleihe gibt.

    INSERT INTO loans (book_id, member_id, loaned_on) VALUES (4, 4, '2026-10-03') RETURNING ____;
    INSERT INTO loans (book_id, member_id, loaned_on) VALUES (4, 4, '2026-10-03') RETURNING ;
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem RETURNING zu, was es zeigt.

  5. Üben · Aufgabe 5 von 7

    Bringen Sie die Teile der Anweisung in die richtige Reihenfolge.

    1. 1.SET copies = copies + 1
    2. 2.WHERE genre = 'sci-fi'
    3. 3.RETURNING title, old.copies, new.copies;
    4. 4.UPDATE books
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Drei Bücher sind sci-fi. SET lässt jeden Wert, wie er war. Wie viele Zeilen gibt das zurück?

    UPDATE books
    SET copies = copies
    WHERE genre = 'sci-fi'
    RETURNING title
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Mit der Bibliothek aus dem durchgerechneten Beispiel: Hedy (hedy@example.com, aus Wien, in den Daten Vienna) tritt am 2026-10-03 bei. Legen Sie sie mit einem INSERT an, das ihre neue id zurückgibt. Halten Sie mit dieser id ihre Ausleihe von The Hobbit (Buch 5) am selben Tag fest und geben Sie wieder die id der neuen Ausleihe zurück. Dann kauft die Bibliothek ein Exemplar mehr von jedem fantasy-Buch: Geben Sie jeden Titel mit seinen Exemplaren vorher und nachher zurück.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Geänderte Zeilen zurückbekommen

setup.sql lädt die Bibliothek aus Modul B1; hier nummeriert PostgreSQL die Mitglieder und Ausleihen selbst. main.sql legt eine Ausleihe an und bekommt ihre id zurück, markiert eine Ausleihe als zurückgegeben, fügt jedem sci-fi-Buch ein Exemplar hinzu, mit den Werten vorher und nachher, und löscht ein Mitglied, wobei es die gelöschte Zeile zeigt. Auf Ihrem Computer: psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql.

main.sql

-- 1. A new loan: PostgreSQL picks the id, RETURNING shows it.
INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (4, 4, '2026-10-03')
RETURNING id, book_id, loaned_on;

-- 2. Kindred comes back: RETURNING shows the row after the update.
UPDATE loans
SET returned_on = '2026-10-03'
WHERE id = 2
RETURNING id, loaned_on, returned_on;

-- 3. PostgreSQL 18: the old and the new value side by side.
UPDATE books
SET copies = copies + 1
WHERE genre = 'sci-fi'
RETURNING title, old.copies AS before, new.copies AS after;

-- 4. A deleted row, shown one last time.
DELETE FROM members
WHERE name = 'Margaret'
RETURNING *;

setup.sql

CREATE TABLE books (
  id integer,
  title text,
  author text,
  year integer,
  genre text,
  copies integer
);

INSERT INTO books VALUES
  (1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
  (2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
  (3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
  (4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
  (5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
  (6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);

-- PostgreSQL numbers the members and the loans itself (lesson B4.4).
CREATE TABLE members (
  id integer GENERATED ALWAYS AS IDENTITY,
  name text,
  email text,
  city text,
  joined date
);

INSERT INTO members (name, email, city, joined) VALUES
  ('Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
  ('Grace', 'grace@example.com', 'Munich', '2025-03-02'),
  ('Linus', NULL, 'Berlin', '2026-02-10'),
  ('Margaret', 'margaret@example.com', NULL, '2026-05-20');

CREATE TABLE loans (
  id integer GENERATED ALWAYS AS IDENTITY,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

INSERT INTO loans (book_id, member_id, loaned_on, returned_on) VALUES
  (1, 1, '2026-09-01', '2026-09-10'),
  (3, 1, '2026-09-12', NULL),
  (2, 2, '2026-09-05', '2026-09-20'),
  (1, 3, '2026-09-15', NULL),
  (6, 2, '2026-09-18', NULL);

Ausführen mit

psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql

Ausgabe

 id | book_id | loaned_on
----+---------+------------
  6 |       4 | 2026-10-03
(1 row)

 id | loaned_on  | returned_on
----+------------+-------------
  2 | 2026-09-12 | 2026-10-03
(1 row)

    title    | before | after
-------------+--------+-------
 Dune        |      2 |     3
 Kindred     |      1 |     2
 Neuromancer |      1 |     2
(3 rows)

 id |   name   |        email         | city |   joined
----+----------+----------------------+------+------------
  4 | Margaret | margaret@example.com |      | 2026-05-20
(1 row)
  • Das INSERT hat keine id angegeben; PostgreSQL hat 6 gewählt, die nächste Nummer, und RETURNING zeigt sie.
  • returned_on ist schon das neue Datum: Das RETURNING eines UPDATE zeigt die Zeile nach der Änderung.
  • old.copies und new.copies zeigen jedes sci-fi-Buch vor und nach dem UPDATE, in einer Anweisung.
  • Das DELETE gibt Margarets Zeile zurück, wie sie war; ihre leere Stadt ist NULL.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Die neuen ids zurückbekommen

Hedy (hedy@example.com, aus Wien, in den Daten Vienna) tritt der Bibliothek am 2026-10-03 bei. Der Startcode legt sie an, gibt aber nichts zurück. Lassen Sie das INSERT ihre id und ihren Namen zurückgeben. Legen Sie dann ihre Ausleihe von The Hobbit (Buch 5) am 2026-10-03 an, mit der id aus der ersten Anweisung als member_id, und geben Sie id, book_id und member_id der Ausleihe zurück.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    RETURNING steht ganz am Ende des INSERT, nach der VALUES-Liste.

  2. Hinweis 2

    Die erste Anweisung gibt 5 zurück; verwenden Sie das als member_id in der Ausleihe.

  3. Hinweis 3

    INSERT INTO loans (book_id, member_id, loaned_on) VALUES (5, 5, '2026-10-03') RETURNING id, book_id, member_id;

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

INSERT INTO members (name, email, city, joined)
VALUES ('Hedy', 'hedy@example.com', 'Vienna', '2026-10-03')
RETURNING id, name;

INSERT INTO loans (book_id, member_id, loaned_on)
VALUES (5, 5, '2026-10-03')
RETURNING id, book_id, member_id;
Auf dem eigenen Computer ausführen

Installieren Sie PostgreSQL 18 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.sql

-- setup.sql has loaded books, members and loans.
INSERT INTO members (name, email, city, joined)
VALUES ('Hedy', 'hedy@example.com', 'Vienna', '2026-10-03');

test.sql

-- test: Hedy ist ein Mitglied aus Wien (Vienna), von PostgreSQL mit 5 nummeriert
SELECT EXISTS (SELECT 1 FROM members WHERE id = 5 AND name = 'Hedy' AND city = 'Vienna');

-- test: Ihre offene Ausleihe von The Hobbit ist Ausleihe 6
SELECT EXISTS (SELECT 1 FROM loans WHERE id = 6 AND book_id = 5 AND member_id = 5 AND loaned_on = '2026-10-03' AND returned_on IS NULL);

-- test: Beide Anweisungen geben die neuen Zeilen zurück
-- output:
--  id | name
-- ----+------
--   5 | Hedy
-- (1 row)
--
--  id | book_id | member_id
-- ----+---------+-----------
--   6 |       5 |         5
-- (1 row)

setup.sql

CREATE TABLE books (
  id integer,
  title text,
  author text,
  year integer,
  genre text,
  copies integer
);

INSERT INTO books VALUES
  (1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
  (2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
  (3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
  (4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
  (5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
  (6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);

-- PostgreSQL numbers the members and the loans itself (lesson B4.4).
CREATE TABLE members (
  id integer GENERATED ALWAYS AS IDENTITY,
  name text,
  email text,
  city text,
  joined date
);

INSERT INTO members (name, email, city, joined) VALUES
  ('Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
  ('Grace', 'grace@example.com', 'Munich', '2025-03-02'),
  ('Linus', NULL, 'Berlin', '2026-02-10'),
  ('Margaret', 'margaret@example.com', NULL, '2026-05-20');

CREATE TABLE loans (
  id integer GENERATED ALWAYS AS IDENTITY,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

INSERT INTO loans (book_id, member_id, loaned_on, returned_on) VALUES
  (1, 1, '2026-09-01', '2026-09-10'),
  (3, 1, '2026-09-12', NULL),
  (2, 2, '2026-09-05', '2026-09-20'),
  (1, 3, '2026-09-15', NULL),
  (6, 2, '2026-09-18', NULL);

psql führt die Dateien auf Ihrem eigenen PostgreSQL-Server aus. Nehmen Sie eine leere Datenbank, die Sie wegwerfen können: Hängen Sie -d und ihren Namen an den Befehl an.

Programm ausführen:

psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql

Für die Prüfungen gibt es auf Ihrem Computer noch keinen Befehl. Sie stehen in test.sql: Jede Prüfabfrage gibt t aus, wenn sie besteht.

Übung 2 von 2

Vorher und nachher

Die Bibliothek verdoppelt jedes Buch, von dem es nur ein Exemplar gibt. Der Startcode macht das, gibt aber nur die neuen copies zurück. Lassen Sie es title, die copies vorher als before und nachher als after zurückgeben. Löschen Sie dann jede zurückgegebene Ausleihe (returned_on ist nicht NULL) und geben Sie id und book_id jeder gelöschten Ausleihe zurück.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser PostgreSQL herunter (bis zu 10.1 MB) und speichert es im Cache. Jeder Lauf beginnt mit einer leeren Datenbank. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Bei einem UPDATE ist ein einfaches copies in RETURNING der neue Wert. old.copies ist der Wert vorher.

  2. Hinweis 2

    Benennen Sie die Spalten mit AS: old.copies AS before, new.copies AS after.

  3. Hinweis 3

    DELETE FROM loans WHERE returned_on IS NOT NULL RETURNING id, book_id;

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

UPDATE books
SET copies = copies * 2
WHERE copies = 1
RETURNING title, old.copies AS before, new.copies AS after;

DELETE FROM loans
WHERE returned_on IS NOT NULL
RETURNING id, book_id;
Auf dem eigenen Computer ausführen

Installieren Sie PostgreSQL 18 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.sql

-- Double the copies of every book with one copy.
UPDATE books
SET copies = copies * 2
WHERE copies = 1
RETURNING title, copies;

test.sql

-- test: Die Bücher haben jetzt insgesamt 13 Exemplare
SELECT sum(copies) = 13 FROM books;

-- test: Nur die drei offenen Ausleihen bleiben
SELECT count(*) = 3 AND count(returned_on) = 0 FROM loans;

-- test: Das UPDATE zeigt vorher und nachher, das DELETE die gelöschten Ausleihen
-- output:
--     title    | before | after
-- -------------+--------+-------
--  Emma        |      1 |     2
--  Kindred     |      1 |     2
--  Beloved     |      1 |     2
--  Neuromancer |      1 |     2
-- (4 rows)
--
--  id | book_id
-- ----+---------
--   1 |       1
--   3 |       2
-- (2 rows)

setup.sql

CREATE TABLE books (
  id integer,
  title text,
  author text,
  year integer,
  genre text,
  copies integer
);

INSERT INTO books VALUES
  (1, 'Dune', 'Frank Herbert', 1965, 'sci-fi', 2),
  (2, 'Emma', 'Jane Austen', 1815, 'classic', 1),
  (3, 'Kindred', 'Octavia E. Butler', 1979, 'sci-fi', 1),
  (4, 'Beloved', 'Toni Morrison', 1987, 'fiction', 1),
  (5, 'The Hobbit', 'J. R. R. Tolkien', 1937, 'fantasy', 3),
  (6, 'Neuromancer', 'William Gibson', 1984, 'sci-fi', 1);

-- PostgreSQL numbers the members and the loans itself (lesson B4.4).
CREATE TABLE members (
  id integer GENERATED ALWAYS AS IDENTITY,
  name text,
  email text,
  city text,
  joined date
);

INSERT INTO members (name, email, city, joined) VALUES
  ('Ada', 'ada@example.com', 'Berlin', '2025-01-15'),
  ('Grace', 'grace@example.com', 'Munich', '2025-03-02'),
  ('Linus', NULL, 'Berlin', '2026-02-10'),
  ('Margaret', 'margaret@example.com', NULL, '2026-05-20');

CREATE TABLE loans (
  id integer GENERATED ALWAYS AS IDENTITY,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

INSERT INTO loans (book_id, member_id, loaned_on, returned_on) VALUES
  (1, 1, '2026-09-01', '2026-09-10'),
  (3, 1, '2026-09-12', NULL),
  (2, 2, '2026-09-05', '2026-09-20'),
  (1, 3, '2026-09-15', NULL),
  (6, 2, '2026-09-18', NULL);

psql führt die Dateien auf Ihrem eigenen PostgreSQL-Server aus. Nehmen Sie eine leere Datenbank, die Sie wegwerfen können: Hängen Sie -d und ihren Namen an den Befehl an.

Programm ausführen:

psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql

Für die Prüfungen gibt es auf Ihrem Computer noch keinen Befehl. Sie stehen in test.sql: Jede Prüfabfrage gibt t aus, wenn sie besteht.

Häufige Fehler

RETURNING vor WHERE

UPDATE loans
SET returned_on = '2026-10-03'
RETURNING *
WHERE id = 2;

Was psql ausgibt

ERROR:  syntax error at or near "WHERE"

Warum, und die Lösung

RETURNING ist die letzte Klausel der Anweisung. PostgreSQL liest RETURNING * als Ende des UPDATE und erwartet danach kein WHERE. Ziehen Sie WHERE nach oben: UPDATE loans SET returned_on = '2026-10-03' WHERE id = 2 RETURNING *. Dasselbe gilt für DELETE … WHERE … RETURNING.

Einer GENERATED-ALWAYS-id einen Wert geben

INSERT INTO members (id, name, email)
VALUES (5, 'Hedy', 'hedy@example.com')
RETURNING id;

Was psql ausgibt

ERROR:  cannot insert a non-DEFAULT value into column "id"

Warum, und die Lösung

members.id ist GENERATED ALWAYS AS IDENTITY: PostgreSQL wählt jede id selbst und lehnt eine ab, die Sie angeben. Lassen Sie id aus der Spaltenliste weg und lassen Sie sich von RETURNING id sagen, welche Nummer die Zeile bekommen hat. psql ergänzt einen HINT zu OVERRIDING SYSTEM VALUE; das ist zum Kopieren vorhandener Daten gedacht, nicht für alltägliche Inserts.

old. nach dem Umbenennen

UPDATE books
SET copies = copies + 1
WHERE id = 5
RETURNING WITH (OLD AS o, NEW AS n) old.copies, n.copies;

Was psql ausgibt

ERROR:  missing FROM-clause entry for table "old"

Warum, und die Lösung

WITH (OLD AS o, NEW AS n) gibt der alten und der neuen Zeile andere Namen, und dann sind die Namen old und new in dieser RETURNING-Liste verdeckt. Verwenden Sie durchgehend die neuen Namen: RETURNING WITH (OLD AS o, NEW AS n) o.copies, n.copies. Ohne den WITH-Teil funktionieren old.copies und new.copies so, wie sie sind.

PostgreSQL im Browser: PGlite 0.5.8 (PostgreSQL 18.3), Apache-2.0 und PostgreSQL License. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

RETURNING gibt die geänderten Zeilen zurück

INSERT, UPDATE und DELETE melden normalerweise nur, wie viele Zeilen sie geändert haben. Setzen Sie RETURNING und eine Liste ganz ans Ende der Anweisung, gibt der Befehl für jede geänderte Zeile eine Zeile zurück. Die Liste funktioniert wie eine SELECT-Liste: RETURNING id, title, ein Ausdruck wie copies * 2 AS doubled oder RETURNING * für alle Spalten. Ein Befehl, der keine Zeile ändert, gibt null Zeilen zurück, keinen Fehler. Der klassische Fall ist ein Wert, den die Datenbank wählt. In setup.sql sind members.id und loans.id GENERATED ALWAYS AS IDENTITY (Lektion B4.4 erklärt das): PostgreSQL nummeriert neue Zeilen selbst, und INSERT … RETURNING id liefert Ihnen die neue Nummer ohne zweite Abfrage.

Welche Fassung der Zeile RETURNING sieht

Bei einem INSERT sieht RETURNING die Zeile, wie sie eingefügt wurde; eine weggelassene Spalte zeigt also ihren Standardwert oder NULL. Bei einem UPDATE sieht es den neuen Inhalt: Nach SET copies = copies + 1 zeigt RETURNING copies die erhöhte Zahl. Bei einem DELETE sieht es die gelöschte Zeile ein letztes Mal, sodass Sie festhalten können, was Sie entfernt haben. UPDATE schreibt jede Zeile, die sein WHERE trifft, auch wenn SET den Wert unverändert lässt, und RETURNING meldet jede davon. Die Zeilen kommen in der Reihenfolge zurück, in der der Befehl sie geändert hat; RETURNING kennt kein ORDER BY, verlassen Sie sich also auf keine Reihenfolge.

old und new, neu in PostgreSQL 18

In PostgreSQL 18 kann RETURNING sagen, welche Fassung gemeint ist: old.copies ist der Wert vor dem Befehl, new.copies der Wert danach, und old.* oder new.* liefern die ganze Zeile. So zeigt ein UPDATE vorher und nachher nebeneinander: RETURNING title, old.copies AS before, new.copies AS after. Ein INSERT hat keine alte Zeile, seine old-Werte sind also NULL; ein DELETE hat keine neue Zeile, seine new-Werte sind NULL. Ein einfacher Spaltenname behält seine Standardbedeutung. RETURNING WITH (OLD AS o, NEW AS n) o.copies, n.copies benennt die beiden um; old und new sind dann verdeckt, und old.copies scheitert mit missing FROM-clause entry for table "old".

Quellen

Zuletzt geprüft am 3. Oktober 2026