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

Sql 如何使用Postgres聚合JSON对象数组?,sql,json,postgresql,Sql,Json,Postgresql,我希望用Postgres聚合一个JSON对象数组,专门用于通过外键将关系列表返回到另一个表。在这种情况下,它是用户及其团队 以下是我正在使用的模式: CREATE TABLE teams ( id TEXT PRIMARY KEY, ... ); CREATE TABLE users ( id TEXT PRIMARY KEY, ... ); CREATE TABLE memberships ( id TEXT PRIMARY KEY, user_id TEXT NO

我希望用Postgres聚合一个JSON对象数组,专门用于通过外键将关系列表返回到另一个表。在这种情况下,它是
用户
及其
团队

以下是我正在使用的模式:

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)
);
使用以下查询:

  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,
    ...
我可以通过
team\u id
s的平面数组获得结果:

{
  id: 'user_1',
  ...
  teams: ['team_1', 'team_2']
}
但我希望将结果作为JSON对象接收:

{
  id: 'user_1',
  ...
  teams: [
    { id: 'team_1' },
    { id: 'team_2' }
  ]
}
我非常接近:

  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,
    ...

但是现在我丢失了
DISTINCT
函数的结果重复消除功能,因此我最终为每个
团队返回了重复的ID

您可以使用子查询来解决这个问题,该子查询选择适当的组合,然后聚合到
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;
您可以从
memberships
表中获取用户id,也可以从
memberships
表中获取团队id,因此将任何一个表连接到
memberships
表都没有意义(除非您从那些表中获取了其他字段,您还没有向我们展示)。如果您确实想使用其他字段,可以将
JOIN
s右键粘贴回


json\u strip\u nulls()
函数将删除出现的
[{“id”:null}]
事件,并将它们替换为空的
[]::json
。这是第9.5页的新功能。这也摆脱了相当丑陋和低效的
案例
条款。

在我看来,这样做可以:

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
在9.4中工作。关于删除
NULL
s的部分对于没有团队的用户是必需的

我认为在Postgres中使用JSON的一个好的一般原则是尽可能长时间地使用数组和记录,并且只在最后一刻切换到JSON。更传统的结构存在的时间更长,并且与关系模型的联系更紧密,因此使用它们时不太可能遇到问题。您可以看到,此查询可以同样轻松地返回名为
id
的列和名为
teams
的数组值列


注意:此查询提供所有用户。如果您只想要一个,请将其放在
WHERE
子句中。

您需要一个带有array_to_json的子查询,请参见这里的最后一个示例:使用
coalesce
似乎会导致在仍然没有匹配的团队时返回
[{id:null}]
,因为
会员资格
也有我需要的信息!因此,您可以拥有一个没有团队的用户。好的如果一个用户至少有一个成员资格,他是否仍然可以拥有一个没有团队的条目?如
(用户id,团队id)=[(1,null),(1,14),(1,26)]
中所述,或者这是不可能的?如果您使用第9.5页,请参阅更新的答案。(如果没有,升级.PG 9.5
json
支持将大大扩展。)