使用聚合时,Postgres连接返回doubled列

时间:2017-03-02 21:37:56

标签: sql postgresql

我有一个project表,其中m2m键是users表和skills表。

我正在尝试向postgres发出请求,我在其中获取每个项目的技能和用户列表。

所以实质上是

{
  "project": "foo",
  "skills": [
    {
      "id":1,
      "name": "js"
    },
    {
      "id":2,
      "name": "py"
    }
  ],
  "members":[
    {
      "id":1,
      "name": "foo"
    },
    {
      "id":2,
      "name": "bar"
    }
  ]
}

我使用json_build_object将多个技能/用户行转换为json数组

这是我的sql

SELECT project.id AS project_id,
      project.name AS project_name,
      array_agg(json_build_object('skill_id',s.id,'name',s.skill)) AS skills,
      array_agg(json_build_object('id',p.project,'name',p.Username)) AS members

from project

    LEFT JOIN (
      select project_skills.id,project_skills.skill,project_skills.project AS name
      from project_skills
    ) s on s.name = project.name

LEFT JOIN (
  select account_projects.Username, account_projects.project AS name
  from account_projects
) p on p.name = project.name

WHERE project.id = $1
GROUP BY project_id,project_name

如果我运行此查询,则技能/用户表加倍(而不是3个用户,我得到6)。

我应该在查询中添加什么以确保没有重复计算?

我正在使用postgres 9.4

2 个答案:

答案 0 :(得分:1)

您正在聚合两个不同的维度。您应该在加入之前汇总而不是之后:

select p.id as project_id, p.name as project_name,
       s.skills, ap.members
from project p left join
     (select s.id, s.name,
             array_agg(json_build_object('skill_id', s.id, 'name', s.skill)) as skills
      from project_skills s
      group by s.name
     ) s
     on s.name = p.name left join
     (select ap.name,
             array_agg(json_build_object('id', ap.project, 'name', ap.Username)) as members
      from account_projects ap
      group by ap.name
     ) ap
     on ap.name = p.name
where p.id = $1;

编辑:

因为您只选择一个项目,所以使用横向连接或相关子查询可能更便宜:

select p.id as project_id, p.name as project_name,
       (select s.id, s.name,
               array_agg(json_build_object('skill_id', s.id, 'name', s.skill)) as skills
        from project_skills s
        where s.name = p.name
       ) skills,
       (select ap.name,
            array_agg(json_build_object('id', ap.project, 'name', ap.Username)) as members
        from account_projects ap
        where ap.name = p.name
       ) as members
from project p 
where p.id = $1;

答案 1 :(得分:0)

如果你可以使用jsonb代替json,查询可能如下所示:

SELECT project.id AS project_id,
      project.name AS project_name,
      jsonb_agg(DISTINCT jsonb_build_object('skill_id',s.id,'name',s.skill)) AS skills,
      jsonb_agg(DISTINCT jsonb_build_object('id',p.id,'name',p.Username)) AS members

from project

    LEFT JOIN (
      select project_skills.id,project_skills.skill,project_skills.project AS name
      from project_skills
    ) s on s.name = project.name

LEFT JOIN (
  select account_projects.id, account_projects.Username, account_projects.project AS name
  from account_projects
) p on p.name = project.name

WHERE project.id = 1
GROUP BY project_id,project_name

至少更好地使用jsonb_agg而不是array_agg。