Этот должен дать вам правильный результат. Внутренний выбор подготавливает список всех месяцев, объединенных со всеми категориями, а LEFT JOIN
обрабатывает все остальные.
SELECT t.month, t.cat_desc, COALESCE(SUM(t2.amount), 0) amount
FROM (
SELECT DISTINCT t.month, c.cat_id, c.cat_desc
FROM categories c
CROSS JOIN transactions t
) t
LEFT JOIN transactions t2 ON ( t2.month = t.month AND t2.cat_id = t.cat_id )
GROUP BY t.month, t.cat_desc
Производительность может быть лучше при следующем (используйте DISTINCT
только там, где это необходимо), но вам придется попробовать:
SELECT t.month, t.cat_desc, COALESCE(SUM(t2.amount), 0) amount FROM (
SELECT t.month, c.cat_id, c.cat_desc
FROM
(SELECT DISTINCT month FROM transactions) t
CROSS JOIN categories c
) t
LEFT JOIN transactions t2 ON ( t2.month = t.month AND t2.cat_id = t.cat_id )
GROUP BY t.month, t.cat_desc