Først og fremmest kan du ikke få adgang til JSON-arrayværdier som den. For en given json-værdi
[{"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"}]
En gyldig test mod det første array-element ville være:
WHERE e->0->>'event_slug' = 'test_1'
Men du vil sandsynligvis ikke begrænse din søgning til det første element i arrayet. Med jsonb
datatype i Postgres 9.4 har du yderligere operatører og indeksunderstøttelse. For at indeksere elementer i et array skal du bruge et GIN-indeks.
De indbyggede operatorklasser for GIN-indekser understøtter ikke "større end" eller "mindre end" operatorer . Dette gælder for > >= < <=
jsonb
også, hvor du kan vælge mellem to operatørklasser. Per dokumentation:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
er standard.) Du kan dække lighedstesten, men ingen af disse operatører dækker dit krav om >=
sammenligning. Du skal bruge et btree-indeks.
Grundlæggende løsning
For at understøtte ligestillingskontrollen med et indeks:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Dette kan være godt nok, hvis filteret er selektivt nok.
Under forudsætning af end_time >= start_time
, så vi behøver ikke to checks. Kontrollerer kun end_time
er billigere og tilsvarende:
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;
Ved at bruge en implicit JOIN LATERAL
. Detaljer (sidste kapitel):
- PostgreSQL unnest() med elementnummer
Vær forsigtig med de forskellige datatyper ! Det, du har i JSON-værdien, ser ud som timestamp [without time zone]
, mens dine prædikater bruger timestamp with time zone
bogstavelige. timestamp
værdien fortolkes i henhold til den aktuelle tidszone indstilling, mens den givne timestamptz
bogstaver skal castes til timestamptz
udtrykkeligt, ellers ville tidszonen blive ignoreret! Ovenstående forespørgsel skal fungere som ønsket. Detaljeret forklaring:
- Ignorerer tidszoner helt i Rails og PostgreSQL
Mere forklaring på jsonb_array_elements()
:
- PostgreSQL-tilmelding ved hjælp af JSONB
Avanceret løsning
Hvis ovenstående ikke er godt nok, vil jeg overveje en MATERIALIZED VIEW
der gemmer relevante attributter i normaliseret form. Dette tillader almindelige btree-indekser.
Koden antager, at dine JSON-værdier har et ensartet format som vist i spørgsmålet.
Opsætning:
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;
Relateret svar for jsonb_populate_recordset()
:
- Sådan konverteres PostgreSQL 9.4's jsonb-type til float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Inkluderer også location_id
for at tillade kun-indeksscanninger . (Se manualsiden og Postgres Wiki.)
Forespørgsel:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Eller hvis du har brug for hele rækker fra de underliggende locations
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);