json) und 9.4 (jsonb)Vergleich
-- Tabelle mit JSONB-Spalte
CREATE TABLE events (
id SERIAL PRIMARY KEY,
ts TIMESTAMPTZ DEFAULT now(),
payload JSONB
);
-- Einfügen von JSON-Daten
INSERT INTO events (payload) VALUES
('{"type":"login","user":"alice","ip":"1.2.3.4"}'),
('{"type":"order","user":"bob",
"items":[1,2,3],"total":49.90}');
-- Gültige JSON-Typen
SELECT '42'::jsonb; -- Zahl
SELECT '"hello"'::jsonb; -- String
SELECT 'true'::jsonb; -- Boolean
SELECT '[1,2,3]'::jsonb; -- Array
SELECT '{"a":1}'::jsonb; -- Objekt
Gültige JSON-Typen
true oder falseHinweis
::jsonbOperatoren
-> gibt JSON-Element zurück (als json/jsonb)->> gibt Textwert zurück (als text)#> navigiert Pfad (Array von Strings)#>> navigiert Pfad, gibt Text zurück@> enthält (containment)<@ wird enthalten in? Key existiert?| mindestens einer der Keys existiert?& alle Keys existierenSELECT payload -> 'user' -- "alice" (jsonb)
SELECT payload ->> 'user' -- alice (text)
SELECT payload #> '{items,0}' -- 1 (jsonb)
SELECT payload #>> '{items,0}' -- 1 (text)
FROM events WHERE id = 2;
JSONPath-Konzepte
$ (Root-Element).key für Objektzugriff, [n] für Array-Zugriff[*] für alle Array-Elemente?(@ > 10).type(), .size(), .keyvalue()-- jsonb_path_query: alle passenden Elemente
SELECT jsonb_path_query(payload, '$.items[*]')
FROM events WHERE id = 2;
-- -> 1, 2, 3
-- Filter: items groesser als 1
SELECT jsonb_path_query(
payload, '$.items[*] ? (@ > 1)')
FROM events WHERE id = 2;
-- -> 2, 3
-- jsonb_path_exists: pruefen ob Pfad existiert
SELECT * FROM events
WHERE jsonb_path_exists(
payload, '$.type ? (@ == "login")');
-- Gleichheit ueber ->>
SELECT * FROM events
WHERE payload->>'type' = 'order';
-- Containment @>: enthaelt diesen JSON-Wert
SELECT * FROM events
WHERE payload @> '{"type":"login"}';
-- Key-Existenz ?
SELECT * FROM events
WHERE payload ? 'items';
-- Numerischer Vergleich (cast noetig)
SELECT * FROM events
WHERE (payload->>'total')::numeric > 30;
-- Array-Element enthaelt
SELECT * FROM events
WHERE payload @> '{"items": [2]}';
Tipps für Performance
@> und ? nutzen GIN-Index direkt->> mit Cast erzwingt Sequenziellen Scan (ohne Funktionsindex)(payload->>'feld')::numeric@> ist oft die schnellste Methode für Containment-ChecksBeispiel Funktionsindex
CREATE INDEX ON events
((payload->>'type'));
-- Dann: WHERE payload->>'type' = 'order'
-- nutzt den Index!
GIN-Index-Konzepte
jsonb_ops (Standard): @>, ?, ?|, ?&@> und ?-Abfragen-- Standard GIN-Index
CREATE INDEX idx_events_payload
ON events USING GIN (payload);
-- Path-ops GIN-Index
-- (kompakter, schneller fuer @>)
CREATE INDEX idx_events_payload_path
ON events USING GIN (payload jsonb_path_ops);
-- Partieller Index auf Subfeld
CREATE INDEX idx_events_type
ON events ((payload->>'type'));
Modifikationsfunktionen
jsonb_set(target, path, value): Wert setzenjsonb_insert(target, path, value): Element einfügenjsonb_strip_nulls(json): null-Werte entfernen||-Operator: zwei jsonb-Objekte zusammenführen (shallow merge)--Operator: Key entfernen-- Wert setzen (erstellt Key wenn nicht vorhanden)
UPDATE events
SET payload = jsonb_set(
payload, '{status}', '"processed"')
WHERE id = 2;
-- Key entfernen
UPDATE events
SET payload = payload - 'ip'
WHERE payload->>'type' = 'login';
-- Objekte zusammenfuehren (shallow merge)
SELECT '{"a":1,"b":2}'::jsonb
|| '{"b":99,"c":3}'::jsonb;
-- -> {"a":1,"b":99,"c":3}
-- Nulls entfernen
SELECT jsonb_strip_nulls(
'{"a":1,"b":null,"c":null}');
-- -> {"a":1}
-- Objekt aus Ausdruecken bauen
SELECT json_build_object(
'name', first_name || ' ' || last_name,
'age', extract(year from age(birthdate))
) FROM users;
-- Array aus Werten
SELECT json_build_array(1, 'two', true, null);
-- -> [1,"two",true,null]
-- Zeilen aggregieren zu JSON-Array
SELECT json_agg(row_to_json(u))
FROM users u WHERE active = true;
-- Key-Value-Aggregation zu Objekt
SELECT json_object_agg(name, value)
FROM config_table;
Aggregationsfunktionen
json_build_object(k,v,...): Objekt aus Schlüssel-Wert-Paarenjson_build_array(...): Array aus beliebigen Wertenjson_agg(): Zeilen zu JSON-Array aggregierenjson_object_agg(k,v): Key-Value-Tabelle zu Objektrow_to_json(): Datenbankzeile als JSON-Objektto_jsonb(): beliebigen Wert zu JSONB konvertierenAnwendungsfälle
-- Objekt-Keys und Werte als Zeilen
SELECT key, value
FROM events,
jsonb_each(payload)
WHERE id = 1;
-- key: type, value: "login"
-- key: user, value: "alice"
-- key: ip, value: "1.2.3.4"
-- Array-Elemente als Zeilen
SELECT item
FROM events,
jsonb_array_elements(
payload->'items') AS item
WHERE id = 2;
-- item: 1, 2, 3
-- Schluessel als Text
SELECT jsonb_object_keys(payload)
FROM events WHERE id = 1;
Konzepte
json_to_record / json_to_recordset ohne vordefinierten TypNULL gefüllt-- Zieltyp definieren
CREATE TYPE order_rec AS (
user_name text,
total numeric
);
-- JSON in Typ umwandeln
SELECT * FROM json_populate_record(
null::order_rec,
'{"user_name":"bob","total":49.90}'
);
-- Array von JSON-Objekten als Tabelle
SELECT * FROM json_to_recordset(
'[{"id":1,"name":"Alice"},
{"id":2,"name":"Bob"}]'
) AS t(id int, name text);
CREATE TABLE api_logs (
id BIGSERIAL PRIMARY KEY,
logged_at TIMESTAMPTZ DEFAULT now(),
data JSONB
);
-- Fehlerrate der letzten Stunde
SELECT
data->>'endpoint' AS endpoint,
count(*) FILTER
(WHERE (data->>'status')::int >= 500)
AS errors,
count(*) AS total
FROM api_logs
WHERE logged_at >= now() - interval '1h'
GROUP BY endpoint
ORDER BY errors DESC;
-- Index fuer schnelle Endpoint-Suche
CREATE INDEX ON api_logs
USING GIN (data jsonb_path_ops);
Vorteile
Typische Log-Felder
endpoint, method, statusduration_ms, user_idrequest_id, trace_idCREATE TABLE app_config (
app TEXT PRIMARY KEY,
config JSONB NOT NULL
);
INSERT INTO app_config VALUES (
'frontend',
'{"theme":"dark","locale":"de",
"features":{
"beta":true,
"newDashboard":false}}'
);
-- Feature-Flag abfragen
SELECT config #>> '{features,beta}'
FROM app_config WHERE app = 'frontend';
-- Config aktualisieren (ohne Ueberschreiben)
UPDATE app_config
SET config = config || '{"timeout": 30}'
WHERE app = 'frontend';
Vorteile
jsonb_set||: nur Top-Level-Keys überschreibenHinweis Merge-Tiefe
|| ist nur shallow – verschachtelte Objekte werden überschriebenjsonb_set mit explizitem Pfad nutzenMotivation
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price NUMERIC(10,2),
category TEXT,
attrs JSONB -- flexible Attribute
);
INSERT INTO products VALUES
(1,'Laptop',999.00,'electronics',
'{"brand":"Dell","ram_gb":16,
"screen_inch":15.6}'),
(2,'T-Shirt',19.90,'clothing',
'{"size":"M","color":"blue",
"material":"cotton"}');
-- Suche nach RAM >= 8 GB
SELECT name, price
FROM products
WHERE (attrs->>'ram_gb')::int >= 8;
-- Alle Bestellwerte ueber 100
SELECT jsonb_path_query_array(
'{"orders":[
{"id":1,"val":120},
{"id":2,"val":80}]}',
'$.orders[*] ? (@.val > 100)'
);
-- -> [{"id":1,"val":120}]
-- Erste passende Zeile
SELECT jsonb_path_query_first(
payload,
'$.items[*] ? (@ > 2)'
) FROM events WHERE id = 2;
-- -> 3
-- Existenz-Check mit Bedingung
SELECT * FROM events
WHERE jsonb_path_exists(
payload, '$.total ? (@ > 40)'
);
-- CHECK-Constraint fuer JSON-Struktur
ALTER TABLE events ADD CONSTRAINT chk_type
CHECK (payload ? 'type');
ALTER TABLE events
ADD CONSTRAINT chk_type_valid
CHECK (payload->>'type'
IN ('login','logout','order'));
-- NOT NULL fuer Pflichtfelder innerhalb JSON
ALTER TABLE events ADD CONSTRAINT chk_user
CHECK (payload ? 'user' AND
payload->>'user' IS NOT NULL);
-- Erweiterung pg_jsonschema (JSON Schema)
CREATE EXTENSION pg_jsonschema;
ALTER TABLE events
ADD CONSTRAINT validate_payload
CHECK (jsonb_matches_schema(
'{"type":"object",
"required":["type","user"]}',
payload
));
Validierungsstrategien
?) ist performantHinweis
| Relational | JSONB |
|---|---|
|
|
| Relational | JSONB |
|---|---|
|
|
| Relational | JSONB |
|---|---|
|
|
| Relational | JSONB |
|---|---|
|
|
| Relational | JSONB |
|---|---|
|
|
| JSON Types | link |
| JSON Functions and Operators | link |
| JSONPATH Type | link |
| SQL/JSON Path Language | link |