Tout d'abord, vous ne pouvez pas accéder aux valeurs de tableau JSON comme ça. Pour une valeur json donnée
[{"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"}]
Un test valide par rapport au premier élément du tableau serait :
WHERE e->0->>'event_slug' = 'test_1'
Mais vous ne voulez probablement pas limiter votre recherche au premier élément du tableau. Avec le jsonb
type de données dans Postgres 9.4, vous disposez d'opérateurs supplémentaires et d'un support d'index. Pour indexer les éléments d'un tableau, vous avez besoin d'un index GIN.
Les classes d'opérateurs intégrées pour les index GIN ne prennent pas en charge les opérateurs "supérieur à" ou "inférieur à" . Ceci est vrai pour > >= < <=
jsonb
ainsi, où vous pouvez choisir entre deux classes d'opérateurs. Par documentation :
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(jsonb_ops
étant la valeur par défaut.) Vous pouvez couvrir le test d'égalité, mais aucun de ces opérateurs ne couvre votre exigence pour >=
comparaison. Vous auriez besoin d'un index btree.
Solution de base
Pour prendre en charge la vérification d'égalité avec un index :
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Cela peut suffire si le filtre est suffisamment sélectif.
En supposant end_time >= start_time
, nous n'avons donc pas besoin de deux chèques. Vérification uniquement end_time
est moins cher et équivalent :
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;
Utilisation d'un JOIN LATERAL
implicite . Détails (dernier chapitre) :
- PostgreSQL unnest() avec le numéro d'élément
Attention aux différents types de données ! Ce que vous avez dans la valeur JSON ressemble à timestamp [without time zone]
, tandis que vos prédicats utilisent timestamp with time zone
littéraux. L'timestamp
la valeur est interprétée en fonction du fuseau horaire actuel paramètre, tandis que le timestamptz
donné les littéraux doivent être convertis en timestamptz
explicitement ou le fuseau horaire serait ignoré ! La requête ci-dessus devrait fonctionner comme vous le souhaitez. Explication détaillée :
- Ignorer complètement les fuseaux horaires dans Rails et PostgreSQL
Plus d'explications pour jsonb_array_elements()
:
- Joindre PostgreSQL en utilisant JSONB
Solution avancée
Si ce qui précède n'est pas assez bon, j'envisagerais une MATERIALIZED VIEW
qui stocke les attributs pertinents sous forme normalisée. Cela autorise les index btree simples.
Le code suppose que vos valeurs JSON ont un format cohérent tel qu'affiché dans la question.
Configuration :
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;
Réponse connexe pour jsonb_populate_recordset()
:
- Comment convertir le type jsonb de PostgreSQL 9.4 en flottant
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Inclut également location_id
pour autoriser les analyses d'index uniquement . (Voir la page de manuel et le wiki Postgres.)
Requête :
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Ou, si vous avez besoin de lignes complètes à partir des locations
sous-jacents tableau :
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);