jsonb查询与数组中的嵌套对象

时间:2015-03-17 19:37:58

标签: sql postgresql relational-division postgresql-9.4 jsonb

我正在使用PostgreSQL 9.4和一个包含名为teams的{​​{1}}列的表jsonb。我正在寻找一个查询,我可以让所有在其玩家阵列中拥有玩家json34的球队。

该表包含两行,其中包含以下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" } ] } } 创建一个数组并进行比较,但我能够完成的任务都是团队中可用的任何比较玩家ID的结果,而不是所有这些。

2 个答案:

答案 0 :(得分:13)

你一次面临两个非平凡的任务。我很感兴趣。

  • 使用复杂的嵌套结构处理jsonb
  • 在文档类型上运行等效的关系除法查询。

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

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

我们只需要id,因此请不要包含namePer documentation:

  

输出

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

查询

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}';
Postgres 9.3中json

SQL Fiddle(第9.4页尚未提供)。

解释

  • 使用玩家记录提取JSON数组:

    t.json#>'{members,players}'
    
  • 从这些开始,我只删除id的行:

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

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

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

    , LATERAL (SELECT ... ) AS p(players)
    
  • 立即过滤生成的数组,只保留我们要查找的数组 - 使用"contains" array operator @>

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

VOILÀ。

如果你在一个大桌面上经常运行这个查询,你可以创建一个虚假的IMMUTABLE函数来提取上面的数组,并根据这个函数创建功能性 GIN索引这超级快 “假”因为函数取决于基础行类型,即在目录查找上,并且如果更改则会更改。 (所以要确保它不会改变。)与此类似:

除了:
不要使用json之类的类型名作为列名(即使允许这样做),这会引发棘手的语法错误并使错误消息混淆。

答案 1 :(得分: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%';

如果有任何帮助,请在此处发布。