Sql 什么';在Postgres jsonb中,查询数组结构的合适索引是什么?
我正在试验在Postgres 9.4中的PostgresSql 什么';在Postgres jsonb中,查询数组结构的合适索引是什么?,sql,postgresql,indexing,set-returning-functions,jsonb,Sql,Postgresql,Indexing,Set Returning Functions,Jsonb,我正在试验在Postgres 9.4中的Postgresjsonb字段中保留如下值: [{"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_
jsonb
字段中保留如下值:
[{"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"}]
我执行的查询如下:
SELECT * FROM locations
WHERE EXISTS (
SELECT 1 FROM jsonb_array_elements(events) AS e
WHERE (
e->>'event_slug' = 'test_1' AND
(
e->>'start_time' >= '2014-10-30 14:04:06 -0400' OR
e->>'end_time' >= '2014-10-30 14:04:06 -0400'
)
)
)
我将如何在该数据上创建索引,以便像上面这样的查询使用?对于该列中每个包含约10个事件的数百万行来说,这种设计听起来合理吗
值得注意的是,我似乎仍在接受以下顺序扫描:
CREATE INDEX events_gin_idx ON some_table USING GIN (events);
我猜这是因为我在查询中要做的第一件事是将数据转换为json数组元素
CREATE INDEX json_array_elements_index ON
json_array_elements ((events_arr->>'event_slug'));
应该让您从正确的方向开始。首先,您不能像那样访问JSON数组值。对于给定的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"}]
针对第一个数组元素的有效测试为:
WHERE e->0->>'event_slug' = 'test_1'
但您可能不想将搜索限制在数组的第一个元素。使用Postgres 9.4中的jsonb
数据类型,您可以获得额外的运算符和索引支持。要索引数组的元素,需要一个GIN索引
GIN索引的内置运算符类不支持“大于”或“小于”运算符比较。你需要一个btree索引
碱性溶液
要使用索引支持相等性检查,请执行以下操作:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
如果过滤器具有足够的选择性,这可能就足够了。假设
end\u time>=start\u time
,那么我们不需要两次检查。只检查结束时间
更便宜,也更划算:
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;
利用隐式连接横向。详情(最后一章):
小心使用不同的数据类型!JSON值中的内容看起来像timestamp[不带时区]
,而谓词使用timestamp和时区
文本。时间戳
值根据当前的时区设置进行解释,而给定的时间戳
文本必须显式转换为时间戳
,否则时区将被忽略!上面的查询应该可以根据需要工作。详细说明:
有关jsonb\u数组\u元素()的更多说明:
高级解决方案
如果上述条件不够好,我会考虑用标准化形式存储相关属性。这允许使用普通的btree索引
代码假定您的JSON值具有问题中显示的一致格式
设置:
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;
jsonb\u populate\u recordset()的相关答案
还包括location\u id
以允许仅索引扫描。(见和。)
查询:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
或者,如果需要基础位置表中的整行:
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);
在查询中将e
作为列名,在索引中我们可以看到events
。请添加表定义(CREATE table
script)以避免混淆。还有你的博士后版本。您标记了jsonb
,但提到了“postgresjson”。同样,表的定义会澄清。@ErwinBrandstetter很抱歉,我用一个更有意义的查询更新了这个问题。“事件”是位置表中的jsonb列。现在一切都清楚了吗?这是有道理的,是的……棘手的部分是比较。我可以使用更大的索引,但也有可能通过只索引event_slug来获得足够的性能。但是,我需要在多个条件上匹配这些嵌入的哈希,因此我只希望返回事件slug是要查询的项目,并且特定事件具有开始时间和结束时间要求。希望我的新查询示例能够澄清这一点。@Tony:添加了更多、简化并修复了一个bug。感谢您的详细解释。我的直觉是,最好只是将其规范化,而不是将其存储为jsonb(类似于您的物化视图建议,尽管可能只是忘记视图并将数据移动到实际表中)……但我将看看性能如何。我必须同时检查开始时间和结束时间的唯一原因是它们中的任何一个都可以为null。还感谢您在时间戳方面付出的额外努力。实际上,我只需要对这些数据进行~1个月的解析,所以我并不担心,但很高兴知道。@Tony:如果start\u time
和end\u time
可以为空,我建议在MV中使用COALESCE(例如end\u time,例如start\u time)作为最后一次时间。以规范化的形式存储数据当然是一个很好的选择。我的回答基于这样一个假设:出于某种原因,您可能需要JSON。这个回答为我的数据模式演变提供了一个很好的起点:我希望存储JSON,并查询它。我可以从postgresql jsonb开始,过渡到json数据的物化视图,最后是json数据的规范化表。谢谢@ErwinBrandstetter
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
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);