在数组中使用嵌套对象进行 jsonb 查询

问题描述 投票:0回答:3

我正在使用 PostgreSQL 9.4 和一个表

teams
,其中包含一个名为
jsonb
json
列。我正在寻找一个查询,可以获取在其球员阵列中拥有球员
3
4
7
的所有球队。

该表包含两行,其中包含以下

json
数据:

第一行:

{
    "id": 1,
    "name": "foobar",
    "members": {
        "coach": {
            "id": 1,
            "name": "A dude"
        },
        "players": [
            {
                "id": 2,
                "name": "B dude"
            },
            {
                "id": 3,
                "name": "C dude"
            },
            {
                "id": 4,
                "name": "D dude"
            },
            {
                "id": 6,
                "name": "F dude"
            },
            {
                "id": 7,
                "name": "G dude"
            }
        ]
    }
}

第二行:

{
    "id": 2,
    "name": "bazbar",
    "members": {
        "coach": {
            "id": 11,
            "name": "A dude"
        },
        "players": [
            {
                "id": 3,
                "name": "C dude"
            },
            {
                "id": 5,
                "name": "E dude"
            },
            {
                "id": 6,
                "name": "F dude"
            },
            {
                "id": 7,
                "name": "G dude"
            },
            {
                "id": 8,
                "name": "H dude"
            }
        ]
    }
}

查询必须是什么样子才能获得所需的团队列表?我尝试过一个查询,从成员玩家

jsonb_array_elements(json -> 'members' -> 'players')->'id'
创建一个数组并比较它们,但我所能完成的只是一个结果,其中任何比较的玩家 id 在团队中都可用,而不是全部他们。

sql postgresql relational-division postgresql-9.4 jsonb
3个回答
16
投票

您同时面临两项重要任务。

  • 使用复杂的嵌套结构处理
    jsonb
  • 对文档类型运行相当于关系划分查询的操作。

First

jsonb_populate_recordset()
适用于已注册的行类型。可以是任何(临时)表或视图的行类型,也可以是使用
CREATE TYPE
显式创建的复合类型。如果没有,请注册一个。对于临时使用,临时类型可以完成这项工作(未记录的黑客,在会话结束时自动删除):

CREATE TYPE pg_temp.foo AS (id int);  -- just "id"

我们只需要

id
,所以不要包含
name
说明书:

目标行类型中未出现的 JSON 字段将从输出中省略

支持索引查询

如果您快速需要,请在 jsonb 列上创建

GIN 索引
。更专业的 运算符类
jsonb_path_ops
甚至比默认的
jsonb_ops
:

还要快
CREATE INDEX teams_json_gin_idx ON teams USING GIN (json jsonb_path_ops);

可由“包含”运算符使用

@>
:

SELECT t.json->>'id' AS team_id
     , ARRAY (SELECT * FROM jsonb_populate_recordset(null::foo, t.json#>'{members,players}')) AS players
FROM   teams t
WHERE  json @> '{"members":{"players":[{"id":3},{"id":4},{"id":7}]}}';

SQL/JSON Postgres 12+ 中的路径语言可以使用相同的索引:

SELECT t.json->>'id' AS team_id
     , ARRAY (SELECT * FROM jsonb_populate_recordset(null::foo, t.json#>'{members,players}')) AS players
FROM   teams t
WHERE  json @? '$.members ? (@.players.id == 3) ? (@.players.id == 4) ? (@.players.id == 7)';

db<>小提琴这里

参见:

简单查询

没有索引支持 - 除非您创建定制的表达式索引,请参见下文。

SELECT t.json->>'id' AS team_id, p.players
FROM   teams t
JOIN   LATERAL (
   SELECT ARRAY (
      SELECT * FROM jsonb_populate_recordset(null::foo, t.json#>'{members,players}')
      )
   ) AS p(players) ON p.players @> '{3,4,7}';

db<>小提琴这里
sqlfiddle

如何?

提取包含玩家记录的 JSON 数组:

t.json#>'{members,players}'

从中,我仅使用

id
取消嵌套行:

jsonb_populate_recordset(null::foo, t.json#>'{members,players}')

...并立即将它们聚合到 Postgres 数组中,因此我们在基表中每行保留一行:

SELECT ARRAY ( ... )

所有这一切都发生在横向连接中:

, JOIN LATERAL (SELECT ... ) AS p(players) ...

如果你在一个大表上多次运行这个查询,你可以创建一个假的

IMMUTABLE
函数,像上面一样提取数组,并基于这个函数创建功能性 GIN 索引,以使其超级快。
“假”是因为该函数取决于基础行类型,即目录查找,并且如果发生变化也会发生变化。 (所以请确保它不会改变。)与此类似:

旁白:
不要使用像

json
这样的类型名称作为列名称(即使允许),这会导致棘手的语法错误和令人困惑的错误消息。


1
投票

我想做和上面一样的事情。唯一的条件是我必须进行子字符串匹配而不是精确匹配。

这就是我最终所做的(当然是受到上面答案的启发)

SELECT t.json->>'name' AS feature_name, f.features::text
FROM   teams t
 , LATERAL  (
     SELECT * FROM json_populate_recordset(null::foo, t.json#>'{members,features}')
   ) AS f(features)
 WHERE f.features LIKE '%dud%';

如果有任何帮助,请发布在这里。


1
投票

https://www.postgresql.org/docs/release/14.0/

下标现在可以应用于它所属的任何数据类型 有用的符号,不仅仅是数组。在此版本中,jsonb 和 hstore 类型已经获得了下标运算符。 让我们使用 postgresql 14 中的下标功能。

with a as(
select data['id'] as teamid,
       (jsonb_array_elements( data['members']['players']))['id'] as playerid
from teams), b as( select teamid, array_agg(playerid) as playerids from a group by 1)
select b.* from b where b.playerids @> '{3,4,7}';

返回:

 teamid |  playerids
--------+-------------
 1      | {2,3,4,6,7}

 

数据库小提琴

© www.soinside.com 2019 - 2024. All rights reserved.