JSONB-Operatoren und Containment-Abfragen
Verwenden Sie die Containment- und Pfadoperatoren, die GIN-Indizes tatsächlich beschleunigen können.
JSONB-Operatoren und Containment-Abfragen ist eine kostenlose PostgreSQL Performance & Query Optimization-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des PostgreSQL Performance & Query Optimization-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der PostgreSQL Performance & Query Optimization-Kurs umfasst insgesamt 4 Lektionen.
Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.
Why Operator Choice Decides Index Use
In PostgreSQL, a column of type jsonb can be searched many different ways, but not every operator can use an index. Performance here is almost entirely about choosing operators that a GIN index can accelerate.
- A GIN index (Generalized Inverted Index) stores the keys and values inside your JSON documents so lookups skip the full table.
- The two operators that matter most are containment (
@>) and key existence (?,?|,?&).
This lesson teaches exactly which operators those are, and how to write queries that stay index-friendly.
The Containment Operator @>
The containment operator @> asks: does the left JSONB contain the right JSONB? The right side is a fragment, and Postgres checks that every key/value in it appears in the left document.
'{"a":1,"b":2}' @> '{"a":1}'is true.'{"a":1}' @> '{"a":1,"b":2}'is false (the right side has more).
This is the workhorse for filtering rows: WHERE data @> '{"status":"active"}' finds every row whose JSON includes that pair.
SELECT '{"a":1,"b":2}'::jsonb @> '{"a":1}'::jsonb AS contains_a,
'{"a":1}'::jsonb @> '{"a":1,"b":2}'::jsonb AS contains_both;Building a GIN Index for Containment
A plain GIN index on a jsonb column supports both containment and key-existence operators. This is the index you reach for first.
- The default
jsonb_opsoperator class indexes every key and value. - It accelerates
@>,?,?|, and?&.
Create it once, and containment filters that previously scanned the whole table become bitmap index scans.
CREATE INDEX idx_events_data
ON events
USING GIN (data);Containment Filters in WHERE
Once the GIN index exists, write the filter as a containment check so the planner can use it. Matching a nested fragment works too, because containment is recursive.
- Top-level match:
data @> '{"status":"active"}'. - Nested match:
data @> '{"user":{"plan":"pro"}}'.
Notice we pass a JSON object literal on the right, not a column reference or function call. That literal shape is what makes the query index-eligible.
SELECT id, created_at
FROM events
WHERE data @> '{"user":{"plan":"pro"}}'
ORDER BY created_at DESC
LIMIT 50;Key Existence Operators ? ?| ?&
Sometimes you only care whether a key is present, regardless of its value. The existence operators handle this and are also GIN-accelerated.
data ? 'email'— true if the top-level keyemailexists.data ?| array['phone','email']— true if any of these keys exist.data ?& array['phone','email']— true if all of these keys exist.
Important: ? checks top-level keys only, and for arrays it checks whether the string is an element.
SELECT '{"email":"x@y.z","phone":"123"}'::jsonb ? 'email' AS has_email,
'{"email":"x@y.z"}'::jsonb ?| array['phone','email'] AS has_any,
'{"email":"x@y.z"}'::jsonb ?& array['phone','email'] AS has_all;The Trap: Path Extraction Operators -> and ->>
The extraction operators look convenient but are not accelerated by a standard GIN index:
data -> 'status'returns the value asjsonb.data ->> 'status'returns the value astext.
A query like WHERE data ->> 'status' = 'active' forces a sequential scan on a plain GIN index, because the index does not index extracted scalar comparisons. Prefer the containment form data @> '{"status":"active"}' instead.
-- Slow on a plain GIN index (seq scan):
SELECT * FROM events WHERE data ->> 'status' = 'active';
-- Fast equivalent (uses GIN):
SELECT * FROM events WHERE data @> '{"status":"active"}';Rescuing ->> with an Expression Index
If you genuinely need range or pattern comparisons on one field, a B-tree expression index on the extracted text is the right tool — not GIN.
- Index the exact expression you query.
- Then comparisons like
=,<,>, andBETWEENcan use it.
The query's expression must match the indexed expression character for character, or the planner ignores the index.
CREATE INDEX idx_events_status
ON events ((data ->> 'status'));
-- Now this can use the B-tree index:
SELECT * FROM events WHERE (data ->> 'status') = 'active';jsonb_path_ops: Smaller, Faster, Containment-Only
The alternative operator class jsonb_path_ops indexes hashed root-to-leaf paths instead of every key.
- It produces a smaller index and is typically faster for
@>queries. - Trade-off: it supports only containment (
@>), not the existence operators?,?|,?&.
Choose jsonb_path_ops when your workload is dominated by containment filtering and you never need key-existence searches.
CREATE INDEX idx_events_data_path
ON events
USING GIN (data jsonb_path_ops);Containment Against Arrays
Containment also matches inside JSON arrays, which makes it ideal for tag-style data. To ask "does this array contain a value," wrap the value in an array on the right side.
'["a","b","c"]' @> '["b"]'is true.- For a tagged document:
data @> '{"tags":["urgent"]}'finds rows whosetagsarray includesurgent.
This stays fully index-eligible on a GIN index, so tag filtering scales well.
SELECT '["a","b","c"]'::jsonb @> '["b"]'::jsonb AS has_b,
'{"tags":["urgent","billing"]}'::jsonb
@> '{"tags":["urgent"]}'::jsonb AS is_urgent;Verify With EXPLAIN
Never assume the index is used — confirm it. Run EXPLAIN and look for a Bitmap Index Scan on your GIN index. A Seq Scan means your operator or expression defeated the index.
- Good sign:
Bitmap Index Scan on idx_events_data. - Bad sign:
Seq Scan on eventswith a JSON filter.
Use EXPLAIN (ANALYZE, BUFFERS) to also see real timing and how many pages were read.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id FROM events
WHERE data @> '{"status":"active"}';Putting It Together: A Decision Rule
Use this quick rule when writing a JSONB filter:
- Matching key/value or nested fragment? Use
@>with a GIN index. - Only checking a key is present? Use
?/?|/?&with defaultjsonb_opsGIN. - Containment-only workload, want the smallest index? Use
jsonb_path_opsGIN. - Range or pattern on one scalar field? Use a B-tree expression index on
->>.
Avoid ->> equality filters without a matching expression index — they trigger sequential scans.
Quick Check
You have a default jsonb_ops GIN index on events.data. Which WHERE clause can use that index?
Recap
You learned which JSONB operators actually benefit from indexing:
- @> (containment) is the primary GIN-accelerated filter, including nested objects and arrays.
- ?, ?|, ?& (key existence) are GIN-accelerated, but only with the default
jsonb_opsclass, and check top-level keys. - jsonb_path_ops gives a smaller, faster containment-only index.
- -> and ->> extraction filters do NOT use a plain GIN index; rewrite as
@>or add a B-tree expression index. - Always confirm with
EXPLAINthat you get a Bitmap Index Scan, not a Seq Scan.
Lerne SQL mit einem KI-Tutor — kostenlos
Schreibe und führe echten Code in deinem Browser aus, bekomme sofortige Hilfe von einem 24/7 KI-Tutor und setze dein Lernen im Web oder in der App fort.
- Kurse
- 22
- Lektionen
- 88
Häufig gestellte Fragen
Ist die Lektion „JSONB-Operatoren und Containment-Abfragen“ kostenlos?
Ja — der vollständige Text von „JSONB-Operatoren und Containment-Abfragen“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des PostgreSQL Performance & Query Optimization-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der PostgreSQL Performance & Query Optimization-Kurs umfasst insgesamt 4 Lektionen.
Was lerne ich in „JSONB-Operatoren und Containment-Abfragen“?
Verwenden Sie die Containment- und Pfadoperatoren, die GIN-Indizes tatsächlich beschleunigen können. Du übst PostgreSQL Performance & Query Optimization mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.
Brauche ich Erfahrung, um PostgreSQL Performance & Query Optimization zu starten?
Keine Vorkenntnisse erforderlich. PostgreSQL Performance & Query Optimization auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.
Wie lange dauert die Lektion „JSONB-Operatoren und Containment-Abfragen“?
Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.
Kann ich in dieser PostgreSQL Performance & Query Optimization-Lektion Code schreiben und ausführen?
Ja. Jede PostgreSQL Performance & Query Optimization-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.
Alle Lektionen in diesem Kurs
- JSONB-Operatoren und Containment-Abfragen
- GIN- versus Ausdrucksindizes für JSONB
- JSONB mit JSONPath abfragen
- Wann JSONB normalisiert werden sollte