在 JSONB 记录数组中查找包含键的行

Kob*_*bus 5 postgresql index array json json-path

我正在尝试查询对象数组中存在的键。这种结构:

column jdata
{"name": "Somedata",
 "array": [ {"name":"bla1", "attr": "somevalue"}, 
            {"name":"bla2", "otherdata": "somevalue2"},
            {"name":"bla3", "otherdata": "somevalue"}
           ],
"otherstuff": "stuff"
}
Run Code Online (Sandbox Code Playgroud)

现在我在jdata->'name'or上做 btree (jdata->'datetime')::cast,效果很好。

我也做 json_path_opsjdata->'array' @> '[{"name":"bla3"}]'真正的魅力所在。

我的问题是attr键可以在数组中的任何对象中,如果键存在,我关心记录,但是值几乎可以是任何东西。有没有办法查询这个?有没有办法索引它?我想做jdata->'array' @> '[{"attr": ?}]'或者也许? 'attr'可以在数组中以某种方式使用?

目前我正在考虑一个扫描键的触发器,然后将它移动到一个带有 true 或 false 或其他什么的标题,然后一个普通的 btree 将工作。有没有更好的办法?我需要在平均站点上编辑大约 50 万条记录才能添加这些值。

请给我指明一个方向。

Erw*_*ter 8

Postgres 12 或更高版本:使用 SQL/JSON 路径表达式

SELECT *
FROM   tbl
WHERE  jdata->'array' @? '$ ? (exists (@."attr"))';
Run Code Online (Sandbox Code Playgroud)

您可以仅在外部嵌套级别或递归中查找键或值,以lax模式或不strict模式(模式)将数组抽象出来(取消嵌套)。我在小提琴中添加了查询变体来演示:

db<>在这里摆弄

@?jsonpath存在运算符

jsonpath价值'$ ? (exists (@."attr"))'解释:

$...查看在左操作数中找到的每个值(在默认的“宽松”模式下)
?...运行以下测试
(exists (@."attr"))...是否存在名称为“attr”的键?

可以使用索引,或者使用默认值jsonb_ops

CREATE INDEX tbl_jdata_array_idx ON tbl USING GIN ((jdata->'array'));
Run Code Online (Sandbox Code Playgroud)

或与jsonb_path_ops

CREATE INDEX tbl_jdata_array_path_ops_idx ON tbl USING GIN ((jdata->'array') jsonb_path_ops);
Run Code Online (Sandbox Code Playgroud)

看:

有关的:

Postgres 11 或更早版本(还没有 SQL/JSON)

内置运算符的普通索引未涵盖该特定用例。

没有索引支持的简单查询

SELECT *
FROM   tbl
WHERE  EXISTS (
   SELECT FROM jsonb_array_elements(jdata->'array') elem
   WHERE  elem ? 'attr' 
   );
Run Code Online (Sandbox Code Playgroud)

EXISTS因为我们希望每个符合条件的行一次,即使多个数组元素可以包含键。而且速度更快。
但是这个查询不能使用索引。

表达索引

您可以在给定的 jsonb 记录数组中生成唯一键的文本数组 - 并将表达式包装到一个简单的IMMUTABLE函数中:

CREATE OR REPLACE FUNCTION jsonb_arr_record_keys(jsonb)
  RETURNS text[] LANGUAGE sql IMMUTABLE AS
'SELECT ARRAY (
   SELECT DISTINCT k
   FROM   jsonb_array_elements($1) elem, jsonb_object_keys(elem) k
   )';

COMMENT ON FUNCTION jsonb_arr_record_keys(jsonb) IS '
   Generates text array of unique keys in jsonb array of records.
   Fails if any array element is not a record!';
Run Code Online (Sandbox Code Playgroud)

然后根据这个函数创建一个 GIN 表达式索引:

CREATE INDEX tbl_special_idx ON tbl USING gin (jsonb_arr_record_keys(jdata->'array'));
Run Code Online (Sandbox Code Playgroud)

像这样查询,使用泛型数组 contains 运算符@>

SELECT *
FROM   tbl
WHERE  jsonb_arr_record_keys(jdata->'array') @> '{attr}';
Run Code Online (Sandbox Code Playgroud)

现在可以有效地使用索引。

提供嵌套在数组 ( '{attr}') 中的键名。(您可以通过这种方式 ( '{attr1, attr2}') 或类似方式方便地检查多个键... )

dbfiddle在这里

有关的: