[英]How to aggregate an array of JSON objects with Postgres?
I'm looking to aggregate an array of JSON objects with Postgres, specifically for returning a list of relationships to another table by foreign key.我希望使用 Postgres 聚合一组 JSON 对象,专门用于通过外键将关系列表返回到另一个表。 In this case it's a
user
and their teams
.在这种情况下,它是
user
和他们的teams
。
Here's the schema I'm working with:这是我正在使用的架构:
CREATE TABLE teams (
id TEXT PRIMARY KEY,
...
);
CREATE TABLE users (
id TEXT PRIMARY KEY,
...
);
CREATE TABLE memberships (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL FOREIGN KEY (user_id) REFERENCES users(id),
team_id TEXT NOT NULL FOREIGN KEY (team_id) REFERENCES teams(id)
);
With the following query:使用以下查询:
SELECT
users.id,
...
CASE
WHEN count(teams.*) = 0
THEN '[]'::JSON
ELSE json_agg(DISTINCT teams.id)
END AS teams
FROM users
LEFT JOIN memberships ON users.id = memberships.user_id
LEFT JOIN teams ON teams.id = memberships.team_id
WHERE users.id = $[userId]
GROUP BY
users.id,
...
I can get results as a flat array of team_id
s:我可以将结果作为
team_id
的平面数组:
{
id: 'user_1',
...
teams: ['team_1', 'team_2']
}
But I'd like to receive the results as JSON objects instead:但我想将结果作为 JSON 对象接收:
{
id: 'user_1',
...
teams: [
{ id: 'team_1' },
{ id: 'team_2' }
]
}
I get extremely close with:我非常接近:
SELECT
users.id,
...
CASE
WHEN count(teams.*) = 0
THEN '[]'::JSON
ELSE json_agg(json_build_object('id', teams.id))
END AS teams
FROM users
LEFT JOIN memberships ON users.id = memberships.user_id
LEFT JOIN teams ON teams.id = memberships.team_id
WHERE users.id = $[userId]
GROUP BY
users.id,
...
But now I've lost the DISTINCT
function's de-duping of results, so I end up with duplicate IDs returned for each team
.但是现在我已经失去了
DISTINCT
函数的重复结果删除功能,所以我最终为每个team
返回了重复的 ID。
You can solve this using a sub-query which selects the appropriate combinations, then aggregate into a json
array:您可以使用选择适当组合的子查询来解决这个问题,然后聚合到一个
json
数组中:
SELECT id, json_strip_nulls(json_agg(json_build_object('id', team))) AS teams
FROM (
SELECT DISTINCT user_id AS id, team_id AS team
FROM memberships
WHERE user_id = $[userId]) sub
GROUP BY id;
You can get the user id from and the team id from the memberships
table, so no point in joining either table to the memberships
table (unless you get other fields from those tables that you haven't shown us).您可以从
memberships
表中获取用户ID 和团队ID,因此将任一表连接到memberships
表都没有意义(除非您从这些表中获取了尚未向我们展示的其他字段)。 If you do want to use other fields you can paste the JOIN
s right back in.如果您确实想使用其他字段,您可以将
JOIN
重新粘贴回来。
The json_strip_nulls()
function will get rid of the [{"id": null}]
occurrences and replace them with an empty []::json
. json_strip_nulls()
函数将去掉[{"id": null}]
出现并用空的[]::json
替换它们。 This is a PG 9.5 new feature.这是 PG 9.5 的新功能。 This also gets rid of the rather ugly and inefficient
CASE
clause.这也摆脱了相当丑陋和低效的
CASE
子句。
It looks to me like this will do it:在我看来,这会做到:
SELECT json_build_object(
'id', u.id,
'teams', array_remove(array_agg(DISTINCT t.*), NULL))
FROM users u
LEFT OUTER JOIN memberships m
ON m.user_id = u.id
LEFT OUTER JOIN teams t
ON m.team_id = t.id
GROUP BY u.id
Works in 9.4.在 9.4 中工作。 The part about removing
NULL
s is necessary for users with no team.对于没有团队的用户来说,删除
NULL
的部分是必要的。
I suspect a good general principle when doing JSON in Postgres is to stick with arrays and records as long as possible, and only switch to JSON at the last moment.我怀疑在 Postgres 中做 JSON 的一个很好的一般原则是尽可能长时间地坚持使用数组和记录,并且只在最后时刻切换到 JSON。 The more traditional structures have been around longer and are more closely tied to the relational model, so you're less likely to run into problems using them.
更传统的结构存在的时间更长并且与关系模型的联系更紧密,因此您在使用它们时遇到问题的可能性较小。 You can see that this query could have just as easily returned a column named
id
and an array-valued column named teams
.你可以看到这个查询可能有很容易地返回指定的列
id
和数组值列命名的teams
。
Note this query gives all users.请注意,此查询为所有用户提供。 If you want just one, put that in a
WHERE
clause.如果你只想要一个,把它放在
WHERE
子句中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.