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は便宜のためだけのものです。したがって、日付範囲を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_idmon

  • LEFT JOIN結果をベーステーブルに、再びuser_id、結果をmon。このように、行は乗算も削除もされません。1か月に1行取得user_idします。Voilá。

ところで、私は列名を使用することはありませんiduser_id表でも呼んでくださいusers

于 2012-09-18T23:37:47.433 に答える