PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Was ist der richtige Index zum Abfragen von Strukturen in Arrays in Postgres jsonb?

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);