Kelompokkan hasil kueri berdasarkan bulan dan tahun di postgresql

157

Saya memiliki tabel database berikut di server 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

Saya ingin membuat kueri yang memberikan SUM dari Saleskolom dan kelompok hasil berdasarkan bulan dan tahun sebagai berikut:

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

Apakah ada cara sederhana untuk melakukan itu?

Frechi
sumber

Jawaban:

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

Atas permintaan Radu, saya akan menjelaskan pertanyaan itu:

to_char(date,'Mon') as mon, : mengonversi atribut "date" ke dalam format yang ditentukan dari bentuk pendek bulan.

extract(year from date) as yyyy : Fungsi "ekstrak" Postgresql digunakan untuk mengekstrak tahun YYYY dari atribut "date".

sum("Sales") as "Sales" : Fungsi SUM () menambahkan semua nilai "Penjualan", dan memasok alias case-sensitive, dengan sensitivitas case dipertahankan dengan menggunakan tanda kutip ganda.

group by 1,2: Fungsi GROUP BY harus berisi semua kolom dari daftar SELECT yang bukan bagian dari agregat (alias, semua kolom tidak di dalam fungsi SUM / AVG / MIN / MAX dll). Ini memberi tahu kueri bahwa SUM () harus diterapkan untuk setiap kombinasi kolom yang unik, yang dalam hal ini adalah kolom bulan dan tahun. Bagian "1,2" adalah singkatan daripada menggunakan alias kolom, meskipun mungkin lebih baik menggunakan ekspresi "to_char (...)" dan "ekstrak (...)" yang lengkap untuk keterbacaan.

bma
sumber
5
Saya tidak berpikir memberikan jawaban tanpa penjelasan adalah ide yang sangat bagus, terutama untuk pemula. Anda harus menjelaskan logika di balik jawaban Anda, mungkin setidaknya sedikit (meskipun mungkin tampak sederhana dan mudah bagi kita semua).
Radu Gheorghiu
1
@ BurakArslan Apakah hasilnya terlihat seperti apa yang diminta OP secara khusus?
Bma
2
@rogerdpack, output dari date_trunctidak persis apa yang diinginkan penanya: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk
2
Saya suka ide menggunakan date_truncdalam group byklausa.
pisaruk
1
Kemungkinan "bidang harus dikelompokkan berdasarkan klausa" ... Lebih baik menggunakan OVER (PARTITION BY).
Zon
318

Saya tidak percaya jawaban yang diterima memiliki begitu banyak upvotes - ini adalah metode yang mengerikan.

Inilah cara yang benar untuk melakukannya, dengan date_trunc :

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

Ini praktik yang buruk tetapi Anda mungkin dimaafkan jika Anda menggunakannya

 GROUP BY 1

dalam permintaan yang sangat sederhana.

Anda juga bisa menggunakan

 GROUP BY date_trunc('month', txn_date)

jika Anda tidak ingin memilih tanggal.

Burak Arslan
sumber
6
sayangnya output dari date_truncapa yang diharapkan penanya: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk
4
Saya setuju bahwa metode ini lebih baik. Saya tidak yakin tetapi saya pikir ini lebih efisien juga, karena hanya ada satu pengelompokan bukan dua. Jika Anda perlu memformat ulang tanggal Anda dapat melakukannya setelah itu menggunakan metode yang dijelaskan dalam jawaban lain:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski
1
ya, jumlah suara untuk jawaban yang diterima sangat membingungkan. date_trunctelah dibuat untuk tujuan yang tepat ini. tidak ada alasan untuk membuat dua kolom
allenwlee
2
Sangat bagus! Ini adalah jawaban yang unggul, terutama karena Anda dapat memesan juga. Terpilih!
bobmarksie
1
Contoh lain di mana jawaban yang paling banyak dipilih harus muncul sebelum jawaban yang diterima
Brian Risk
33

to_char sebenarnya memungkinkan Anda menarik Tahun dan bulan dalam satu gerakan!

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'

atau dalam kasus contoh pengguna di atas:

select to_char(date,'YY-Mon') as year_month
       sum("Sales") as "Sales"
