加速嵌套在jsonb对象数组中的键值的范围测试

时间:2018-03-28 11:00:15

标签: sql arrays json postgresql jsonb

假设我有以下parents表:

create table parents (
  id       integer not null constraint parents_pkey primary key,
  name     text    not null,
  children jsonb   not null
);

children是以下结构的 json数组

[
    {
        "name": "child1",
        "age": 10
    }, 
    {
        "name": "child2",
        "age": 12
    } 
]

例如,我需要让所有有孩子的父母年龄在10到12岁之间。

我创建了以下查询:

select distinct
  p.*
from
  parents p, jsonb_array_elements(p.children) c
where
  (c->>'age')::int between 10 and 12;

当表parents很大(例如1M记录)时,它运行良好但速度很慢。我尝试使用'杜松子酒' children字段上的索引,但这没有帮助。

那么有没有办法加快这样的查询?或者也许还有另一种解决方案可以对嵌套的json数组中的字段进行查询/索引?

查询计划

Unique  (cost=1793091.18..1803091.18 rows=1000000 width=306) (actual time=4070.866..5106.998 rows=399947 loops=1)
  ->  Sort  (cost=1793091.18..1795591.18 rows=1000000 width=306) (actual time=4070.864..4836.241 rows=497313 loops=1)
        Sort Key: p.id, p.children, p.name
        Sort Method: external merge  Disk: 186040kB
        ->  Gather  (cost=1000.00..1406321.34 rows=1000000 width=306) (actual time=0.892..1354.147 rows=497313 loops=1)
              Workers Planned: 2
              Workers Launched: 2
              ->  Nested Loop  (cost=0.00..1305321.34 rows=416667 width=306) (actual time=0.162..1794.134 rows=165771 loops=3)
                    ->  Parallel Seq Scan on parents p  (cost=0.00..51153.67 rows=416667 width=306) (actual time=0.075..239.786 rows=333333 loops=3)
                    ->  Function Scan on jsonb_array_elements c  (cost=0.00..3.00 rows=1 width=0) (actual time=0.004..0.004 rows=0 loops=1000000)
                          Filter: ((((value ->> 'age'::text))::integer >= 10) AND (((value ->> 'age'::text))::integer <= 12))
                          Rows Removed by Filter: 3
Planning time: 0.218 ms
Execution time: 5140.277 ms

2 个答案:

答案 0 :(得分:1)

第一个立即措施是让查询更快一些:

SELECT *
FROM   parents p
WHERE  EXISTS (
   SELECT FROM jsonb_array_elements(p.children) c
   WHERE (c->>'age')::int BETWEEN 10 AND 12
   );

当多个数组对象匹配时,EXISTS半连接避免了中间表中的行重复 - 并且外部查询中需要DISTINCT ON。但那只是稍微快一点了。

核心问题是您要测试范围的整数值,而existing jsonb operators不提供此类功能。

有很多方法可以解决这个问题。不知道这些,这里是一个&#34; smart&#34;解决给定示例的解决方案。诀窍是将范围拆分为不同的值并使用jsonb包含运算符@>

SELECT *
FROM   parents p
WHERE (p.children @> '[{"age": 10}]'
OR     p.children @> '[{"age": 11}]'
OR     p.children @> '[{"age": 12}]');

jsonb_path_ops GIN索引支持:

CREATE INDEX parents_children_gin_idx ON parents USING gin (children jsonb_path_ops);

但是如果你的范围超过一整个整数值的手,那么你需要更通用的东西。由于总是,最好的解决方案取决于完整的情况:数据分布,值频率,查询中的典型范围,可能的NULL值,行大小,读/写模式,每个 jsonb值是否有一个或多个匹配的age密钥? ...

相关答案与专业,非常快的索引:

相关:

答案 1 :(得分:0)

我建议你尝试这种方式(根据我的经验)。

WITH t AS (SELECT id, jsonb_array_elements(children) as child_data FROM parents)
SELECT *  
  FROM parents 
 WHERE id IN (
              SELECT id
                FROM t
               WHERE (child_data->>'age')::int between 10 and 12
           )

希望它有效。