Gruppieren Sie die Abfrageergebnisse nach Monat und Jahr in postgresql


156

Ich habe die folgende Datenbanktabelle auf einem Postgres-Server:

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

Ich möchte eine Abfrage erstellen, die SUMdie SalesSpalte angibt und die Ergebnisse nach Monat und Jahr wie folgt gruppiert:

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

Gibt es eine einfache Möglichkeit, das zu tun?

Antworten:


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

Auf Wunsch von Radu werde ich diese Frage erläutern:

to_char(date,'Mon') as mon, : konvertiert das Attribut "Datum" in das definierte Format der Kurzform des Monats.

extract(year from date) as yyyy : Mit der Funktion "Extrahieren" von Postgresql wird das JJJJ-Jahr aus dem Attribut "Datum" extrahiert.

sum("Sales") as "Sales" : Die Funktion SUM () addiert alle "Sales" -Werte und liefert einen Alias ​​für Groß- und Kleinschreibung, wobei die Groß- und Kleinschreibung durch doppelte Anführungszeichen beibehalten wird.

group by 1,2: Die GROUP BY-Funktion muss alle Spalten aus der SELECT-Liste enthalten, die nicht Teil des Aggregats sind (auch bekannt als alle Spalten, die nicht in den Funktionen SUM / AVG / MIN / MAX usw. enthalten sind). Dies teilt der Abfrage mit, dass SUM () für jede eindeutige Kombination von Spalten angewendet werden soll, in diesem Fall die Spalten für Monat und Jahr. Der Teil "1,2" ist eine Abkürzung anstelle der Verwendung der Spaltenaliasnamen, obwohl es wahrscheinlich am besten ist, die vollständigen Ausdrücke "to_char (...)" und "extract (...)" für die Lesbarkeit zu verwenden.


5
Ich denke nicht, dass es eine sehr gute Idee ist, eine Antwort ohne Erklärung zu geben, besonders für Anfänger. Sie hätten die Logik hinter Ihrer Antwort erklären sollen, vielleicht zumindest ein wenig (obwohl es für den Rest von uns einfach und unkompliziert erscheinen mag).
Radu Gheorghiu

1
@ BurakArslan Haben die Ergebnisse so ausgesehen, wie es das OP speziell verlangt hat?
BMA

2
@rogerdpack, die Ausgabe von date_truncist nicht genau das, was der Fragesteller wollte: select date_trunc('month', timestamp '2001-02-16 20:38:40')::date=>2001-02-01
pisaruk

2
Ich mag die Idee, date_truncin der group byKlausel zu verwenden.
Pisaruk

1
Mögliche Probleme mit dem Feld "Feld muss nach Klausel gruppiert sein" ... Es ist besser, OVER (PARTITION BY) zu verwenden.
Zon

316

Ich kann nicht glauben, dass die akzeptierte Antwort so viele positive Stimmen hat - es ist eine schreckliche Methode.

Hier ist der richtige Weg, dies mit date_trunc zu tun :

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

Es ist eine schlechte Praxis, aber Sie könnten vergeben werden, wenn Sie verwenden

 GROUP BY 1

in einer sehr einfachen Abfrage.

Sie können auch verwenden

 GROUP BY date_trunc('month', txn_date)

wenn Sie das Datum nicht auswählen möchten.


6
Leider entspricht die Ausgabe von date_truncnicht dem, was der Fragesteller erwartet hat: select date_trunc('month', timestamp '2001-02-16 20:38:40')=> 2001-02-01 00:00:00.
Pisaruk

3
Ich bin damit einverstanden, dass diese Methode besser ist. Ich bin mir nicht sicher, aber ich denke, es ist auch effizienter, da es nur eine Gruppierung anstelle von zwei gibt. Wenn Sie das Datum neu formatieren müssen, können Sie es anschließend mit den in anderen Antworten beschriebenen Methoden tun:to_char(date_trunc('month', txn_date), 'YY-Mon')
Paweł Sokołowski

1
Ja, die Anzahl der Stimmen für die akzeptierte Antwort ist umwerfend. date_truncwurde genau für diesen Zweck erstellt. Es gibt keinen Grund, zwei Spalten zu erstellen
Allenwlee

2
Sehr schön! Dies ist eine überlegene Antwort, zumal Sie auch bestellen können. Upvoted!
Bobmarksie

1
Ein weiteres Beispiel, bei dem die am besten bewertete Antwort vor der akzeptierten Antwort erscheinen sollte
Brian Risk,

33

to_char Tatsächlich können Sie das Jahr und den Monat auf einen Schlag herausholen!

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'

