从多个可能为空的表中优化SQL查询

时间:2016-11-22 19:45:19

标签: sql postgresql query-optimization

我正在编写一个关于我的数据库(Postgres 9.5)的查询,希望从用户的多个表中获取所有信息。例如,我有一个用户,可能有多双鞋,个人记录,多个团队等。我有几个关联表来存储一对多的关系。我希望查询尽可能快,因为用户可能有每个表的多个项目。这是我目前的情况:

SELECT p.username, p.sex, p.birthdate, p.firstname, p.lastname, json_agg(json_build_object('team', t.*)) as teams, json_agg(json_build_object('shoe', s.*)) as shoes, json_agg(json_build_object('pr', pr.*)) as prs, d.devicename FROM person_tbl p
LEFT JOIN person_team_tbl tp ON tp.person_id = p.person_id
LEFT JOIN team_tbl t ON tp.team_id = t.team_id
LEFT JOIN person_shoe_tbl ps on ps.person_id = p.person_id
LEFT JOIN shoe_tbl s on ps.shoe_id = s.shoe_id
LEFT JOIN person_pr_tbl ppr on ppr.person_id = p.person_id
LEFT JOIN personalrecord_tbl pr on ppr.pr_id = pr.pr_id
LEFT JOIN person_device_tbl dp on dp.person_id = p.person_id
LEFT JOIN deviceinfo_tbl d on dp.device_id = d.device_id
GROUP BY p.username, p.sex, p.birthdate, p.firstname, p.lastname, d.devicename

这是编写查询的最有效方法吗?它返回我需要的东西,但我想确保它有效地编写。此外,它不会返回所有用户,而只会返回一个(我还没有写过)。

3 个答案:

答案 0 :(得分:2)

您拥有的是一个自然描述所需数据的查询;这是您通常应该努力的目标,然后由查询优化器来找到运行它的最快方法。

在某些情况下,您可能会发现可以重写查询以更快地运行,但是(a)随着数据库软件变得越来越好,这种情况变得越来越模糊,而且(b)它通常可能是一个坏主意,如什么技巧使基于成本的优化器今天运行良好可能会使它明天无法对付不同的数据集。

除非您有明确的性能问题 - 即您可以明确表达目标并且您没有达到目标 - 我会谨慎行事。如果您决定优化,那么在尝试超越优化器之前还需要考虑其他方法:

1)索引,统计信息和其他物理模型注意事项是否适合以您支持查询的方式定义的DBMS?我特别不是Postgress的专家,但我认为你想要查询中使用的主键和外键的索引,如果优化器要求你定义统计数据,那么d也希望在这些列上收集适当的统计数据。

2)查询是否在最佳环境中运行?期望重度分析查询在实时事务系统或动力不足的服务器上高效运行可能是不现实的

答案 1 :(得分:2)

见编辑的答案。

select      p.username, p.sex, p.birthdate, p.firstname, p.lastname
           ,t.teams,s.shoes,pr.prs
           ,d.devicename 

from                            person_tbl p

            left join lateral  (select  json_agg(json_build_object('team', t.* )) as teams 
                                from            person_team_tbl tp  
                                        join    team_tbl        t 
                                        on      tp.team_id = t.team_id
                                where   tp.person_id = p.person_id
                                ) t on true

            left join lateral  (select  json_agg(json_build_object('shoe', s.* )) as shoes 
                                from            person_shoe_tbl ps
                                        join    shoe_tbl        s  
                                        on      ps.shoe_id = s.shoe_id
                                where   ps.person_id = p.person_id
                                ) s on true

            left join lateral  (select  json_agg(json_build_object('pr', pr.*)) as prs 
                                from            person_pr_tbl       ppr
                                        join    personalrecord_tbl  pr 
                                        on      ppr.pr_id = pr.pr_id
                                where   ppr.person_id = p.person_id
                                ) pr on true

            left join           person_device_tbl   dp  on dp.person_id     = p.person_id
            left join           deviceinfo_tbl      d   on dp.device_id     = d.device_id
;            

答案 2 :(得分:0)

使用来自不同表的聚合时,加入聚合是一个好习惯,而不是在聚合之前构建所有记录组合的大中间结果。 (这也有助于在使用这些时获得总和和计数。)如果我必须编写查询,那么它将是这样的:

SELECT 
  p.username, p.sex, p.birthdate, p.firstname, p.lastname, 
  teams.json_team as teams, 
  shoes.json_shoe as shoes, 
  precs.json_prec as prs, 
  d.devicename
FROM person_tbl p
LEFT JOIN person_device_tbl dp on dp.person_id = p.person_id
LEFT JOIN deviceinfo_tbl d on dp.device_id = d.device_id
LEFT JOIN
(
  SELECT pt.person_id, json_agg(json_build_object('team', t.*)) as json_team
  FROM person_team_tbl pt
  JOIN team_tbl t ON t.team_id = pt.team_id
  GROUP BY pt.person_id
) teams ON teams.person_id = p.person_id
LEFT JOIN
(
  SELECT ps.person_id, json_agg(json_build_object('shoe', s.*)) as json_shoe
  FROM person_shoe_tbl ps
  JOIN shoe_tbl s on s.shoe_id = ps.shoe_id
  GROUP BY ps.person_id
) shoes ON shoes.person_id = p.person_id
LEFT JOIN
(
  SELECT ppr.person_id, json_agg(json_build_object('pr', pr.*)) as json_prec
  FROM person_pr_tbl ppr 
  JOIN personalrecord_tbl pr on pr.pr_id = ppr.pr_id
  GROUP BY ppr.person_id
) precs ON precs.person_id = p.person_id;