Pertama-tama, Anda tidak dapat mengakses nilai array JSON seperti itu. Untuk nilai json yang diberikan
[{"event_slug":"test_1","start_time":"2014-10-08","end_time":"2014-10-12"},
{"event_slug":"test_2","start_time":"2013-06-24","end_time":"2013-07-02"},
{"event_slug":"test_3","start_time":"2014-03-26","end_time":"2014-03-30"}]
Pengujian yang valid terhadap elemen larik pertama adalah:
WHERE e->0->>'event_slug' = 'test_1'
Tetapi Anda mungkin tidak ingin membatasi pencarian Anda pada elemen pertama dari array. Dengan jsonb
tipe data di Postgres 9.4 Anda memiliki operator tambahan dan dukungan indeks. Untuk mengindeks elemen larik, Anda memerlukan indeks GIN.
Kelas operator bawaan untuk indeks GIN tidak mendukung operator "lebih besar dari" atau "kurang dari" . Ini berlaku untuk > >= < <=
jsonb
juga, di mana Anda dapat memilih antara dua kelas operator. Per dokumentasi:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
menjadi default.) Anda dapat mencakup uji kesetaraan, tetapi tidak satu pun dari operator tersebut yang memenuhi persyaratan Anda untuk >=
perbandingan. Anda akan membutuhkan indeks btree.
Solusi dasar
Untuk mendukung pemeriksaan kesetaraan dengan indeks:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Ini mungkin cukup baik jika filternya cukup selektif.
Dengan asumsi end_time >= start_time
, jadi kita tidak perlu dua cek. Hanya memeriksa end_time
lebih murah dan setara:
SELECT l.*
FROM locations l
, jsonb_array_elements(l.events) e
WHERE l.events @> '[{"event_slug":"test_1"}]'
AND (e->>'end_time')::timestamp >= '2014-10-30 14:04:06 -0400'::timestamptz;
Memanfaatkan JOIN LATERAL
implicit implisit . Detail (bab terakhir):
- PostgreSQL unnest() dengan nomor elemen
Hati-hati dengan tipe data yang berbeda ! Apa yang Anda miliki dalam nilai JSON terlihat seperti timestamp [without time zone]
, sedangkan predikat Anda menggunakan timestamp with time zone
literal. timestamp
nilai ditafsirkan menurut zona waktu saat ini pengaturan, sedangkan timestamptz
yang diberikan literal harus ditransmisikan ke timestamptz
secara eksplisit atau zona waktu akan diabaikan! Permintaan di atas harus berfungsi seperti yang diinginkan. Penjelasan detail:
- Mengabaikan zona waktu sama sekali di Rails dan PostgreSQL
Penjelasan lebih lanjut untuk jsonb_array_elements()
:
- Penggabungan PostgreSQL menggunakan JSONB
Solusi lanjutan
Jika hal di atas tidak cukup baik, saya akan mempertimbangkan MATERIALIZED VIEW
yang menyimpan atribut yang relevan dalam bentuk yang dinormalisasi. Ini memungkinkan indeks btree biasa.
Kode mengasumsikan bahwa nilai JSON Anda memiliki format yang konsisten seperti yang ditampilkan dalam pertanyaan.
Penyiapan:
CREATE TYPE event_type AS (
, event_slug text
, start_time timestamp
, end_time timestamp
);
CREATE MATERIALIZED VIEW loc_event AS
SELECT l.location_id, e.event_slug, e.end_time -- start_time not needed
FROM locations l, jsonb_populate_recordset(null::event_type, l.events) e;
Jawaban terkait untuk jsonb_populate_recordset()
:
- Cara mengonversi tipe jsonb PostgreSQL 9.4 menjadi float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Juga termasuk location_id
untuk mengizinkan pemindaian hanya indeks . (Lihat halaman manual dan Postgres Wiki.)
Pertanyaan:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Atau, jika Anda memerlukan baris lengkap dari locations
yang mendasarinya tabel:
SELECT l.*
FROM (
SELECT DISTINCT location_id
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz
) le
JOIN locations l USING (location_id);