PostgreSQL hat ein außergewöhnlich reiches Typsystem — viel reicher als MySQL oder SQLite. Neben den klassischen SQL-Typen gibt es Postgres-eigene Spezialitäten wie jsonb, uuid, range-Typen und Arrays. Dieser Artikel ist die Landkarte: er sortiert alle wichtigen Typen in Kategorien, zeigt typische Use-Cases und verlinkt auf die Detail-Artikel.

Numerische Typen

Für Zahlen gibt es zwei große Familien: Ganzzahlen und Gleitkomma-/Dezimal-Zahlen.

TypGrößeWertebereichWann nehmen?
smallint2 Bytes−32 768 … +32 767Sehr kleine Zähler, Statuscodes
integer (int, int4)4 Bytesca. ±2,1 Mrd.Standard für IDs, Counts, Mengen
bigint (int8)8 Bytesca. ±9,2 TrillionenWenn IDs in den Milliardenbereich gehen oder Aggregate-Sums groß werden
numeric(p, s) (decimal)variabelbeliebig groß, exaktGeld, Steuern, alles, was 100% genau sein muss
real (float4)4 Bytes6 Stellen PräzisionWissenschaftliche Werte, wo kleine Rundungsfehler okay sind
double precision (float8)8 Bytes15 Stellen PräzisionWenn real zu ungenau ist

Faustregel: Für Geldbeträge immer numeric (z. B. numeric(10,2)). Niemals float — die Rundungsfehler sind klein, summieren sich aber bei vielen Operationen zu sichtbaren Differenzen.

Mehr dazu: Numerische Typen — int, bigint, numeric.

Auto-inkrementierende IDs

Zwei Wege, Postgres das automatische Hochzählen einer Primary-Key-Spalte zu überlassen:

SQL Modern (empfohlen seit PG 10)
CREATE TABLE users (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    email text NOT NULL UNIQUE
);
SQL Legacy (immer noch verbreitet)
CREATE TABLE users (
    id    bigserial PRIMARY KEY,
    email text NOT NULL UNIQUE
);

Beide funktionieren. IDENTITY ist SQL-Standard und der saubere moderne Weg. Mehr Detail: SERIAL vs. IDENTITY.

Text-Typen

TypBegrenzungSpeicherEmpfehlung
textunbegrenztTOAST bei großen WertenDefault-Wahl für die meisten Strings
varchar(n)n Zeichenwie text + Length-CheckNur wenn die Länge wirklich begrenzt sein muss
varchar (ohne n)unbegrenztwie textidentisch zu text
char(n)exakt n Zeichenmit Spaces aufgefülltSehr selten sinnvoll (Legacy)

Faustregel: In Postgres benutzt man text für fast alles. Anders als in MySQL/Oracle gibt es keinen Performance-Unterschied zwischen text und varchar — Postgres speichert sie intern gleich.

Mehr: text, varchar, char.

Wahrheitswerte

boolean (kurz bool) speichert TRUE, FALSE oder NULL. Ein Byte. Postgres akzeptiert bei Inserts viele Schreibweisen:

SQL
INSERT INTO settings (active) VALUES (TRUE);
INSERT INTO settings (active) VALUES (FALSE);
INSERT INTO settings (active) VALUES ('t');     -- 'true'
INSERT INTO settings (active) VALUES ('f');     -- 'false'
INSERT INTO settings (active) VALUES ('yes');   -- 'true'
INSERT INTO settings (active) VALUES (1);       -- ERROR: Postgres ist hier streng!

boolean ist nicht einfach „eine 0 oder 1” — Postgres lässt sich numerisch nicht überreden. Mehr: boolean.

Datum und Zeit

TypWas speichert erBeispiel
dateNur Datum'2026-05-07'
timeNur Uhrzeit, ohne Tag'14:30:00'
timestamp (= timestamp without time zone)Datum + Uhrzeit, ohne Zeitzone'2026-05-07 14:30:00'
timestamptz (= timestamp with time zone)Datum + Uhrzeit, mit Zeitzone-Bezug'2026-05-07 14:30:00+00'
intervalZeitspanne'2 hours 30 minutes', '7 days'

Wichtigste Empfehlung: Für jede Spalte, die einen Zeitpunkt darstellt (created_at, updated_at, paid_at, …), immer timestamptz nehmen, nicht timestamp. Der Unterschied ist in der Praxis riesig — Detail-Artikel: Datum, Zeit und Zeitstempel.

UUID

Universally Unique Identifier — 128-Bit-Werte, die ohne zentrale Vergabe weltweit eindeutig sind:

SQL
SELECT gen_random_uuid();
--      gen_random_uuid
-- --------------------------------------
--  3f9a8b1c-4e2d-4a1f-9e7c-1a2b3c4d5e6f

gen_random_uuid() ist eingebaut seit PG 13 — keine Extension nötig. Beliebt in verteilten Systemen oder als „undurchschaubare” Public-IDs. Mehr: UUID.

JSON / JSONB

Postgres kann JSON-Dokumente direkt als Spaltenwert speichern und durchsuchen:

