最近、PostgreSQLのクロス集計クエリについて多くの回答を提供しました。次のような「単純な」クエリが機能する場合があります。
WITH x AS (SELECT '2012-01-01'::date AS _from
,'2012-12-01'::date As _to) -- provide date range once in CTE
SELECT u.id
,to_char(m.mon, 'MM.YYYY') AS month_year
,g.amount_paid AS grocery_amount_paid
,f.amount_paid AS fishmarket_amount_paid
FROM users u
CROSS JOIN (SELECT generate_series(_from, _to, '1 month') AS mon FROM x) m
LEFT JOIN (
SELECT user_id
,date_trunc('month', date) AS mon
,sum(amount_paid) AS amount_paid
FROM x, grocery -- CROSS JOIN with a single row
WHERE date >= _from
AND date < (_to + interval '1 month')
GROUP BY 1,2
) g ON g.user_id = u.id AND m.mon = g.mon
LEFT JOIN (
SELECT user_id
,date_trunc('month', date) AS mon
,sum(amount_paid) AS amount_paid
FROM x, fishmarket
WHERE date >= _from
AND date < (_to + interval '1 month')
GROUP BY 1,2
) f ON f.user_id = u.id AND m.mon = g.mon
ORDER BY u.id, m.mon;
この出力を生成します:
id | month_year | grocery_amount_paid | fishmarket_amount_paid
---+------------+---------------------+------------------------
1 | 01.2012 | 10 | NULL
1 | 02.2012 | NULL | 65
1 | 03.2012 | 98 | 13
...
2 | 02.2012 | 40 | 71
2 | 02.2012 | NULL | NULL
主なポイント
最初のCTEは便宜のためだけのものです。したがって、日付範囲を1回だけ入力する必要があります。月の最初の日付である限り、任意の日付範囲を使用できます(月の残りの部分が含まれます!)。追加date_trunc()
することもできますが、無効な日付を使用したいという衝動を抑えることができると思います。
あなたの日付範囲で月に1行を提供する( )CROSS JOIN
の結果への最初のユーザー。最後の質問で、それがユーザーごとに複数の行になる方法を学びました。generate_series()
m
2つのサブクエリは同一の双子です。ベース列を操作する句を使用WHERE
して、インデックスを利用できるようにします。これは、テーブルが何年にもわたって実行される場合に必要です(1年または2年だけ使用しない場合は、順次スキャンが高速になります)。
CREATE INDEX grocery_date ON grocery (date);
次に、すべての日付を月の最初に減らしdate_trunc()
、合計と結果を計算しamount_paid
ます。user_id
mon
LEFT JOIN
結果をベーステーブルに、再びuser_id
、結果をmon
。このように、行は乗算も削除もされません。1か月に1行取得user_id
します。Voilá。
ところで、私は列名を使用することはありませんid
。user_id
表でも呼んでくださいusers
。