I explained the underlying problem (joining multiple tables with multiple matches) in this close answer .
First, I simplified your query syntactically to make it easier to read:
select pe.year ,sum(pe.wins) AS wins ,sum(pe.losses) AS losses ,sum(pe.ties) AS ties ,array_agg(po.id) AS position_id ,array_agg(po.name) AS position_names from periods_positions_coaches_linking pp join positions po ON po.id = pp.position join periods pe ON pe.id = pp.period where pp.coach = 1 group by pe.year order by pe.year;
Sets the same (incorrect) result as your original, but easier / faster / easier to read.
It makes no sense to join the table trainer until you use the columns in the SELECT list. I completely removed it and replaced the WHERE clause with where pp.coach = 1 .
You do not need COALESCE at all. NULL values ββare ignored in the aggregate function sum() . It makes no sense to replace 0 .
Use table aliases to make reading easier
Next, I solved your problem as follows:
SELECT * FROM ( SELECT pe.year ,array_agg(DISTINCT po.id) AS position_id ,array_agg(DISTINCT po.name) AS position_names FROM periods_positions_coaches_linking pp JOIN positions po ON po.id = pp.position JOIN periods pe ON pe.id = pp.period WHERE pp.coach = 1 GROUP BY pe.year ) po LEFT JOIN ( SELECT pe.year ,sum(pe.wins) AS wins ,sum(pe.losses) AS losses ,sum(pe.ties) AS ties FROM ( SELECT period FROM periods_positions_coaches_linking WHERE coach = 1 GROUP BY period ) pp JOIN periods pe ON pe.id = pp.period GROUP BY pe.year ) pe USING (year) ORDER BY year
Join positions and periods separately before joining them.
In the first list of position subqueries, only once, simply using DISTINCT .
In the second subquery
GROUP BY , because a coach can have several positions per period.JOIN to the data periods after that, and then aggregate to get the amounts.
source share