postgresqlで月と年でクエリ結果をグループ化する


156

Postgresサーバーに次のデータベーステーブルがあります。

id      date          Product Sales
1245    01/04/2013    Toys    1000     
1245    01/04/2013    Toys    2000
1231    01/02/2013    Bicycle 50000
456461  01/01/2014    Bananas 4546

私は与えクエリを作成したいSUMSales月と年次のようにすることにより、カラムとグループの結果を:

Apr    2013    3000     Toys
Feb    2013    50000    Bicycle
Jan    2014    4546     Bananas

それを行う簡単な方法はありますか?

回答:


217
select to_char(date,'Mon') as mon,
       extract(year from date) as yyyy,
       sum("Sales") as "Sales"
from yourtable
group by 1,2

ラドゥの要請で、私はそのクエリを説明します:

to_char(date,'Mon') as mon, :「日付」属性を月の短い形式の定義済みフォーマットに変換します。

extract(year from date) as yyyy :Postgresqlの「抽出」関数は、「日付」属性からYYYY年を抽出するために使用されます。

sum("Sales") as "Sales" :SUM()関数は、すべての "Sales"値を合計し、二重引用符を使用して大文字と小文字を区別するエイリアスを提供します。

group by 1,2:GROUP BY関数には、集約の一部ではないSELECTリストからのすべての列(別名、SUM / AVG / MIN / MAXなどの関数内にないすべての列)が含まれている必要があります。これは、列の一意の組み合わせごとにSUM()を適用する必要があることをクエリに伝えます。この場合、月と年の列です。"1,2"の部分は列のエイリアスを使用する代わりに省略形ですが、読みやすくするために完全な "to_char(...)"および "extract(...)"式を使用するのがおそらく最善です。


5
説明なしで答えることは、特に初心者にとって非常に良い考えだとは思いません。答えの背後にあるロジックを説明する必要があります。少なくとも少しは(おそらく、他の人にとっては単純で簡単に見えるかもしれませんが)。
Radu Gheorghiu 2013

1
@BurakArslan結果は、OPが具体的に要求したもののように見えましたか?
bma 2014年

2
@rogerdpack、の出力は、質問者date_truncが望んでいたものとはselect date_trunc('month', timestamp '2001-02-16 20:38:40')::date2001-02-01
異なり

2
私が使用してのアイデアのようdate_truncgroup by句を。
pisaruk

1
可能な「フィールドはgroup by句に含まれている必要がある」という問題... OVER(PARTITION BY)を使用することをお勧めします。
Zon

316

受け入れられた回答に多くの賛成票があるとは信じられません-それは恐ろしい方法です。

date_truncを使用した正しい方法は次のとおりです。

   SELECT date_trunc('month', txn_date) AS txn_month, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY txn_month

悪い習慣ですが、使用すれば許されるかもしれません

 GROUP BY 1

非常に単純なクエリで。

あなたも使うことができます

 GROUP BY date_trunc('month', txn_date)

日付を選択したくない場合。


6
残念ながら、の出力はdate_trunc、質問者が期待したものではありません: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00
pisaruk

3
この方法の方が良いことに同意します。よくわかりませんが、グループ化が2つではなく1つしかないため、より効率的でもあると思います。日付を再フォーマットする必要がある場合は、後で他の回答に記載されている方法を使用して行うことができます。to_char(date_trunc('month', txn_date), 'YY-Mon')
PawełSokołowski

1
はい、受け入れられた回答の投票数は気が遠くなるほどで​​す。date_truncこの正確な目的のために作成されました。2つの列を作成する理由はありません
allenwlee

2
非常に素晴らしい!特に注文もできるので、これは優れた答えです。賛成です!
bobmarksie

1
最も賛成された回答が承認された回答の前に表示されるさらにもう1つの例
ブライアンリスク

33

to_char 年と月を一気に引き出すことができます!

select to_char(date('2014-05-10'),'Mon-YY') as year_month; --'May-14'
select to_char(date('2014-05-10'),'YYYY-MM') as year_month; --'2014-05'

または上記のユーザーの例の場合:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;

6
テーブルに適切な量のデータがある場合は、これを実行しないことを強くお勧めします。これは、group byを実行する場合の方法よりもパフォーマンスが大幅に低下date_truncします。27万行のテーブルで便利なDBを実験すると、date_truncメソッドはTO_CHARの2倍の速度を超えます
Chris Clark

@ChrisClarkパフォーマンスが懸念される場合、date_truncを使用することは理にかなっていることに同意しますが、場合によっては、フォーマットされた日付文字列を使用することが望ましいです。また、パフォーマンスデータウェアハウスを使用している場合、追加の計算は取引ブレーカーではない可能性があります。 。たとえば、redshiftを使用してクイック分析レポートを実行していて、通常3秒かかる場合、おそらく6秒のクエリで問題ありません(ただし、レポートを実行している場合、追加の計算により、処理速度が遅くなる可能性があります。より大きな計算オーバーヘッドがあります)
mgoldwasser 2017

