PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Apa indeks yang tepat untuk menanyakan struktur dalam array di Postgres jsonb?

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);


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Menyiapkan Django dan PostgreSQL pada dua instans EC2 yang berbeda

  2. PostgreSQL:Pencarian Teks Lengkap - Bagaimana cara mencari sebagian kata?

  3. Bagaimana cara mengelompokkan cap waktu ke dalam pulau (berdasarkan celah sewenang-wenang)?

  4. Langkah demi Langkah postgres_fdw

  5. Postgres - bagaimana cara mengembalikan baris dengan 0 hitungan untuk data yang hilang?