Ton*_*ony 16 sql postgresql indexing set-returning-functions jsonb
我正在试验jsonb在Postgres 9.4 中的Postgres 字段中保留以下值:
[{"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"}]
Run Code Online (Sandbox Code Playgroud)
我正在执行以下查询:
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'
)
)
)
Run Code Online (Sandbox Code Playgroud)
如何利用上述查询为该数据创建索引?这对于几百万行来说听起来是否合理?每行包含〜10个事件?
值得注意的是,我似乎仍在进行顺序扫描:
CREATE INDEX events_gin_idx ON some_table USING GIN (events);
Run Code Online (Sandbox Code Playgroud)
我猜是因为我在查询中做的第一件事就是将数据转换为json数组元素.
Erw*_*ter 41
首先,您不能像这样访问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"}]
Run Code Online (Sandbox Code Playgroud)
针对第一个数组元素的有效测试将是:
WHERE e->0->>'event_slug' = 'test_1'Run Code Online (Sandbox Code Playgroud)
但您可能不希望将搜索限制为数组的第一个元素.使用jsonbPostgres 9.4中的数据类型,您可以获得额外的运算符和索引支持.要索引数组的元素,您需要一个GIN索引.
GIN索引的内置运算符类不支持"大于"或"小于"运算符.这也是如此> >= < <=jsonb,您可以在两个运算符类之间进行选择.每个文件:
Name Indexed Data Type Indexable Operators
...
jsonb_ops jsonb ? ?& ?| @>
jsonb_path_ops jsonb @>
Run Code Online (Sandbox Code Playgroud)
(jsonb_ops作为默认值.)您可以涵盖相等性测试,但这些操作符都不能满足您的>=比较要求.你需要一个btree索引.
要支持与索引的相等性检查:
CREATE INDEX locations_events_gin_idx ON locations
USING gin (events jsonb_path_ops);
SELECT * FROM locations WHERE events @> '[{"event_slug":"test_1"}]';
Run Code Online (Sandbox Code Playgroud)
如果过滤器足够有选择性,这可能就足够了.
假设end_time >= start_time,我们不需要两次检查.仅检查end_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;
Run Code Online (Sandbox Code Playgroud)
利用隐含的JOIN LATERAL.细节(最后一章):
小心不同的数据类型!您在JSON值中的含义如下timestamp [without time zone],而您的谓词使用timestamp with time zone文字.该timestamp值根据当前时区设置进行解释,而给定的timestamptz文字必须timestamptz显式转换为或者时区将被忽略!以上查询应该按照需要工作.详细说明:
更多解释jsonb_array_elements():
如果上述情况不够好,我会考虑MATERIALIZED VIEW以标准化形式存储相关属性.这允许简单的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;
Run Code Online (Sandbox Code Playgroud)
相关答案jsonb_populate_recordset():
CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);
Run Code Online (Sandbox Code Playgroud)
还包括location_id允许仅索引扫描.(参见手册页和Postgres Wiki.)
查询:
SELECT *
FROM loc_event
WHERE event_slug = 'test_1'
AND end_time >= '2014-10-30 14:04:06 -0400'::timestamptz;
Run Code Online (Sandbox Code Playgroud)
或者,如果您需要基础locations表中的完整行:
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);
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
7973 次 |
| 最近记录: |