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.
| Typ | Größe | Wertebereich | Wann nehmen? |
|---|---|---|---|
smallint | 2 Bytes | −32 768 … +32 767 | Sehr kleine Zähler, Statuscodes |
integer (int, int4) | 4 Bytes | ca. ±2,1 Mrd. | Standard für IDs, Counts, Mengen |
bigint (int8) | 8 Bytes | ca. ±9,2 Trillionen | Wenn IDs in den Milliardenbereich gehen oder Aggregate-Sums groß werden |
numeric(p, s) (decimal) | variabel | beliebig groß, exakt | Geld, Steuern, alles, was 100% genau sein muss |
real (float4) | 4 Bytes | 6 Stellen Präzision | Wissenschaftliche Werte, wo kleine Rundungsfehler okay sind |
double precision (float8) | 8 Bytes | 15 Stellen Präzision | Wenn 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:
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL UNIQUE
);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
| Typ | Begrenzung | Speicher | Empfehlung |
|---|---|---|---|
text | unbegrenzt | TOAST bei großen Werten | Default-Wahl für die meisten Strings |
varchar(n) | n Zeichen | wie text + Length-Check | Nur wenn die Länge wirklich begrenzt sein muss |
varchar (ohne n) | unbegrenzt | wie text | identisch zu text |
char(n) | exakt n Zeichen | mit Spaces aufgefüllt | Sehr 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:
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
| Typ | Was speichert er | Beispiel |
|---|---|---|
date | Nur Datum | '2026-05-07' |
time | Nur 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' |
interval | Zeitspanne | '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:
SELECT gen_random_uuid();
-- gen_random_uuid
-- --------------------------------------
-- 3f9a8b1c-4e2d-4a1f-9e7c-1a2b3c4d5e6fgen_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:
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:
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:
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:
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:
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:
-- 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 kmMehr: 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
- Data Types – PostgreSQL Documentation
- CREATE TYPE – PostgreSQL Documentation
- CREATE DOMAIN – PostgreSQL Documentation
- Type Conversion Rules