Mais comment grouper des lignes d’une table MySQL selon une période de date ?

Publié dans SQL | Marqué avec ,
Share

Mettons que j’ai une table MySQL avec une liste de commandes datées : id, date, amount

iddateamount
12024-03-20100
22024-09-0350
32025-02-2825
42025-05-0225

Pour obtenir le montant total des commandes réalisé par année, je peux effectuer la requête suivante :

SELECT YEAR(date) as date_year, SUM(amount) as total
FROM mytable
GROUP BY YEAR(date)

Le YEAR(date) permet de récupérer uniquement la partie « année » de la date en question. J’aurai aussi pu utiliser EXTRACT(YEAR FROM date).

Cela va me donner :

date_yeartotal
2024150
202550

Mais comment faire pour obtenir le total des commandes réalisé entre le 1 juillet et le 30 juin de l’année suivante ? Bref, comment grouper selon une période de date ?

J’ai l’impression que c’est aussi la question posée ici. La méthode GROUP BY ne permettant que de filtrer sur un nom de colonne, et non sur une condition, ce n’est pas simple. HAVING ne me sauvera pas, HAVING c’est « comme » un WHERE mais qui s’applique après le SELECT (i.e. il « connaît » les alias AS que l’on a créé) et après la résolution des functions d’agrégats (i.e. il sait faire une condition sur une somme, comme illustré ici).

Mais quand bien même GROUP BY fonctionnerait avec une condition, en y réfléchissant, je ne saurai pas tellement comment exprimer ce que je recherche comme une condition.

A vrai dire, je ne sais pas (encore) répondre à cette question. Mais en attendant, j’ai appris quelques petites choses sur SQL (i.e. MySQL) au passage et les voici.

Bon, j’ai envoyé cet article à ChatGPT et il a trouvé. Fichtre. Je n’aurai pas compris sa réponse si je n’avais pas cherché un peu au préalable. Mais quand même, c’est me fiche un coup à chaque fois. D’autant plus qu’il a compris que je voulais faire un groupement par année fiscale, ce dont je n’avais même pas parlé dans cet article ! (histoire de rire, je lui ai demandé de relire cette nouvelle version de l’article, il me félicite le petit malin, et il a même trouvé une coquille)

L’astuce ici, c’est que dans une condition CASE WHEN on peut générer une nouvelle valeur. On fait donc une condition :

  • Si le mois est supérieur au mois de janvier, alors on créé un item « année en cours »-« année en cours + 1 »
  • Sinon on créé « année en cours – 1 « -« année en cours »

Et il n’y a plus qu’à grouper sur ce résultat. Ce qui donne, avec un petit rollup pour avoir le somme total de toutes les années :

SELECT 
    CASE
      WHEN MONTH(date) >= 7 THEN CONCAT(YEAR(date), '-', YEAR(date) + 1)
      ELSE CONCAT(YEAR(date) - 1, '-', YEAR(date))
    END AS date_year_fiscal,
    SUM(amount) as total
FROM mytable
GROUP BY date_year_fiscal WITH ROLLUP

Ce qui donne bien :

date_year_fiscaltotal
2023-2024100
2024-2025100
NULL200

Et voici maintenant les astuces que j’avais trouvé entre temps. Je n’étais pas si loin.

CASE WHEN

Avec la fonction QUARTER(date) je peux récupérer le numéro du trimestre d’une date. J’ai découvert CASE WHEN qui permet d’exprimer une condition de type switch, et donc de faire un total par semestre :

SELECT
   YEAR(date) as date_year
   (CASE WHEN QUARTER(date) IN (1,2) THEN 1 ELSE 2 END) as date_semester,
   SUM(amount) as total
FROM mytable
GROUP BY YEAR(date), (CASE WHEN QUARTER(date) IN (1,2) THEN 1 ELSE 2 END)

J’obtiens alors le résultat suivant, que je peux re-traiter en dehors de SQL pour sommer les 2 morceaux de semestre qui m’intéresse. Ce n’est pas l’idéal, mais c’est déjà pas mal.

date_yeardate_semestertotal
20241100
2024250
2025150

Avec CONCAT on peut aussi facilement rajouter une colonne qui indique le semestre de chaque année :
CONCAT(YEAR(date), '_', (CASE WHEN QUARTER(date) IN (1,2) THEN 1 ELSE 2 END)) as date_year_semester

Documentation et exemple d’utilisation :

WITH ROLLUP

En rajoutant WITH ROLLUP à la clause GROUP BY, MySQL va s’occuper de calculer aussi la somme en groupant uniquement par la colonne YEAR(date), puis la somme totale de tout le data set.

SELECT
   YEAR(date) as date_year
   (CASE WHEN QUARTER(date) IN (1,2) THEN 1 ELSE 2 END) as date_semester,
   SUM(amount) as total
FROM mytable
GROUP BY YEAR(date), (CASE WHEN QUARTER(date) IN (1,2) THEN 1 ELSE 2 END) WITH ROLLUP

Ce qui donne le résultat suivant. Sympa. Ce n’est pas ce que je voulais, mais auparavant pour avoir ce genre de résultat, je faisais un retraitement en dehors de SQL :

date_yeardate_semestertotal
20241100
2024250
2024NULL150
2025150
2025NULL50
NULLNULL200

Plus d’informations sur rollup ici et surtout cette documentation de MySQL qui explique comment renommer ces NULL disgracieux (et éviter de les confondre de vrais lignes contenant un NULL)

Autres pistes

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *