sql >> Database teknologi >  >> RDS >> PostgreSQL

Hvad er det korrekte indeks for at forespørge strukturer i arrays i Postgres jsonb?

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


  1. Entity Framework opretter et tabelnavn i flertal, men visningen forventer et tabelnavn i ental?

  2. 2ndQuadrant Deutschland – Special Training Opening Bargain

  3. Nulstil PostgreSQL primærnøgle til 1

  4. Hvor skal værdien af ​​small_case_table_names=2 ændres på windows xampp