【问题标题】:What's the proper index for querying structures in arrays in Postgres jsonb?在 Postgres jsonb 中查询数组结构的正确索引是什么?
【发布时间】:2014-12-17 10:04:24
【问题描述】:

我正在尝试在 Postgres 9.4 的 Postgres 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 数组元素。

【问题讨论】:

  • 在查询中你有e作为列名,在索引中我们看到events。请添加表定义(CREATE TABLE 脚​​本)以避免混淆。还有你的 Postgres 版本。您标记了jsonb,但谈到了“Postgres json”。同样,表定义会澄清。
  • @ErwinBrandstetter 抱歉造成混淆,我用更有意义的查询更新了问题。 “事件”是位置表中的 jsonb 列。现在都清楚了吗?

标签: sql postgresql indexing set-returning-functions jsonb


【解决方案1】:

首先,您不能像那样访问 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 索引的内置运算符类不支持“大于”或“小于”运算符> >= < <=jsonb 也是如此,您可以在其中选择两个运算符类。 Per documentation:

Name             Indexed Data Type  Indexable Operators
...
jsonb_ops        jsonb              ? ?& ?| @>
jsonb_path_ops   jsonb              @>
   

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"}]';

如果过滤器有足够的选择性,这可能就足够了。
假设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;

使用隐式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;

jsonb_populate_recordset()的相关答案:

CREATE INDEX loc_event_idx ON loc_event (event_slug, end_time, location_id);

还包括location_id 以允许仅索引扫描。 (见manual pagePostgres Wiki。)

查询:

SELECT *
FROM   loc_event
WHERE  event_slug = 'test_1'
AND    end_time  >= '2014-10-30 14:04:06 -0400'::timestamptz;

或者,如果您需要底层 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);

【讨论】:

  • 这是有道理的,所以是的......棘手的部分是比较。我可以使用更大的索引,但也有可能通过仅索引 event_slug 来获得足够的性能。但是,我需要在多个条件下匹配那些嵌入的哈希,所以我只想返回事件 slug 是被查询的项,并且该特定事件具有开始时间和结束时间要求。希望我的新查询示例能澄清这一点。
  • @Tony:添加了更多,简化并修复了一个错误。
  • 感谢您的详细解释。我的预感是最好只是标准化而不是将其存储为 jsonb (有点像您的物化视图推荐,虽然可能只是忘记视图并将数据移动到实际表中)......但我会看看性能如何出去。我必须检查开始时间和结束时间的唯一原因是要么可能为空。还要感谢您在时间戳方面付出的额外努力。实际上,我只需要大约 1 个月的时间来解决这些数据,所以并不担心,但很高兴知道。
  • @Tony: 如果start_timeend_time 可以为NULL,我建议在MV 中使用COALESCE(e.end_time, e.start_time) AS last_time。以标准化形式存储数据无疑是一个很好的选择。我的回答是基于您出于某种原因需要 JSON 的假设。
  • 这个答案为我的数据模式演变提供了一个很好的起点:我想存储 json 并查询它。我可以从 postgresql jsonb 开始,然后过渡到该 json 数据的物化视图,最后是该 json 数据的规范化表。谢谢@ErwinBrandstetter
【解决方案2】:
CREATE INDEX json_array_elements_index ON
    json_array_elements ((events_arr->>'event_slug'));

应该让你朝着正确的方向开始。

【讨论】:

    猜你喜欢
    • 2016-07-24
    • 2019-03-21
    • 1970-01-01
    • 1970-01-01
    • 2020-10-22
    • 2023-03-05
    • 2015-09-10
    • 1970-01-01
    • 2015-11-28
    相关资源
    最近更新 更多