jsonb 查询数组中的嵌套对象

jsonb query with nested objects in an array

我将 PostgreSQL 9.4 与 table teams 一起使用,其中包含名为 jsonjsonb 列。我正在寻找一个查询,我可以在其中获得所有在其球员数组中拥有球员 347 的球队。

table 包含两行 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 在团队中可用,而不是所有这些。

您同时面临两项重要任务。我很好奇。

  • 处理具有复杂嵌套结构的 jsonb
  • 运行相当于文档类型的关系划分查询。

首先,为 jsonb_populate_recordset() 注册一个行类型。您可以使用 CREATE TYPE 创建一个永久类型,或者创建一个临时 table 供临时使用(在会话结束时自动删除):

CREATE TEMP TABLE foo(id int);  -- just "id", we don't need "name"

我们只需要 id,所以不包括 namePer documentation:

JSON fields that do not appear in the target row type will be omitted from the output

查询

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

SQL Fiddle for json 在 Postgres 9.3 中(pg 9.4 尚不可用)。

解释一下

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

    t.json#>'{members,players}'
    
  • 从这些中,我只用 id 取消嵌套行:

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

    ... 并立即将它们聚合到一个 Postgres 数组中,因此我们在基础 table:

    中每行保留一行
    SELECT ARRAY ( ... )
    
  • 所有这些都发生在横向连接中:

    , LATERAL (SELECT ... ) AS p(players)
    
  • 立即过滤结果数组以仅保留我们正在寻找的数组 - 使用 "contains" array operator @>:

    WHERE p.players @> '{3,4,7}'
    

瞧瞧。

如果你 运行 这个查询在一个大的 table 上很多,你可以创建一个假的 IMMUTABLE 函数来提取上面的数组并创建函数 GIN 索引 基于这个函数使这个超级快。
"Fake" 因为该函数取决于基础行类型,即目录查找,并且如果发生变化也会发生变化。 (所以确保它不会改变。)类似于这个:

  • Index for finding an element in a JSON array

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

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

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

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%';

如果对您有帮助,请在此发布。

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

Subscripting can now be applied to any data type for which it is a useful notation, not only arrays. In this release, the jsonb and hstore types have gained subscripting operators. Let's use subscripting feature in 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}';

returns:

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

 

DB fiddle