Zunächst einmal können Sie auf diese Weise nicht auf JSON-Array-Werte zugreifen. Für einen gegebenen json-Wert
[{"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"}]
Ein gültiger Test für das erste Array-Element wäre:
WHERE e->0->>'event_slug' = 'test_1'
Aber wahrscheinlich möchten Sie Ihre Suche nicht auf das erste Element des Arrays beschränken. Mit dem jsonb
Datentyp in Postgres 9.4 haben Sie zusätzliche Operatoren und Indexunterstützung. Um Elemente eines Arrays zu indizieren, benötigen Sie einen GIN-Index.
Die integrierten Operatorklassen für GIN-Indizes unterstützen keine „größer als“- oder „kleiner als“-Operatoren . Dies gilt für > >= < <=
jsonb
auch, wo Sie zwischen zwei Betreiberklassen wählen können. Per Dokumentation:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
ist die Standardeinstellung.) Sie können den Gleichheitstest abdecken, aber keiner dieser Operatoren erfüllt Ihre Anforderung für >=
Vergleich. Sie benötigen einen Btree-Index.
Basislösung
Um die Gleichheitsprüfung mit einem Index zu unterstützen:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Dies könnte gut genug sein, wenn der Filter selektiv genug ist.
Angenommen end_time >= start_time
, also brauchen wir keine zwei Schecks. Nur end_time
prüfen ist billiger und gleichwertig:
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;
Verwenden eines impliziten JOIN LATERAL
. Details (letztes Kapitel):
- PostgreSQL unnest() mit Elementnummer
Vorsicht mit den unterschiedlichen Datentypen ! Was Sie im JSON-Wert haben, sieht aus wie timestamp [without time zone]
, während Ihre Prädikate timestamp with time zone
verwenden Literale. Der timestamp
Der Wert wird gemäß der aktuellen Zeitzone interpretiert Einstellung, während der angegebene timestamptz
Literale müssen in timestamptz
umgewandelt werden explizit oder die Zeitzone würde ignoriert! Die obige Abfrage sollte wie gewünscht funktionieren. Ausführliche Erklärung:
- Zeitzonen in Rails und PostgreSQL komplett ignorieren
Weitere Erklärung für jsonb_array_elements()
:
- PostgreSQL-Beitritt mit JSONB
Erweiterte Lösung
Wenn das Obige nicht gut genug ist, würde ich eine MATERIALIZED VIEW
in Betracht ziehen das relevante Attribute in normalisierter Form speichert. Dies ermöglicht einfache Btree-Indizes.
Der Code geht davon aus, dass Ihre JSON-Werte ein konsistentes Format haben, wie in der Frage angezeigt.
Einrichtung:
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;
Zugehörige Antwort für jsonb_populate_recordset()
:
- So konvertieren Sie den jsonb-Typ von PostgreSQL 9.4 in Float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Auch einschließlich location_id
um Nur-Index-Scans zuzulassen . (Siehe Handbuchseite und Postgres-Wiki.)
Abfrage:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Oder wenn Sie vollständige Zeilen aus den zugrunde liegenden locations
benötigen Tabelle:
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);