Zum Inhalt springen
aviral gupta

// B2.4 · ca. 30 Min. · Einstieg

NULL, IS NULL und COALESCE

Nach dieser Lektion finden Sie die fehlenden Werte einer Tabelle, verhindern, dass sie still Zeilen verschwinden lassen, und zeigen an ihrer Stelle einen sinnvollen Ersatzwert.

Lektion 4 von 5 in B2 Abfragen auf einer Tabelle

Danach können Sie

  • Erklären, was NULL bedeutet, und Vergleiche und WHERE-Bedingungen vorhersagen, die auf ein NULL treffen
  • Fehlende Werte mit IS NULL, IS NOT NULL und IS DISTINCT FROM finden und die NOT-IN-Falle vermeiden
  • NULL mit COALESCE ersetzen, einen Wert mit NULLIF zu NULL machen und Text mit concat() verbinden
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Linus hat keine E-Mail-Adresse angegeben, und seine Zeile wurde mit NULL in der Spalte email eingefügt. Was steht in der Spalte?

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es voraus, bevor Sie weiterlesen. Genau ein Mitglied, Linus, hat keine E-Mail. Was liefert dies?

    SELECT count(*) FROM members WHERE email = NULL;
  3. Üben · Aufgabe 3 von 7

    Ergänzen Sie das Schlüsselwort, damit die Abfrage das Mitglied ohne E-Mail findet.

    SELECT name FROM members WHERE email ____ NULL;
    SELECT name FROM members WHERE email NULL;
  4. Üben · Aufgabe 4 von 7

    Ordnen Sie jedem Ausdruck sein Ergebnis zu.

  5. Üben · Aufgabe 5 von 7

    Zwei Ausleihen kamen zurück, am 2026-09-10 und am 2026-09-20; die anderen drei sind noch unterwegs (returned_on ist NULL). Was liefert dies?

    SELECT count(*) FROM loans WHERE returned_on > '2026-09-15';
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Ada und Linus wohnen in Berlin, Grace in München (Munich), Margarets Stadt ist NULL. Jemand hat NULL in die Liste geschrieben, „um die Mitglieder ohne Stadt auszuschließen“. Was liefert dies?

    SELECT count(*) FROM members WHERE city NOT IN ('Munich', NULL);
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie mit der Bibliothek des Beispiels zwei Abfragen: jedes Mitglied mit Name, E-Mail und Stadt, wobei eine fehlende E-Mail als (none) und eine fehlende Stadt als unknown erscheint; und die ids der Ausleihen, die noch offen sind. Ersetzen Sie dann IS NULL durch = NULL, sagen Sie das Ergebnis voraus und führen Sie es aus.

    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

Die Lücken der Bibliothek finden und füllen

setup.sql lädt die Bibliothek aus Modul B1: Bücher, Mitglieder und Ausleihen. main.sql findet die Mitglieder mit einem fehlenden Wert, zeigt eine Kontaktliste mit gefüllten Lücken und listet die Ausleihen, die noch offen sind. Auf Ihrem Computer: psql -X -q -v ON_ERROR_STOP=1 -f setup.sql -f main.sql.

main.sql

-- 1. Who is missing an email or a city?
SELECT name, email, city
FROM members
WHERE email IS NULL OR city IS NULL
ORDER BY id;

-- 2. The same gaps, filled for display.
SELECT name,
       coalesce(email, '(no email)') AS contact,
       name || ' (' || city || ')' AS with_pipes,
       concat(name, ' (', city, ')') AS with_concat
FROM members
ORDER BY id;

-- 3. Loans that are still open.
SELECT id, book_id, member_id, loaned_on
FROM loans
WHERE returned_on IS NULL
ORDER BY id;

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);

CREATE TABLE members (
  id integer,
  name text,
  email text,
  city text,
  joined date
);

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

