如何在SQL中按月按产品创建透视表

如何在SQL中按月按产品创建透视表,sql,postgresql,aggregate-functions,crosstab,Sql,Postgresql,Aggregate Functions,Crosstab,我有三张桌子: users (id, account_balance) grocery (user_id, date, amount_paid) fishmarket (user_id, date, amount_paid) fishmarket和杂货店表格可能会多次出现在同一用户id上,并且支付的日期和金额不同,或者对于任何给定的用户都没有。我正在尝试开发以下结构的透视表: id | grocery_amount_paid_January | fishmarket_amount_paid_J

我有三张桌子:

users (id, account_balance)
grocery (user_id, date, amount_paid)
fishmarket (user_id, date, amount_paid)
fishmarket和杂货店表格可能会多次出现在同一用户id上,并且支付的日期和金额不同,或者对于任何给定的用户都没有。我正在尝试开发以下结构的透视表:

id | grocery_amount_paid_January | fishmarket_amount_paid_January
  1          10                           NULL
  2          40                           71

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

最近我提供了很多关于这个问题的答案。有时,像以下这样的简单查询可以完成此任务:

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;
要点 第一台CTE仅为方便起见。因此,您只需键入一次日期范围。您可以使用任何日期范围-只要它的日期与本月的第一个月剩余的将包括在内!。你可以添加,但我想你可以控制使用无效日期的冲动

第一个用户返回m的结果,它在您的日期范围内每月提供一行。您已经了解了如何为每个用户生成多行

这两个子查询是同卵双胞胎。使用在基列上操作的WHERE子句,这样它就可以利用一个索引—如果表运行了很多年,并且只有一年或两年没有使用,则顺序扫描会更快:

CREATE INDEX grocery_date ON grocery (date);
然后将所有日期减少到每月的第一个日期,包括每个用户id支付的金额和产生的mon

左键将结果连接到基表,再次按用户id和结果mon进行连接。这样,行既不会相乘也不会被删除。每个用户id和月份可获得一行。瞧


顺便说一句,我从不使用列名id。在表users中也可以称之为user\u id。

我最近提供了很多关于列名id的答案。有时,像以下这样的简单查询可以完成此任务:

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;
要点 第一台CTE仅为方便起见。因此,您只需键入一次日期范围。您可以使用任何日期范围-只要它的日期与本月的第一个月剩余的将包括在内!。你可以添加,但我想你可以控制使用无效日期的冲动

第一个用户返回m的结果,它在您的日期范围内每月提供一行。您已经了解了如何为每个用户生成多行

这两个子查询是同卵双胞胎。使用在基列上操作的WHERE子句,这样它就可以利用一个索引—如果表运行了很多年,并且只有一年或两年没有使用,则顺序扫描会更快:

CREATE INDEX grocery_date ON grocery (date);
然后将所有日期减少到每月的第一个日期,包括每个用户id支付的金额和产生的mon

左键将结果连接到基表,再次按用户id和结果mon进行连接。这样,行既不会相乘也不会被删除。每个用户id和月份可获得一行。瞧


顺便说一句,我从不使用列名id。在表users中也可以称之为user\u id。

如果MS SQL查看PIVOT和UNPIVOT@RyanBostwick在PostgreSQL中,你会看到你不清楚自己想要什么。请提供完整的示例结果。每月一列?还是每个id和月份一行?鱼市和杂货店在一张桌子上?那么24列+id?或者每个id 12行?@Erwin每个产品每个月+id应该有24列。Fishmarket和杂货是不同的表。它澄清了吗?如果MS SQL查看PIVOT和UNPIVOT@RyanBostwick在PostgreSQL中,你会看到你不清楚自己想要什么。请提供完整的示例结果。每月一列?还是每个id和月份一行?鱼市和杂货店在一张桌子上?那么24列+id?或者每个id 12行?@Erwin每个产品每个月+id应该有24列。Fishmarket和杂货是不同的表。它澄清了吗?