如果键尚不存在,则将 key/value 对添加到 POSTGRES 中 jsonb 数组内的所有对象

Add key/value pair to all objects inside a jsonb array in POSTGRES if key does not already exist

我有一个table,我们称它为my_table,结构如下

ID   | data
____________
uuid | jsonb

jsonb字段中的数据是一个结构如下的对象:

{
  "A":[
    {"index":"1", "index2":"50"},
    {"index":"2"},
    {"index":"3"},
    {"index":"4", "index2":"10"},
  ],
  "B": "something",
  "C": "something_else",
}

我想为“A”中的每个对象添加一个“index2”键,值为 null,但前提是该键不存在。

这是我想要的结果:

{
  "A":[
    {"index":"1", "index2":"50"},
    {"index":"2", "index2":null},
    {"index":"3", "index2":null},
    {"index":"4", "index2":"10"},
  ],
  "B": "something",
  "C": "something_else",
}

我有以下查询有效但速度非常慢。在我的查询 运行ning EXPLAIN 之后,它显示对于每一行,它正在扫描整个 table(基本上是 n^2 复杂度)。

这是当前查询:

UPDATE my_table
SET data = JSONB_SET(my_table.data, '{A}', (
    SELECT JSONB_AGG( element || '{"index2": null}' )
    FROM JSONB_ARRAY_ELEMENTS(my_table.data -> 'A') element
))
FROM my_table as my_table_2, JSONB_ARRAY_ELEMENTS(
    my_table_2.data -> 'A'
) as element
WHERE jsonb_array_length(my_table.data -> 'A') > 0
AND element#>'{index2}' IS NULL;

如何加快速度以达到线性 运行 时间?

您不需要导致 CROSS JOIN 和二次行为的 FROM。

我在本地模拟了一下,经过简化,效果很快:

explain analyse
UPDATE my_table
SET data = JSONB_SET(data, '{A}',
                     (SELECT JSONB_AGG('{"index2": null}' || element)
                      FROM JSONB_ARRAY_ELEMENTS(data -> 'A') element))
WHERE data @? '$.A[*] ? (!exists(@.index2))';

线性计划:

Update on my_table  (cost=0.00..31308.89 rows=8801 width=42) (actual time=1156.675..1156.677 rows=0 loops=1)
  ->  Seq Scan on my_table  (cost=0.00..31308.89 rows=8801 width=42) (actual time=0.128..865.131 rows=100000 loops=1)
"        Filter: (data @? '$.""A""[*]?(!(exists (@.""index2"")))'::jsonpath)"
        Rows Removed by Filter: 100000
        SubPlan 1
          ->  Aggregate  (cost=1.51..1.52 rows=1 width=32) (actual time=0.005..0.005 rows=1 loops=100000)
                ->  Function Scan on jsonb_array_elements element  (cost=0.01..1.00 rows=100 width=32) (actual time=0.001..0.001 rows=4 loops=100000)
Planning Time: 0.268 ms
Execution Time: 1156.836 ms

如您所见,我用 200000 行对其进行了测试,其中一半需要更新。

请注意,您切换了 || 的操作数。此外,我的解决方案适用于 Postgres 12+,版本 11 不支持 jsonpath。您使用哪个版本?


回复关于 Postgres 10 的评论:

对于旧版本,我没有设法直接在条件中表达过滤器。看起来您可以改为定义一个函数:

create or replace function misses_index2(data jsonb) returns boolean
    language sql
as
$$
select count(*) filter ( where element #> '{index2}' IS NULL ) > 0
from JSONB_ARRAY_ELEMENTS(data -> 'A') element
$$;

(已在 Postgres 13 上测试,但在 Postgres 10 上也应该可以正常工作。)

那么where条件就是:

WHERE misses_index2(data);

查询速度大约是原来的两倍,但这是预料之中的。

Update on my_table  (cost=0.00..161007.00 rows=66990 width=42) (actual time=2413.235..2413.236 rows=0 loops=1)
  ->  Seq Scan on my_table  (cost=0.00..161007.00 rows=66990 width=42) (actual time=0.351..2123.757 rows=100000 loops=1)
        Filter: misses_index2(data)
        Rows Removed by Filter: 100000
        SubPlan 1
          ->  Aggregate  (cost=1.51..1.52 rows=1 width=32) (actual time=0.005..0.005 rows=1 loops=100000)
                ->  Function Scan on jsonb_array_elements element  (cost=0.01..1.00 rows=100 width=32) (actual time=0.001..0.001 rows=4 loops=100000)
Planning Time: 0.303 ms
Execution Time: 2413.347 ms