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?
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é.
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.
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;
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
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"
)
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)