The offer group bywill not create records in which there is no data, as you saw. What you can do is left joinall this result with another set of results that contains all the records you need, for example, one of them that you dynamically generate with 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
source
share