1
あなたはまだそれを行うことができます-クエリでグループを「ラップ」することにより、別のステップとしてフォーマットを行います。たとえば、SELECT to_char(d、 'YYYY-DD')FROM(SELECT date_trunc( 'month'、d)AS "d" FROM tbl)AS foo。両方の長所!
クリスクラーク

1
このソリューションはシンプルでエレガントです。私はそれが好きで、私の場合は十分に速いです。この回答ありがとうございます!
guettli

5

postgresのdate_part()関数を使用して結果を得る別の方法があります。

 SELECT date_part('month', txn_date) AS txn_month, date_part('year', txn_date) AS txn_year, sum(amount) as monthly_sum
     FROM yourtable
 GROUP BY date_part('month', txn_date)

ありがとう


1

bma回答は素晴らしいです!私はそれをActiveRecordsで使用しました、誰かがRailsでそれを必要とする場合です:

Model.find_by_sql(
  "SELECT TO_CHAR(created_at, 'Mon') AS month,
   EXTRACT(year from created_at) as year,
   SUM(desired_value) as desired_value
   FROM desired_table
   GROUP BY 1,2
   ORDER BY 1,2"
)

3
または、それを行うyourscopeorclass.group("extract(year from tablename.colname)")ことができ、それを3回一緒にチェーンして、年、月、日を取得できます
nruth

1

このチュートリアルの例Eをご覧ください -> https://www.postgresqltutorial.com/postgresql-group-by/

selectで作成した仮想属性の名前を呼び出す代わりに、GROUP BYで関数を呼び出す必要があります。私は上記のすべての回答が推奨することを行っていて、column 'year_month' does not existエラーが発生しました。

私のために働いたのは:

SELECT 
    date_trunc('month', created_at), 'MM/YYYY' AS month
FROM 
    "orders"  
GROUP BY 
    date_trunc('month', created_at)

0

Postgresにはいくつかのタイプのタイムスタンプがあります:

タイムゾーンなしのタイムスタンプ -(UTCタイムスタンプを保存するのが望ましい)多国籍データベースストレージで見つけます。この場合のクライアントは、各国のタイムゾーンオフセットを処理します。

タイムゾーン付きのタイムスタンプ -タイムゾーンのオフセットはすでにタイムスタンプに含まれています。

場合によっては、データベースはタイムゾーンを使用しませんが、ローカルタイムゾーンと夏時間に関してレコードをグループ化する必要があります(例:https : //www.timeanddate.com/time/zone/romania/bucharest

タイムゾーンを追加するには、この例を使用して、タイムゾーンオフセットを自分のものに置き換えることができます。

"your_date_column" at time zone '+03'

DSTに固有の+1サマータイムオフセットを追加するには、タイムスタンプがサマーDSTに該当するかどうかを確認する必要があります。これらの間隔は1日または2日で異なるため、月末のレコードに影響を与えない近似を使用します。この場合、この場合、毎年の正確な間隔は無視できます。

より正確なクエリを作成する必要がある場合は、条件を追加してより多くのケースを作成する必要があります。しかし、おおまかに言って、データベースでタイムゾーンのないタイムスタンプを見つけた場合、タイムゾーンとサマータイムに関して月ごとのデータ分割することでこれはうまく機能します。

SELECT 
    "id", "Product", "Sale",
    date_trunc('month', 
        CASE WHEN 
            Extract(month from t."date") > 03 AND
            Extract(day from t."date") > 26 AND
            Extract(hour from t."date") > 3 AND
            Extract(month from t."date") < 10 AND
            Extract(day from t."date") < 29 AND
            Extract(hour from t."date") < 4
        THEN 
            t."date" at time zone '+03' -- Romania TimeZone offset + DST
        ELSE
            t."date" at time zone '+02' -- Romania TimeZone offset 
        END) as "date"
FROM 
    public."Table" AS t
WHERE 1=1
    AND t."date" >= '01/07/2015 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
    AND t."date" < '01/07/2017 00:00:00'::TIMESTAMP WITHOUT TIME ZONE
GROUP BY date_trunc('month', 
    CASE WHEN 
        Extract(month from t."date") > 03 AND
        Extract(day from t."date") > 26 AND
        Extract(hour from t."date") > 3 AND
        Extract(month from t."date") < 10 AND
        Extract(day from t."date") < 29 AND
        Extract(hour from t."date") < 4
    THEN 
        t."date" at time zone '+03' -- Romania TimeZone offset + DST
    ELSE
        t."date" at time zone '+02' -- Romania TimeZone offset 
    END)
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.