web-dev-qa-db-fra.com

Résultats de la requête de groupe par mois et par an dans postgresql

J'ai la table de base de données suivante sur un serveur 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

Je souhaite créer une requête qui donne la SUM de la colonne Sales et regroupe les résultats par mois et par an de la manière suivante:

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

Y a-t-il un moyen simple de faire ça?

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

À la demande de Radu, je vais expliquer cette requête:

to_char(date,'Mon') as mon,: convertit l'attribut "date" dans le format défini pour la forme abrégée du mois.

extract(year from date) as yyyy: la fonction "extract" de Postgresql est utilisée pour extraire l'année AAAA de l'attribut "date".

sum("Sales") as "Sales": la fonction SUM () additionne toutes les valeurs "Sales" et fournit un alias respectant la casse, avec la sensibilité à la casse conservée à l'aide de guillemets doubles.

group by 1,2: La fonction GROUP BY doit contenir toutes les colonnes de la liste SELECT qui ne font pas partie de l'agrégat (autrement dit, toutes les colonnes ne font pas partie des fonctions SUM/AVG/MIN/MAX, etc.). Cela indique à la requête que la somme () () doit être appliquée pour chaque combinaison unique de colonnes, qui dans ce cas sont les colonnes mois et année. La partie "1,2" est un raccourci au lieu d'utiliser les alias de colonnes, bien qu'il soit probablement préférable d'utiliser les expressions complètes "to_char (...)" et "extract (...)" pour des raisons de lisibilité.

180
bma

Je ne peux pas croire que la réponse acceptée a autant de votes positifs - c'est une méthode horrible.

Voici la bonne façon de le faire, avec date_trunc :

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

C'est une mauvaise pratique, mais vous pourriez être pardonné si vous utilisez

 GROUP BY 1

dans une requête très simple.

Vous pouvez aussi utiliser

 GROUP BY date_trunc('month', txn_date)

si vous ne voulez pas choisir la date.

249
Burak Arslan

to_char vous permet de sortir l'année et le mois d'un seul coup!

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'

ou dans le cas de l'exemple utilisateur ci-dessus:

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

Il existe un autre moyen d'obtenir le résultat à l'aide de la fonction date_part () de 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)

Merci

3
Nayan

bma la réponse est géniale! Je l'ai utilisé avec ActiveRecords, le voici si quelqu'un en a besoin dans 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"
)
1
mekdigital

Postgres a peu de types d'horodatage:

horodatage sans fuseau horaire Préférable de stocker les horodatages UTC) Vous le trouvez dans une base de données multinationale. Dans ce cas, le client s’occupera du décalage horaire pour chaque pays.

horodatage avec fuseau horaire - Le décalage de fuseau horaire est déjà inclus dans l'horodatage.

Dans certains cas, votre base de données n'utilise pas le fuseau horaire mais vous devez quand même regrouper les enregistrements en respectant le fuseau horaire local et l'heure d'été (par exemple, --- (https://www.timeanddate.com/time/zone/romania/bucharest). )

Pour ajouter un fuseau horaire, vous pouvez utiliser cet exemple et remplacer le décalage de fuseau horaire par le vôtre.

"your_date_column" at time zone '+03'

Pour ajouter le décalage +1 de l'heure d'été spécifique à l'heure d'été, vous devez vérifier si votre horodatage tombe dans une heure d'été. Comme ces intervalles varient avec 1 ou 2 jours, je vais utiliser une approximation qui n'affecte pas les enregistrements de fin de mois, donc dans ce cas, je peux ignorer chaque intervalle exact d'année.

Si une requête plus précise doit être créée, vous devez ajouter des conditions pour créer plus de cas. Mais grosso modo, cela fonctionnera très bien si vous divisez des données par mois en respectant le fuseau horaire et l’heure estivale lorsque vous trouvez un horodatage sans fuseau horaire dans votre base de données:

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