oder im Fall des obigen Benutzerbeispiels:

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

6
Ich würde dringend davon abraten, wenn Sie eine anständige Datenmenge in Ihrer Tabelle haben. Dies ist viel schlechter als die date_truncMethode, wenn die Gruppe von ausgeführt wird. Experimentieren mit einer Datenbank Ich habe es zur Hand, in einer Tabelle mit 270.000 Zeilen ist die date_trunc-Methode mehr als doppelt so schnell wie TO_CHAR
Chris Clark

@ChrisClark Wenn die Leistung ein Problem darstellt, stimme ich zu, dass die Verwendung von date_trunc möglicherweise sinnvoll ist. In einigen Fällen ist jedoch eine formatierte Datumszeichenfolge vorzuziehen. Wenn Sie ein performantes Data Warehouse verwenden, ist die zusätzliche Berechnung möglicherweise kein Deal Breaker . Wenn Sie beispielsweise einen Schnellanalysebericht mit Redshift ausführen und dies normalerweise 3 Sekunden dauert, ist eine 6-Sekunden-Abfrage wahrscheinlich in Ordnung (obwohl die zusätzliche Berechnung bei der Ausführung von Berichten die Leistung möglicherweise um einen geringeren Prozentsatz verlangsamt, weil es gibt einen größeren Rechenaufwand)
mgoldwasser

1
Sie können dies weiterhin tun - führen Sie die Formatierung einfach als separaten Schritt durch, indem Sie die Gruppe per Abfrage "einschließen". ZB SELECT to_char (d, 'JJJJ-TT') FROM (SELECT date_trunc ('month', d) AS "d" FROM tbl) AS foo. Beste aus beiden Welten!
Chris Clark

1
Diese Lösung ist einfach und elegant. Ich mag es und in meinem Fall ist es schnell genug. Vielen Dank für diese Antwort!
Guettli

5

Es gibt eine andere Möglichkeit, das Ergebnis mit der Funktion date_part () in postgres zu erzielen.

 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)

Vielen Dank


1

bma Antwort ist großartig! Ich habe es mit ActiveRecords verwendet. Hier ist es, wenn jemand es in Rails benötigt:

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
oder du kannst es tun yourscopeorclass.group("extract(year from tablename.colname)")und du kannst das dreimal
verketten

1

Schauen Sie sich Beispiel E dieses Tutorials an -> https://www.postgresqltutorial.com/postgresql-group-by/

Sie müssen die Funktion in Ihrer GROUP BY aufrufen, anstatt den Namen des virtuellen Attributs aufzurufen, das Sie bei select erstellt haben. Ich habe getan, was alle oben genannten Antworten empfohlen haben, und es wurde ein column 'year_month' does not existFehler angezeigt.

Was für mich funktioniert hat war:

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

0

Postgres hat nur wenige Arten von Zeitstempeln:

Zeitstempel ohne Zeitzone - (Vorzugsweise zum Speichern von UTC-Zeitstempeln) Sie finden ihn im multinationalen Datenbankspeicher. In diesem Fall kümmert sich der Kunde um den Zeitzonenversatz für jedes Land.

Zeitstempel mit Zeitzone - Der Zeitzonenversatz ist bereits im Zeitstempel enthalten.

In einigen Fällen verwendet Ihre Datenbank die Zeitzone nicht, Sie müssen jedoch Datensätze in Bezug auf die lokale Zeitzone und die Sommerzeit gruppieren (z. B. https://www.timeanddate.com/time/zone/romania/bucharest ).

Um eine Zeitzone hinzuzufügen, können Sie dieses Beispiel verwenden und den Zeitzonenversatz durch Ihren ersetzen.

"your_date_column" at time zone '+03'

Um den für die Sommerzeit spezifischen Versatz von +1 Sommerzeit hinzuzufügen, müssen Sie überprüfen, ob Ihr Zeitstempel in eine Sommerzeit fällt. Da diese Intervalle mit 1 oder 2 Tagen variieren, verwende ich eine Annäherung, die die Aufzeichnungen zum Monatsende nicht beeinflusst. In diesem Fall kann ich jedes Jahr das genaue Intervall ignorieren.

Wenn eine genauere Abfrage erstellt werden muss, müssen Sie Bedingungen hinzufügen, um weitere Fälle zu erstellen. In etwa funktioniert dies jedoch gut, wenn Sie Daten pro Monat in Bezug auf Zeitzone und Sommerzeit aufteilen, wenn Sie einen Zeitstempel ohne Zeitzone in Ihrer Datenbank finden:

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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.