我最近在 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 只是为了方便。因此,您只需输入一次日期范围。您可以使用任何日期范围 - 只要它是当月第一天的日期(将包括该月的其余时间!)。您可以添加date_trunc()
它,但我想您可以控制使用无效日期的冲动。
获得( ) 结果的第一批CROSS JOIN
用户,该结果在您的日期范围内每月提供一行。您已经在上一个问题中了解了如何导致每个用户多行。generate_series()
m
这两个子查询是同卵双胞胎。使用WHERE
对基列进行操作的子句,因此它可以利用索引 - 如果您的表运行多年,您应该拥有索引(仅使用一两年,顺序扫描会更快):
CREATE INDEX grocery_date ON grocery (date);
然后将所有日期减少到本月的第一天,date_trunc()
并使用 sum amount_paid
peruser_id
和结果mon
.
LEFT JOIN
结果到基表,再次通过user_id
和结果mon
。这样,行既不会增加也不会减少。user_id
您每月获得一排。瞧。
顺便说一句,我永远不会使用列名id
。user_id
也可以在表中调用它users
。