Prima di tutto, non puoi accedere ai valori dell'array JSON in questo modo. Per un dato valore json
[{"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"}]
Un test valido rispetto al primo elemento dell'array sarebbe:
WHERE e->0->>'event_slug' = 'test_1'
Ma probabilmente non vuoi limitare la tua ricerca al primo elemento dell'array. Con il jsonb
tipo di dati in Postgres 9.4 hai operatori aggiuntivi e supporto per gli indici. Per indicizzare gli elementi di un array è necessario un indice GIN.
Le classi di operatori integrate per gli indici GIN non supportano gli operatori "maggiore di" o "minore di" . Questo è vero per > >= < <=
jsonb
inoltre, dove puoi scegliere tra due classi di operatori. Per documentazione:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
essendo l'impostazione predefinita.) Puoi coprire il test di uguaglianza, ma nessuno di questi operatori soddisfa i tuoi requisiti per >=
confronto. Avresti bisogno di un indice btree.
Soluzione di base
Per supportare il controllo di uguaglianza con un indice:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Questo potrebbe essere abbastanza buono se il filtro è sufficientemente selettivo.
Supponendo end_time >= start_time
, quindi non abbiamo bisogno di due controlli. Controllo solo end_time
è più economico ed equivalente:
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;
Utilizzando un implicito JOIN LATERAL
. Dettagli (ultimo capitolo):
- PostgreSQL unnest() con numero elemento
Fai attenzione ai diversi tipi di dati ! Quello che hai nel valore JSON è simile a timestamp [without time zone]
, mentre i tuoi predicati usano timestamp with time zone
letterali. Il timestamp
il valore viene interpretato in base al fuso orario corrente impostazione, mentre il dato timestamptz
i letterali devono essere trasmessi a timestamptz
in modo esplicito o il fuso orario verrebbe ignorato! La query sopra dovrebbe funzionare come desiderato. Spiegazione dettagliata:
- Ignora del tutto i fusi orari in Rails e PostgreSQL
Ulteriori spiegazioni per jsonb_array_elements()
:
- Partecipazione a PostgreSQL tramite JSONB
Soluzione avanzata
Se quanto sopra non è abbastanza buono, prenderei in considerazione una MATERIALIZED VIEW
che memorizza gli attributi rilevanti in forma normalizzata. Ciò consente semplici indici btree.
Il codice presuppone che i tuoi valori JSON abbiano un formato coerente come mostrato nella domanda.
Configurazione:
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;
Risposta correlata per jsonb_populate_recordset()
:
- Come convertire il tipo jsonb di PostgreSQL 9.4 in float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Includendo anche location_id
per consentire scansioni solo indice . (Vedi la pagina del manuale e il Wiki di Postgres.)
Domanda:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Oppure, se hai bisogno di righe complete dalle locations
sottostanti tabella:
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);