Sorgu sonuçlarını postgresql'de ay ve yıla göre gruplandırma


157

Postgres sunucusunda aşağıdaki veritabanı tablosu var:

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

Ben veren bir sorgu oluşturmak istiyoruz SUMait Salesay ve yıl şöyle tarafından sütuna ve gruplar sonuçları:

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

Bunu yapmanın basit bir yolu var mı?

Yanıtlar:


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

Radu'nun isteği üzerine, bu sorguyu açıklayacağım:

to_char(date,'Mon') as mon, : "tarih" niteliğini ayın kısa biçiminin tanımlı biçimine dönüştürür.

extract(year from date) as yyyy : Postgresql'in "extract" işlevi YYYY yılını "date" özelliğinden ayıklamak için kullanılır.

sum("Sales") as "Sales" : SUM () işlevi tüm "Sales" değerlerini toplar ve büyük / küçük harf duyarlı bir büyük / küçük harf kullanarak büyük / küçük harf duyarlı bir diğer ad sağlar.

group by 1,2: GROUP BY işlevi, SELECT listesinden toplamın parçası olmayan tüm sütunları içermelidir (diğer bir deyişle, SUM / AVG / MIN / MAX vb. İşlevlerinde olmayan tüm sütunlar). Bu, sorguyu SUM () yönteminin her bir benzersiz sütun birleşimi için uygulanması gerektiğini bildirir (bu durumda ay ve yıl sütunlarıdır). "1,2" kısmı, sütun takma adlarını kullanmak yerine bir kısayoldur, ancak okunabilirlik için tam "to_char (...)" ve "extract (...)" ifadelerini kullanmak en iyisidir.


5
Bir açıklama yapmadan cevap vermenin özellikle yeni başlayanlar için çok iyi bir fikir olduğunu düşünmüyorum. Cevabınızın arkasındaki mantığı, belki de en azından biraz açıklamalısınız (geri kalanımız için basit ve basit görünmesine rağmen).
Radu Gheorghiu

1
@BurakArslan Sonuçlar OP'nin özellikle istediği gibi mi görünüyordu?
bma

2
@rogerdpack, çıktısı date_trunctam olarak select date_trunc('month', timestamp '2001-02-16 20:38:40')::date2001-02-01
askerin

2
Ben kullanarak fikir gibi date_truncde group byfıkra.
pisaruk

1
Olası "alan yan tümcesine göre gruplandırılmalıdır" sorunları ... OVER (PARTITION BY) kullanmak daha iyidir.
Zon

318

Kabul edilen cevabın çok fazla oyu olduğuna inanamıyorum - bu korkunç bir yöntem.

Date_trunc ile bunu yapmanın doğru yolu :

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

Kötü uygulama ama kullanırsanız affedilebilir

 GROUP BY 1

çok basit bir sorguda.

Ayrıca kullanabilirsiniz

 GROUP BY date_trunc('month', txn_date)

tarihi seçmek istemiyorsanız.


6
ne yazık ki çıktı çıktı date_truncbeklediği gibi değil: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
pisaruk

4
Bu yöntemin daha iyi olduğuna katılıyorum. Emin değilim ama bence daha verimli, iki yerine sadece bir gruplama var. Tarihi yeniden biçimlendirmeniz gerekiyorsa, daha sonra diğer yanıtlarda açıklanan yöntemleri kullanarak bunu yapabilirsiniz:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski

1
evet, kabul edilen cevap için oy sayısı akıl almaz. date_truncbu amaç için yaratıldı. iki sütun oluşturmak için bir neden yoktur
allenwlee

2
Çok hoş! Bu, özellikle de sipariş verebileceğiniz için üstün bir cevaptır. Upvoted!
bobmarksie

1
En çok oylanan cevabın kabul edilen cevaptan önce görünmesi gereken başka bir örnek
Brian Risk

33

to_char aslında bir yıl ve ay bir düştü atmak sağlar!

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'

veya yukarıdaki kullanıcı örneğinde:

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

