我正在使用 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 在团队中都可用,而不是全部他们。
您同时面临两项重要任务。
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}';
提取包含玩家记录的 JSON 数组:
t.json#>'{members,players}'
从中,我仅使用
id
取消嵌套行:
jsonb_populate_recordset(null::foo, t.json#>'{members,players}')
...并立即将它们聚合到 Postgres 数组中,因此我们在基表中每行保留一行:
SELECT ARRAY ( ... )
所有这一切都发生在横向连接中:
, JOIN LATERAL (SELECT ... ) AS p(players) ...
立即过滤连接条件中的结果数组,仅保留我们正在查找的数组 - 使用 “contains”array 运算符
@>
:
...ON p.players @> '{3,4,7}'
如果你在一个大表上多次运行这个查询,你可以创建一个假的
IMMUTABLE
函数,像上面一样提取数组,并基于这个函数创建功能性 GIN 索引,以使其超级快。旁白:
不要使用像
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/
下标现在可以应用于它所属的任何数据类型 有用的符号,不仅仅是数组。在此版本中,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}