Em primeiro lugar, você não pode acessar valores de matriz JSON assim. Para um determinado valor 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"}]
Um teste válido contra o primeiro elemento da matriz seria:
WHERE e->0->>'event_slug' = 'test_1'
Mas você provavelmente não deseja limitar sua pesquisa ao primeiro elemento da matriz. Com o
jsonb
tipo de dados no Postgres 9.4 você tem operadores adicionais e suporte a índices. Para indexar elementos de um array você precisa de um índice GIN. As classes de operadores integradas para índices GIN não suportam operadores "maior que" ou "menor que"
> >= < <=
jsonb
também, onde você pode escolher entre duas classes de operadores. Por documentação:Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
(
jsonb_ops
sendo o padrão.) Você pode cobrir o teste de igualdade, mas nenhum desses operadores cobre seu requisito para >=
comparação. Você precisaria de um índice btree. Solução básica
Para dar suporte à verificação de igualdade com um índice:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Isso pode ser bom o suficiente se o filtro for seletivo o suficiente.
Supondo que
end_time >= start_time
, então não precisamos de duas verificações. Verificando apenas end_time
é mais barato e equivalente: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;
Utilizando um
JOIN LATERAL
implícito . Detalhes (último capítulo):- PostgreSQL unnest() com número do elemento
Cuidado com os diferentes tipos de dados ! O que você tem no valor JSON se parece com
timestamp [without time zone]
, enquanto seus predicados usam timestamp with time zone
literais. O timestamp
o valor é interpretado de acordo com o fuso horário atual configuração, enquanto o timestamptz
fornecido literais devem ser convertidos em timestamptz
explicitamente ou o fuso horário seria ignorado! A consulta acima deve funcionar como desejado. Explicação detalhada:- Ignorando completamente os fusos horários no Rails e no PostgreSQL
Mais explicações para
jsonb_array_elements()
:- Ingressando no PostgreSQL usando JSONB
Solução avançada
Se o acima não for bom o suficiente, eu consideraria uma
MATERIALIZED VIEW
que armazena atributos relevantes em formato normalizado. Isso permite índices btree simples. O código pressupõe que seus valores JSON tenham um formato consistente conforme exibido na pergunta.
Configuração:
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;
Resposta relacionada para
jsonb_populate_recordset()
:- Como converter o tipo jsonb do PostgreSQL 9.4 para float
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Incluindo também
location_id
para permitir verificações somente de índice . (Consulte a página de manual e o Postgres Wiki.) Inquerir:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Ou, se você precisar de linhas completas dos
locations
subjacentes 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);