CREATE TABLE loans (
  id integer,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

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

Ausführen mit

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

Ausgabe

   name   |        email         |  city
----------+----------------------+--------
 Linus    |                      | Berlin
 Margaret | margaret@example.com |
(2 rows)

   name   |       contact        |   with_pipes   |  with_concat
----------+----------------------+----------------+----------------
 Ada      | ada@example.com      | Ada (Berlin)   | Ada (Berlin)
 Grace    | grace@example.com    | Grace (Munich) | Grace (Munich)
 Linus    | (no email)           | Linus (Berlin) | Linus (Berlin)
 Margaret | margaret@example.com |                | Margaret ()
(4 rows)

 id | book_id | member_id | loaned_on
----+---------+-----------+------------
  2 |       3 |         1 | 2026-09-12
  4 |       1 |         3 | 2026-09-15
  5 |       6 |         2 | 2026-09-18
(3 rows)
  • psql zeigt NULL als leere Zelle: Linus’ E-Mail und Margarets Stadt im ersten Ergebnis.
  • coalesce() setzt (no email) ein, wo die E-Mail NULL ist, und lässt die anderen E-Mails, wie sie sind.
  • Mit || macht ein einziger NULL-Teil das ganze Label zu NULL, deshalb ist Margarets Zelle with_pipes leer. concat() überspringt das NULL und liefert Margaret ().
  • returned_on IS NULL findet die drei offenen Ausleihen; returned_on = NULL fände keine.
Ä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

Eine Kontaktliste und ein Ausleihstatus ohne Lücken

Schreiben Sie zwei Abfragen. Erstens: Name, E-Mail und Stadt jedes Mitglieds, nach id sortiert, wobei eine fehlende E-Mail als (no email) und eine fehlende Stadt als unknown erscheint; die Spaltennamen bleiben name, email und city. Zweitens: id und loaned_on jeder Ausleihe, dazu eine Spalte returned, die das Rückgabedatum zeigt oder open, wenn es keines gibt, nach id sortiert.

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

    coalesce(email, '(no email)') AS email behält den Spaltennamen email.

  2. Hinweis 2

    returned_on ist ein date und 'open' ist Text: coalesce braucht einen Typ für beide.

  3. Hinweis 3

    Wandeln Sie das Datum zuerst in Text um: coalesce(returned_on::text, 'open').

Eine Lösung zeigen

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

SELECT name,
       coalesce(email, '(no email)') AS email,
       coalesce(city, 'unknown') AS city
FROM members
ORDER BY id;

SELECT id, loaned_on, coalesce(returned_on::text, 'open') AS returned
FROM loans
ORDER BY 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.
SELECT * FROM members ORDER BY id;

test.sql

-- test: Die Kontaktliste füllt die Lücken, danach zeigt jede Ausleihe ihr Rückgabedatum oder open
-- output:
--    name   |        email         |  city
-- ----------+----------------------+---------
--  Ada      | ada@example.com      | Berlin
--  Grace    | grace@example.com    | Munich
--  Linus    | (no email)           | Berlin
--  Margaret | margaret@example.com | unknown
-- (4 rows)
--
--  id | loaned_on  |  returned
-- ----+------------+------------
--   1 | 2026-09-01 | 2026-09-10
--   2 | 2026-09-12 | open
--   3 | 2026-09-05 | 2026-09-20
--   4 | 2026-09-15 | open
--   5 | 2026-09-18 | open
-- (5 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);

CREATE TABLE members (
  id integer,
  name text,
  email text,
  city text,
  joined date
);

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

CREATE TABLE loans (
  id integer,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

INSERT INTO loans VALUES
  (1, 1, 1, '2026-09-01', '2026-09-10'),
  (2, 3, 1, '2026-09-12', NULL),
  (3, 2, 2, '2026-09-05', '2026-09-20'),
  (4, 1, 3, '2026-09-15', NULL),
  (5, 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

Alle außerhalb Berlins

Die Vorlage soll jedes Mitglied auflisten, das nicht in Berlin wohnt, mit Name und Stadt, nach id sortiert. Sie findet Grace, übersieht aber Margaret, deren Stadt unbekannt und damit sicher nicht als Berlin eingetragen ist. Reparieren Sie die Bedingung, sodass beide erscheinen. Probieren Sie beide Reparaturen: IS DISTINCT FROM und ein zusätzliches OR … IS NULL.

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

    Für Margaret ist city <> 'Berlin' NULL, nicht wahr, deshalb verwirft WHERE sie.

  2. Hinweis 2

    IS DISTINCT FROM behandelt NULL als eigenen Wert: NULL IS DISTINCT FROM 'Berlin' ist true.

  3. Hinweis 3

    Oder behalten Sie <> und ergänzen den fehlenden Fall: city <> 'Berlin' OR city IS NULL.

Eine Lösung zeigen

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

-- Members who do not live in Berlin.
SELECT name, city
FROM members
WHERE city IS DISTINCT FROM 'Berlin'
ORDER BY 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

-- Members who do not live in Berlin.
SELECT name, city
FROM members
WHERE city <> 'Berlin'
ORDER BY id;

test.sql

-- test: Grace und Margaret erscheinen, Margaret mit leerer Stadt
-- output:
--    name   |  city
-- ----------+--------
--  Grace    | Munich
--  Margaret |
-- (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);

CREATE TABLE members (
  id integer,
  name text,
  email text,
  city text,
  joined date
);

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

CREATE TABLE loans (
  id integer,
  book_id integer,
  member_id integer,
  loaned_on date,
  returned_on date
);

INSERT INTO loans VALUES
  (1, 1, 1, '2026-09-01', '2026-09-10'),
  (2, 3, 1, '2026-09-12', NULL),
  (3, 2, 2, '2026-09-05', '2026-09-20'),
  (4, 1, 3, '2026-09-15', NULL),
  (5, 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

Ein Text als Ersatz für eine Zahlenspalte

SELECT title, coalesce(year, 'unknown') AS year
FROM books;

Was psql ausgibt

ERROR:  invalid input syntax for type integer: "unknown"

Warum, und die Lösung

coalesce() liefert einen Typ für alle seine Argumente. year ist ein integer, also versucht PostgreSQL, 'unknown' als integer zu lesen, und scheitert. Machen Sie beide Seiten zu Text: coalesce(year::text, 'unknown'). Oder wählen Sie einen Ersatzwert vom Typ der Spalte, etwa coalesce(copies, 0).

IS als allgemeines „gleich“

SELECT name FROM members WHERE city IS 'Berlin';

Was psql ausgibt

ERROR:  syntax error at or near "'Berlin'"

Warum, und die Lösung

IS funktioniert nur mit einer festen Reihe von Wörtern: IS NULL, IS NOT NULL, IS TRUE, IS DISTINCT FROM und einigen mehr. Es ist keine andere Schreibweise für =. Für einen Wert schreiben Sie city = 'Berlin'; soll NULL als eigener Wert gelten, city IS NOT DISTINCT FROM 'Berlin'.

Durch eine Spalte teilen, die 0 sein kann

SELECT member, pages * 60 / minutes AS pages_per_hour
FROM readings;

Was psql ausgibt

ERROR:  division by zero

Warum, und die Lösung

Eine einzige Zeile mit minutes = 0 bricht die ganze Abfrage ab, und es erscheint gar keine Zeile. Teilen Sie stattdessen durch NULLIF(minutes, 0): Für diese Zeile ist der Teiler NULL, und das Ergebnis ist NULL statt eines Fehlers. Braucht der Bericht einen Ersatzwert, setzen Sie ihn danach mit coalesce().

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

NULL heißt unbekannt, und Unbekanntes breitet sich aus

Linus hat keine E-Mail: Die Spalte enthält NULL, keinen leeren String und nicht das Wort 'NULL'. NULL heißt: Der Wert ist unbekannt, und fast alles, was man damit tut, ist wieder unbekannt. email = NULL ist nicht wahr, sondern NULL, ebenso NULL = NULL: Von zwei Unbekannten weiß man nicht, ob sie gleich sind. 1 + NULL ist NULL, und 'Linus <' || NULL ist NULL. Die Logik von SQL hat drei Werte: wahr, falsch und unbekannt. NOT NULL bleibt NULL; NULL AND false ist false, NULL OR true ist true, weil der unbekannte Teil diese Ergebnisse nicht ändern kann. WHERE behält eine Zeile nur, wenn ihre Bedingung wahr ist; eine Bedingung, die NULL ist, verwirft die Zeile wie false.

Nach NULL fragen Sie mit IS NULL, nie mit =

Fehlende Werte finden Sie mit email IS NULL, vorhandene mit email IS NOT NULL. Beide sind immer wahr oder falsch, nie NULL. Eine Bedingung wie city <> 'Berlin' lässt Margaret still weg, deren Stadt unbekannt ist: Für sie ist der Vergleich NULL. Soll NULL als verschieden gelten, schreiben Sie city IS DISTINCT FROM 'Berlin' oder ergänzen OR city IS NULL. Die schärfste Falle steckt in NOT IN: city NOT IN ('Munich', NULL) bedeutet city <> 'Munich' AND city <> NULL, und der zweite Teil ist nie wahr, also kommt gar keine Zeile zurück. Halten Sie NULL aus NOT-IN-Listen heraus und formulieren Sie Bedingungen, wo es geht, positiv.

COALESCE füllt Lücken, NULLIF erzeugt sie

coalesce(email, '(no email)') liefert das erste seiner Argumente, das nicht NULL ist: die E-Mail, wenn es eine gibt, sonst den Text. Alle Argumente brauchen einen gemeinsamen Typ, deshalb scheitert coalesce(year, 'unknown') bei einem integer-Jahr; schreiben Sie coalesce(year::text, 'unknown'). NULLIF arbeitet umgekehrt: nullif(minutes, 0) ist NULL, wenn minutes 0 ist, sonst minutes. Teilen Sie dadurch, wird aus einer Division durch null, die die Abfrage abbricht, für diese Zeile NULL. Bei Text liefert || NULL, sobald ein Teil NULL ist, während concat(name, ' (', city, ')') die NULL-Teile überspringt und den Rest trotzdem liefert.

Quellen

Zuletzt geprüft am 30. September 2026