Först och främst kan du inte komma åt JSON-arrayvärden som det. För ett givet json-värde
[{"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"}]
Ett giltigt test mot det första arrayelementet skulle vara:
WHERE e->0->>'event_slug' = 'test_1'
Men du vill förmodligen inte begränsa din sökning till det första elementet i arrayen. Med jsonb
datatyp i Postgres 9.4 har du ytterligare operatörer och indexstöd. För att indexera element i en array behöver du ett GIN-index.
De inbyggda operatorklasserna för GIN-index stöder inte operatorerna "större än" eller "mindre än" . Detta gäller för > >= < <=
jsonb
också, där du kan välja mellan två operatörsklasser. Per dokumentation:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
är standard.) Du kan täcka likhetstestet, men ingen av dessa operatörer täcker ditt krav på >=
jämförelse. Du skulle behöva ett btree-index.
Grundlösning
För att stödja jämställdhetskontrollen med ett index:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Detta kan vara tillräckligt bra om filtret är tillräckligt selektivt.
Antar end_time >= start_time
, så vi behöver inte två kontroller. Kontrollerar endast end_time
är billigare och likvärdig:
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;
Använder en implicit JOIN LATERAL
. Detaljer (sista kapitlet):
- PostgreSQL unnest() med elementnummer
Var försiktig med de olika datatyperna ! Det du har i JSON-värdet ser ut som timestamp [without time zone]
, medan dina predikat använder timestamp with time zone
bokstavliga ord. timestamp
värdet tolkas enligt den aktuella tidszonen inställning, medan den givna timestamptz
bokstaver måste castas till timestamptz
explicit, annars skulle tidszonen ignoreras! Ovanstående fråga bör fungera som önskat. Detaljerad förklaring:
- Ignorerar tidszoner helt och hållet i Rails och PostgreSQL
Mer förklaring för jsonb_array_elements()
:
- PostgreSQL-anslutning med JSONB
Avancerad lösning
Om ovanstående inte är tillräckligt bra skulle jag överväga en MATERIALIZED VIEW
som lagrar relevanta attribut i normaliserad form. Detta tillåter vanliga btree-index.
Koden förutsätter att dina JSON-värden har ett konsekvent format som visas i frågan.
Inställningar:
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;
Relaterat svar för jsonb_populate_recordset()
:
- Hur man konverterar PostgreSQL 9.4:s jsonb-typ till flytande
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Inkluderar även location_id
för att tillåta endast indexsökningar . (Se manualsidan och Postgres Wiki.)
Fråga:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Eller om du behöver hela rader från de underliggande locations
tabell:
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);