sql >> Databáze >  >> RDS >> PostgreSQL

Jaký je správný index pro dotazování struktur v polích v Postgres jsonb?

Za prvé, nemůžete takto přistupovat k hodnotám pole JSON. Pro danou hodnotu json

[{"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"}]

Platný test proti prvnímu prvku pole by byl:

WHERE e->0->>'event_slug' = 'test_1'

Pravděpodobně ale nechcete omezit hledání na první prvek pole. Pomocí jsonb datového typu v Postgresu 9.4 máte další operátory a podporu indexů. K indexování prvků pole potřebujete index GIN.

Vestavěné třídy operátorů pro indexy GIN nepodporují operátory "větší než" nebo "menší než" > >= < <= . To platí pro jsonb také, kde si můžete vybrat mezi dvěma třídami operátorů. Podle dokumentace:

Name             Indexed Data Type  Indexable Operators
...
jsonb_ops        jsonb              ? ?& ?| @>
jsonb_path_ops   jsonb              @>
   

(jsonb_ops je výchozí.) Můžete pokrýt test rovnosti, ale žádný z těchto operátorů nepokrývá váš požadavek na >= srovnání. Potřebovali byste index btree.

Základní řešení

Pro podporu kontroly rovnosti pomocí indexu:

CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);

SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';

To by mohlo stačit, pokud je filtr dostatečně selektivní.
Za předpokladu end_time >= start_time , takže nepotřebujeme dvě kontroly. Kontroluje se pouze end_time je levnější a ekvivalentní:

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;

Použití implicitního JOIN LATERAL . Podrobnosti (poslední kapitola):

  • PostgreSQL unnest() s číslem prvku

Buďte opatrní s různými typy dat ! To, co máte v hodnotě JSON, vypadá jako timestamp [without time zone] , zatímco vaše predikáty používají timestamp with time zone literály. timestamp hodnota je interpretována podle aktuálního časového pásma nastavení, zatímco daný timestamptz literály musí být přetypovány do timestamptz explicitně, jinak bude časové pásmo ignorováno! Výše uvedený dotaz by měl fungovat podle potřeby. Podrobné vysvětlení:

  • Úplné ignorování časových pásem v Rails a PostgreSQL

Další vysvětlení pro jsonb_array_elements() :

  • Připojování k PostgreSQL pomocí JSONB

Pokročilé řešení

Pokud výše uvedené nestačí, uvažoval bych o MATERIALIZED VIEW který ukládá příslušné atributy v normalizované podobě. To umožňuje prosté indexy btree.

Kód předpokládá, že vaše hodnoty JSON mají konzistentní formát, jak je zobrazeno v otázce.

Nastavení:

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;

Související odpověď pro jsonb_populate_recordset() :

  • Jak převést typ jsonb PostgreSQL 9.4 na float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);

Také včetně location_id povolit pouze indexové skenování . (Viz manuálovou stránku a Postgres Wiki.)

Dotaz:

SELECT *
FROM   loc_event
WHERE  event_slug = 'test_1'
AND    end_time  >= '2014-10-30 14:04:06 -0400'::timestamptz;

Nebo, pokud potřebujete celé řádky ze základních locations tabulka:

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. Získejte informace o zobrazení pomocí zobrazení informačního schématu VIEWS na serveru SQL Server

  2. MySQL:jak provést zabezpečení na úrovni řádků (jako je virtuální privátní databáze Oracle)?

  3. Hodnoty NULL uvnitř klauzule NOT IN

  4. Najděte v MySQL hodnoty, které neobsahují čísla