Operator JSONB dan Kueri Containment
Gunakan operator containment dan jalur yang benar-benar dapat dipercepat oleh indeks GIN.
Operator JSONB dan Kueri Containment adalah pelajaran PostgreSQL Performance & Query Optimization gratis di CoddyKit. Ini adalah pelajaran 1 dari 4. Kamu bisa membaca pelajaran lengkapnya di bawah secara gratis — lalu praktikkan langsung di browser dengan editor kode bawaan dan tutor AI 24/7. Ini adalah bagian dari jalur belajar PostgreSQL Performance & Query Optimization, dan progresmu tersinkronisasi di web dan aplikasi CoddyKit. Kursus PostgreSQL Performance & Query Optimization mencakup 4 pelajaran total.
Bagian dari pelajaran ini belum diterjemahkan dan ditampilkan dalam bahasa Inggris.
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.
Pertanyaan yang Sering Diajukan
Apakah pelajaran “Operator JSONB dan Kueri Containment” gratis?
Ya — teks lengkap “Operator JSONB dan Kueri Containment” gratis dibaca di sini di web. Untuk praktiknya secara interaktif (editor kode bawaan dan tutor AI 24/7) dan buka sisa kursus PostgreSQL Performance & Query Optimization, upgrade ke CoddyKit PRO. Kursus PostgreSQL Performance & Query Optimization mencakup 4 pelajaran total.
Apa yang akan aku pelajari di “Operator JSONB dan Kueri Containment”?
Gunakan operator containment dan jalur yang benar-benar dapat dipercepat oleh indeks GIN. Kamu berlatih PostgreSQL Performance & Query Optimization dengan kode praktik yang langsung kamu jalankan di browser, dan tutor AI 24/7 menjawab pertanyaanmu saat kamu mengerjakan pelajaran ini.
Apakah aku perlu pengalaman untuk memulai PostgreSQL Performance & Query Optimization?
Tidak diperlukan pengalaman sebelumnya. PostgreSQL Performance & Query Optimization di CoddyKit dirancang untuk pemula hingga pelajar tingkat lanjut, jadi kamu bisa memulai di sini atau dari awal dan belajar sesuai kecepatan kamu sendiri. Ini adalah pelajaran 1 dari 4.
Berapa lama pelajaran “Operator JSONB dan Kueri Containment” memakan waktu?
Sebagian besar pelajaran CoddyKit memakan waktu sekitar 5–10 menit. Setiap pelajaran ringkas dan interaktif, jadi kamu membuat kemajuan stabil dan melanjutkan dari tempat kamu tinggalkan di web dan aplikasi.
Bisakah aku menulis dan menjalankan kode dalam pelajaran PostgreSQL Performance & Query Optimization ini?
Ya. Setiap pelajaran PostgreSQL Performance & Query Optimization menyertakan editor kode bawaan, jadi kamu menulis dan menjalankan kode nyata langsung di browser dan mendapatkan umpan balik AI instan — tidak diperlukan penyiapan lokal.
Semua pelajaran dalam kursus ini
- Operator JSONB dan Kueri Containment
- GIN vs Indeks Ekspresi pada JSONB
- Mengkueri JSONB dengan JSONPath
- Kapan Melakukan Normalisasi dari JSONB