Allereerst hebt u op die manier geen toegang tot JSON-arraywaarden. Voor een bepaalde json-waarde
[{"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"}]
Een geldige test tegen het eerste array-element zou zijn:
WHERE e->0->>'event_slug' = 'test_1'
Maar u wilt uw zoekopdracht waarschijnlijk niet beperken tot het eerste element van de array. Met de jsonb
gegevenstype in Postgres 9.4 heeft u extra operators en indexondersteuning. Om elementen van een array te indexeren heb je een GIN-index nodig.
De ingebouwde operatorklassen voor GIN-indexen ondersteunen geen "groter dan" of "kleiner dan" operators . Dit geldt voor > >= < <=
jsonb
ook, waar u kunt kiezen tussen twee operatorklassen. Per documentatie:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
is de standaard.) U kunt de gelijkheidstest dekken, maar geen van deze operators dekt uw vereiste voor >=
vergelijking. Je hebt een btree-index nodig.
Basisoplossing
Om de gelijkheidscontrole te ondersteunen met een index:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Dit kan goed genoeg zijn als het filter selectief genoeg is.
Ervan uitgaande dat end_time >= start_time
, dus we hebben geen twee cheques nodig. Alleen end_time
controleren is goedkoper en gelijkwaardig:
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;
Gebruikmakend van een impliciete JOIN LATERAL
. Details (laatste hoofdstuk):
- PostgreSQL unnest() met elementnummer
Pas op met de verschillende gegevenstypen ! Wat je in de JSON-waarde hebt, ziet eruit als timestamp [without time zone]
, terwijl uw predikaten timestamp with time zone
. gebruiken letterlijke. De timestamp
waarde wordt geïnterpreteerd volgens de huidige tijdzone instelling, terwijl de gegeven timestamptz
letterlijke waarden moeten worden gegoten naar timestamptz
expliciet of de tijdzone zou worden genegeerd! Bovenstaande query zou naar wens moeten werken. Gedetailleerde uitleg:
- Tijdzones volledig negeren in Rails en PostgreSQL
Meer uitleg voor jsonb_array_elements()
:
- PostgreSQL-deelname met JSONB
Geavanceerde oplossing
Als het bovenstaande niet goed genoeg is, zou ik een MATERIALIZED VIEW
overwegen die relevante attributen in genormaliseerde vorm opslaat. Dit maakt gewone btree-indexen mogelijk.
De code gaat ervan uit dat uw JSON-waarden een consistente indeling hebben zoals weergegeven in de vraag.
Instelling:
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;
Gerelateerd antwoord voor jsonb_populate_recordset()
:
- Hoe het jsonb-type van PostgreSQL 9.4 naar float te converteren
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Ook inclusief location_id
om alleen-index scans toe te staan . (Zie handleiding en Postgres Wiki.)
Vraag:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Of, als u volledige rijen nodig heeft van de onderliggende locations
tafel:
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);