SQL
CREATE TABLE products (
    id    bigserial PRIMARY KEY,
    sku   text NOT NULL,
    attrs jsonb
);

INSERT INTO products (sku, attrs) VALUES
    ('A1', '{"color": "red", "weight_kg": 1.2, "tags": ["new", "sale"]}');

SELECT sku FROM products WHERE attrs->>'color' = 'red';

jsonb ist die binäre, optimierte Variante — fast immer die richtige Wahl gegenüber dem älteren json. Eigenes Kapitel: JSON und JSONB.

Arrays

Jede Spalte kann ein Array sein:

SQL
CREATE TABLE articles (
    id   bigserial PRIMARY KEY,
    tags text[]
);

INSERT INTO articles (tags) VALUES
    (ARRAY['postgres', 'tutorial', 'sql']);

SELECT * FROM articles WHERE 'postgres' = ANY(tags);

Praktisch für Listen-Felder, Tags und kleine Mengen. Bei sehr vielen Einträgen oder relationalen Beziehungen lohnen sich aber separate Tabellen. Mehr: Arrays.

Ranges

Wertebereiche als eigener Typ — perfekt für Buchungs-Slots oder Datums-Spannen:

SQL
CREATE TABLE bookings (
    id      bigserial PRIMARY KEY,
    room_id integer,
    period  tstzrange   -- Range von Timestamps mit TZ
);

INSERT INTO bookings (room_id, period) VALUES
    (1, '[2026-05-07 14:00, 2026-05-07 16:00)');

Postgres bringt Built-in-Operatoren für Überschneidungen, Eindämmung etc. — und kann mit Exclusion-Constraints automatisch Doppel-Buchungen verhindern. Mehr: Ranges.

Enum

Aufzählungstypen für feste Wertelisten:

SQL
CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');

CREATE TABLE orders (
    id     bigserial PRIMARY KEY,
    status order_status NOT NULL DEFAULT 'pending'
);

Klingt elegant, hat aber Tücken bei Änderungen — siehe Detail-Artikel: Enum. Häufig ist eine Lookup-Tabelle mit text-Spalte + Foreign Key flexibler.

Binär-Daten

bytea für kleine bis mittlere Binär-Daten direkt in der Tabelle:

SQL
CREATE TABLE attachments (
    id   bigserial PRIMARY KEY,
    data bytea
);

Für richtig große Dateien (>10 MB) gibt’s Large Objects — und in Produktion meist die Empfehlung, das Binär-Zeug ganz aus der DB zu lassen (S3 / Object Storage) und nur die URL zu speichern. Mehr: bytea vs. Large Objects.

Geometrie

Postgres bringt Built-in-Geometrie-Typen mit (point, line, polygon, circle) — für ernsthafte Geo-Anwendungen ist aber PostGIS die de-facto-Standardlösung:

SQL
-- Built-in:
SELECT point(13.40, 52.52);

-- PostGIS:
CREATE EXTENSION postgis;
SELECT ST_Distance(
    ST_MakePoint(13.40, 52.52)::geography,  -- Berlin
    ST_MakePoint(2.35, 48.86)::geography    -- Paris
);  -- ca. 878 km

Mehr: Geometrie und PostGIS — Kurzeinführung.

Interessantes

Postgres lässt eigene Typen zu.

Mit CREATE TYPE und CREATE DOMAIN definierst du eigene zusammengesetzte Typen oder eingeschränkte Standardtypen (etwa „positive Zahl” oder „E-Mail-validiert”). In den meisten Anwendungen kommt man ohne aus, aber das Feature ist da. Mehr im Kapitel Programmability.

Type-Casting ist explizit, mit zwei Schreibweisen.

'42'::integer (Postgres-Stil) und CAST('42' AS integer) (SQL-Standard) sind äquivalent. In SELECT-Listen und WHERE-Klauseln meist ::, in DDL eher CAST für Lesbarkeit.

Storage-Größe interessiert selten — bis sie's tut.

Bei einer Tabelle mit 10 Millionen Zeilen macht der Unterschied zwischen int (4 Bytes) und bigint (8 Bytes) 40 MB aus. Auf modernen Disks vernachlässigbar, in RAM-knappen Setups oder dichten Indexen aber relevant. Faustregel: erst Funktionalität wählen, dann optimieren.

Domains für validierte Strings

Mit CREATE DOMAIN email_address AS text CHECK (VALUE ~ '^.+@.+$') baust du eigene Pseudo-Typen mit Validierung. Tabellen, die email_address verwenden, lehnen ungültige Werte automatisch ab. Praktisch für wiederkehrende Constraints.

Reichhaltigkeit ist eine Postgres-Stärke — und Falle.

MySQL hat kein jsonb, kein uuid, keine Ranges, keine Arrays — Migrationen nach Postgres öffnen oft Türen zu eleganteren Daten-Modellen. Aber Vorsicht: zurück geht’s nicht so einfach. Wer Datenbank-Wechsel plant, sollte sich auf den gemeinsamen Nenner beschränken.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Datentypen

Zur Übersicht