1

我有 3 张桌子:

users (id, account_balance)
grocery (user_id, date, amount_paid)
fishmarket (user_id, date, amount_paid)

鱼市和杂货店表都可能多次出现相同的 user_id,但支付的日期和金额不同,或者对于任何给定的用户都没有。我正在尝试开发以下结构的数据透视表:

id | grocery_amount_paid_January | fishmarket_amount_paid_January
  1          10                           NULL
  2          40                           71

我能想到的唯一想法是创建多个左连接,但这应该是错误的,因为每个产品将有 24 个连接(每个月)。有没有更好的办法?

4

1 回答 1

4

我最近在 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_paidperuser_id和结果mon.

  • LEFT JOIN结果到基表,再次通过user_id和结果mon。这样,行既不会增加也不会减少。user_id您每月获得一排。瞧。

顺便说一句,我永远不会使用列名iduser_id也可以在表中调用它users

于 2012-09-18T23:37:47.433 回答