6
Tablonuzda yeterli miktarda veri varsa bunu yapmamanızı şiddetle tavsiye ederim. Bu , grubu gerçekleştirirken yöntemden çok daha kötü performans gösterir date_trunc. Bir DB üzerinde deneme Ben 270k satır içeren bir masada, date_trunc yöntemi TO_CHAR
Chris Clark

@ChrisClark performans bir endişe ise, date_trunc kullanmanın mantıklı olabileceğini kabul ediyorum, ancak bazı durumlarda biçimlendirilmiş bir tarih dizesine sahip olmak tercih edilir ve bir performans veri ambarı kullanıyorsanız, ek hesaplama bir anlaşma kırıcı olmayabilir . Örneğin, redshift kullanarak hızlı bir analiz raporu çalıştırıyorsanız ve genellikle 3 saniye sürüyorsa, 6 saniyelik bir sorgu muhtemelen tamamdır (ancak rapor çalıştırıyorsanız, ek hesaplama işleri daha küçük bir yüzde yavaşlatabilir, çünkü daha büyük bir hesaplama yükü vardır)
mgoldwasser

1
bunu yine de yapabilirsiniz - grubu sorguyu 'sararak' biçimlendirmeyi ayrı bir adım olarak yapmanız yeterlidir. Örneğin SELECT to_char (d, 'YYYY-GG') FROM (SELECT date_trunc ('ay', d) AS "d" Fbl tbl) AS foo. Her iki dünyanın en iyisi!
Chris Clark

1
Bu çözüm basit ve zariftir. Beğendim ve benim durumumda yeterince hızlı. Bu cevap için teşekkürler!
guettli

5

Postgres'de date_part () işlevini kullanarak sonuca ulaşmanın başka bir yolu daha vardır.

 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)

Teşekkürler


1

bma cevabı harika! ActiveRecords ile kullandım, işte herkes raylarda ihtiyacı varsa:

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
ya da yapabilirsiniz yourscopeorclass.group("extract(year from tablename.colname)")ve zincirleme yapabilirsiniz 3 kez birlikte yıl, ay, gün
nruth

1

Bu öğreticinin E örneğine bir göz atın -> https://www.postgresqltutorial.com/postgresql-group-by/

Seçtiğinizde oluşturduğunuz sanal özniteliğin adını çağırmak yerine GROUP BY'nuzdaki işlevi çağırmanız gerekir. Yukarıdaki tüm cevapların önerdiği şeyi yapıyordum vecolumn 'year_month' does not exist hata .

Benim için işe yarayan şuydu:

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

0

Postgres'in birkaç tür zaman damgası vardır:

saat dilimi olmadan zaman damgası - (UTC zaman damgalarını saklamak tercih edilir) Çok uluslu veritabanı depolama alanında bulabilirsiniz. Bu durumda müşteri, her ülke için saat dilimi dengelemesine bakacaktır.

saat dilimi ile zaman damgası - Zaman dilimi uzaklığı, zaman damgasına zaten dahil edilmiştir.

Bazı durumlarda, veritabanınız saat dilimini kullanmaz, ancak yine de kayıtları yerel saat dilimine ve Yaz Saati uygulamasına göre gruplandırmanız gerekir (örn. Https://www.timeanddate.com/time/zone/romania/bucharest )

Saat dilimi eklemek için bu örneği kullanabilir ve saat dilimi farkını kendinizinkilerle değiştirebilirsiniz.

"your_date_column" at time zone '+03'

DST'ye özgü +1 Yaz Saati ofsetini eklemek için zaman damganızın Yaz DST'sine girip girmediğini kontrol etmeniz gerekir. Bu aralıklar 1 veya 2 güne göre değiştiğinden, ay sonu kayıtlarını etkilemeyen bir yakınlaştırma kullanacağım, bu durumda her yıl tam aralığı görmezden gelebilirim.

Daha kesin bir sorgu oluşturulması gerekiyorsa, daha fazla vaka oluşturmak için koşullar eklemeniz gerekir. Ancak, kabaca, veritabanınızda saat dilimi olmadan zaman damgası bulduğunuzda , aylık olarak saat dilimi ve SummerTime'a göre verileri bölmede iyi çalışır :

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)
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.