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