El group by
cláusula no creará entradas donde no hay datos, como ha visto. Lo que podrías hacer es left join
este resultado completo con otro conjunto de resultados que tiene todas las entradas que desea, por ejemplo, una que genera dinámicamente con generate_series
:
SELECT generate_series AS month_number, cnt
FROM GENERATE_SERIES(1,12) g
LEFT JOIN (SELECT COUNT(s.id) AS cnt,
DATE_PART('month', s.viewed_at) AS month_number
FROM statistics_maps_view s
INNER JOIN maps m ON s.maps_id = m.id
WHERE m.users_id = $users_id
GROUP BY month_number) s ON g.generate_series = s.month_number
ORDER BY 1 ASC