sql >> Database >  >> RDS >> PostgreSQL

Wat is de juiste index voor het opvragen van structuren in arrays in Postgres jsonb?

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


  1. Hoe de SQL Server DIFFERENCE()-functie werkt

  2. Hoe gegevens te herstellen van een verwijderde Docker-container? Hoe kan ik het opnieuw verbinden met de gegevens?

  3. Kan geen instantie van OLE DB-providerfout maken als Windows-verificatiegebruiker

  4. Wat betekent AAN [PRIMAIR]?