简体   繁体   English

如何使用 Postgres 聚合一组 JSON 对象?

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM