Przede wszystkim nie możesz uzyskać dostępu do wartości tablicy JSON w ten sposób. Dla danej wartości 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"}]
Prawidłowy test na pierwszy element tablicy to:
WHERE e->0->>'event_slug' = 'test_1'
Ale prawdopodobnie nie chcesz ograniczać wyszukiwania do pierwszego elementu tablicy. Z jsonb
typ danych w Postgresie 9.4 masz dodatkowe operatory i obsługę indeksów. Aby zindeksować elementy tablicy, potrzebujesz indeksu GIN.
Wbudowane klasy operatorów dla indeksów GIN nie obsługują operatorów „większy niż” ani „mniejszy niż” . Dotyczy to > >= < <=
jsonb
jak również, gdzie możesz wybrać pomiędzy dwiema klasami operatorów. Zgodnie z dokumentacją:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
jest wartością domyślną). Możesz pokryć test równości, ale żaden z tych operatorów nie spełnia wymagań dla >=
porównanie. Będziesz potrzebował indeksu btree.
Podstawowe rozwiązanie
Aby wesprzeć sprawdzanie równości za pomocą indeksu:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Może to wystarczyć, jeśli filtr jest wystarczająco selektywny.
Zakładając, że end_time >= start_time
, więc nie potrzebujemy dwóch kontroli. Sprawdzanie tylko end_time
jest tańszy i równoważny:
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;
Wykorzystanie niejawnego JOIN LATERAL
. Szczegóły (ostatni rozdział):
- PostgreSQL unnest() z numerem elementu
Uważaj na różne typy danych ! To, co masz w wartości JSON, wygląda jak timestamp [without time zone]
, podczas gdy Twoje predykaty używają timestamp with time zone
literały. timestamp
wartość jest interpretowana zgodnie z aktualną strefą czasową ustawienie, podczas gdy podany timestamptz
literały muszą być rzutowane na timestamptz
jawnie lub strefa czasowa zostanie zignorowana! Powyższe zapytanie powinno działać zgodnie z oczekiwaniami. Szczegółowe wyjaśnienie:
- Całkowite ignorowanie stref czasowych w Rails i PostgreSQL
Więcej wyjaśnień dla jsonb_array_elements()
:
- Dołączanie do PostgreSQL przy użyciu JSONB
Zaawansowane rozwiązanie
Jeśli powyższe nie jest wystarczająco dobre, rozważyłbym MATERIALIZED VIEW
który przechowuje odpowiednie atrybuty w znormalizowanej formie. Pozwala to na zwykłe indeksy btree.
Kod zakłada, że Twoje wartości JSON mają spójny format, jak pokazano w pytaniu.
Konfiguracja:
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;
Powiązana odpowiedź dla jsonb_populate_recordset()
:
- Jak przekonwertować typ jsonb PostgreSQL 9.4 na pływający
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Uwzględnia także location_id
aby zezwolić na skanowanie tylko do indeksu . (Zobacz stronę podręcznika i Postgres Wiki.)
Zapytanie:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Lub, jeśli potrzebujesz pełnych wierszy z podstawowych locations
tabela:
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);