from some_table
group by 1;
mgoldwasser
sumber
6
Saya akan sangat menyarankan untuk tidak melakukan ini jika Anda memiliki jumlah data yang layak di meja Anda. Ini melakukan jauh lebih buruk daripada date_truncmetode ketika melakukan grup oleh. Bereksperimen dengan DB yang saya miliki, di atas meja dengan baris 270k, metode date_trunc lebih dari dua kali kecepatan TO_CHAR
Chris Clark
@ChrisClark jika kinerja menjadi masalah, saya setuju bahwa mungkin masuk akal untuk menggunakan date_trunc, tetapi dalam beberapa kasus memiliki string tanggal yang diformat lebih disukai, dan jika Anda menggunakan gudang data performan perhitungan tambahan mungkin bukan pemecah kesepakatan . Misalnya, jika Anda menjalankan laporan analitik cepat menggunakan pergeseran merah, dan biasanya membutuhkan waktu 3 detik, kueri 6 detik mungkin baik-baik saja (walaupun, jika Anda menjalankan laporan, perhitungan tambahan mungkin memperlambat segalanya dengan persentase yang lebih kecil, karena ada overhead komputasi yang lebih besar)
mgoldwasser
1
Anda masih dapat melakukannya - cukup lakukan pemformatan sebagai langkah terpisah dengan 'membungkus' grup dengan kueri. Misalnya SELECT to_char (d, 'YYYY-DD') DARI (SELECT date_trunc ('bulan', d) SEBAGAI "d" DARI tbl) SEBAGAI foo. Terbaik dari kedua dunia!
Chris Clark
1
Solusi ini sederhana dan elegan. Saya suka dan dalam kasus saya cukup cepat. Terima kasih atas jawaban ini!
guettli
5

Ada cara lain untuk mencapai hasil menggunakan fungsi date_part () di postgres.

 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)

Terima kasih

Nayan
sumber
1

jawaban bma luar biasa! Saya telah menggunakannya dengan ActiveRecords, ini dia kalau ada yang membutuhkannya di 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"
)
mekdigital
sumber
3
atau Anda dapat melakukannya yourscopeorclass.group("extract(year from tablename.colname)")dan Anda dapat
mengaitkannya
1

Lihatlah contoh E tutorial ini -> https://www.postgresqltutorial.com/postgresql-group-by/

Anda perlu memanggil fungsi pada GROUP BY Anda alih-alih memanggil nama atribut virtual yang Anda buat pada pilih. Saya melakukan apa yang direkomendasikan semua jawaban di atas dan saya mendapatkan column 'year_month' does not existkesalahan.

Apa yang berhasil untuk saya adalah:

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

Postgres memiliki beberapa jenis cap waktu:

timestamp tanpa zona waktu - (Lebih disukai menyimpan cap waktu UTC) Anda menemukannya di penyimpanan basis data multinasional. Klien dalam hal ini akan menangani penggantian zona waktu untuk masing-masing negara.

timestamp dengan zona waktu - Offset zona waktu sudah termasuk dalam timestamp.

Dalam beberapa kasus, basis data Anda tidak menggunakan zona waktu tetapi Anda masih perlu mengelompokkan catatan sehubungan dengan zona waktu lokal dan Waktu Musim Panas (mis. Https://www.timeanddate.com/time/zone/romania/bucharest )

Untuk menambahkan zona waktu, Anda dapat menggunakan contoh ini dan mengganti zona waktu dengan milik Anda.

"your_date_column" at time zone '+03'

Untuk menambahkan +1 Waktu Musim Panas khusus untuk DST Anda perlu memeriksa apakah cap waktu Anda jatuh ke dalam Summer DST. Karena interval tersebut bervariasi dengan 1 atau 2 hari, saya akan menggunakan aproximation yang tidak mempengaruhi catatan akhir bulan, jadi dalam hal ini saya dapat mengabaikan interval tepat setiap tahun.

Jika kueri yang lebih tepat harus dibuat, maka Anda harus menambahkan kondisi untuk membuat lebih banyak kasus. Namun secara kasar, ini akan berfungsi dengan baik dalam membagi data per bulan sehubungan dengan zona waktu dan SummerTime ketika Anda menemukan cap waktu tanpa zona waktu di basis data Anda:

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)
profimedica
sumber