J'ai une table comme cette deux
Tableau A P>
2019-05-31 33488.69 109.127800 2019-06-30 263.690 1248932.227800 2019-07-31 274.690 131.827800 2019-08-31 627.690 13.687800 2019-09-30 1533.370000 08.347800 2019-10-31 1444.370000 01.327800 2019-11-30 5448.370000 247.227800 2019-12-31 61971.370000 016.990450 2020-01-31 19550.370000 2535.185450 2020-02-29 986.370000 405.123300 2020-03-31 1152.370000 26.793300 2020-04-30 9404.370000 11894.683300 2020-05-31 3404.370000 17894.683300
3 Réponses :
J'utiliserais l'agrégation conditionnelle et pré-agrégerais les totaux mensuels en une seule passe, au lieu de faire vingt-six passages individuels à plusieurs reprises sur les mêmes données.
Je commencerais par quelque chose comme ceci: p >
SELECT d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY AS last_date
, SUM(IFNULL(t.tot_type_5,0)) AS rt_type_5
, SUM(IFNULL(t.tot_type_7,0)) AS rt_type_7
FROM ( -- first day of next month
SELECT DATE(NOW()) + INTERVAL -DAY(DATE(NOW()))+1 DAY + INTERVAL 1 MONTH AS dt
) d
CROSS
JOIN ( -- thirteen integers, integers 0 thru 12
SELECT 0 AS n
UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4
UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8
UNION ALL SELECT 9 UNION ALL SELECT 10 UNION ALL SELECT 11 UNION ALL SELECT 12
) i
LEFT
JOIN ( -- totals by month
SELECT CASE WHEN A.date < DATE(NOW()) + INTERVAL -14 MONTH
THEN LAST_DAY( DATE(NOW()) + INTERVAL -14 MONTH )
ELSE LAST_DAY( A.date )
END AS _month_end
, SUM(IF( B.type = 5 , B.amount , NULL)) AS tot_type_5
, SUM(IF( B.type = 7 , B.amount , NULL)) AS tot_type_7
FROM A
JOIN B
ON B.id = A.B_id
WHERE B.type IN (5,7)
GROUP
BY _month_end
) t
ON t._month_end < d.dt
GROUP BY d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY
ORDER BY d.dt + INTERVAL -i.n MONTH + INTERVAL -1 DAY DESC
(le montant de la colonne n'est pas qualifié dans la requête d'origine, il suffit donc de deviner ici de quelle table provient. ajustez si nécessaire. La meilleure pratique consiste à qualifier tout références de colonnes.
Cela nous donne les sous-totaux pour chaque mois, en un seul passage par A et B.
Nous pouvons tester et ajuster cette requête.
Ensuite, nous pouvons incorporer cela comme une vue en ligne dans une requête externe qui additionne ces totaux mensuels (je ferais une jointure externe, juste au cas où des lignes manquent, semez nous ne finirons pas par omettre des lignes.) p>
Quelque chose comme ceci:
SELECT CASE WHEN A.date < DATE(NOW()) + INTERVAL -14 MONTH
THEN LAST_DAY( DATE(NOW()) + INTERVAL -14 MONTH )
ELSE LAST_DAY( A.date )
END AS _month_end
, SUM(IF( B.type = 5 , B.amount , NULL)) AS tot_type_5
, SUM(IF( B.type = 7 , B.amount , NULL)) AS tot_type_7
FROM A
JOIN B
ON B.id = A.B_id
WHERE B.type IN (5,7)
GROUP
BY _month_end
La conception est destinée à faire un coup à travers l'ensemble A JOIN B . Nous nous nous attendons à récupérer environ 14 lignes. Et nous effectuons une semi-jointure, en dupliquant plusieurs fois les mois les plus anciens, soit environ 14 x 13/2 = 91 lignes, qui se réduisent en 13 lignes.
Le gros rocher en termes de performances va matérialiser cette requête de vue en ligne.
Hé! Merci de m'avoir si bien expliqué. Je pense que Group By _month_end ne donne pas le résultat que je recherchais .. je voulais SUM jusqu'au mois dernier des 12 derniers mois .. La réponse de Thorsten Kettner a fonctionné le mieux pour moi.
Yah, la requête de vue en ligne renvoie les totaux par mois. Ce n'est qu'une partie de celui-ci. C'est la condition dans la semi-jointure de la requête externe ( t._month_end
Voici comment j'aborderais probablement cela dans MySQL 8 avec SUM OVER :
La requête:
with months (date) as ( select last_day(current_date - interval 1 month) union all select last_day(current_date - interval 2 month) union all select last_day(current_date - interval 3 month) union all select last_day(current_date - interval 4 month) union all select last_day(current_date - interval 5 month) union all select last_day(current_date - interval 6 month) union all select last_day(current_date - interval 7 month) union all select last_day(current_date - interval 8 month) union all select last_day(current_date - interval 9 month) union all select last_day(current_date - interval 10 month) union all select last_day(current_date - interval 11 month) union all select last_day(current_date - interval 12 month) ) , data (date, amount, type) as ( select last_day(a.date), a.amount, b.type from a join b on b.id = a.b_id where b.type in (5, 7) union all select date, null, null from months ) select date, sum(sum(case when type = 5 then amount end)) over (order by date) as t5, sum(sum(case when type = 7 then amount end)) over (order by date) as t7 from data group by date order by date limit 12;
Démo: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=ddeb3ab3e086bfc182f0503615fba74b
Je ne sais pas si c'est plus rapide que votre propre requête ou non. Essayez-le. (Vous obtiendrez ma requête beaucoup plus rapidement en ajoutant une colonne générée pour last_day (date) à votre table et en l'utilisant. Si vous en avez besoin souvent, cela peut être une option.)
Merci pour votre réponse. Cela a parfaitement fonctionné ... n'était pas familier avec les fonctions de fenêtre. C'est aussi beaucoup plus rapide. Merci.
Vous obtenez des réponses compliquées. Je pense que c'est plus facile. Commencez par savoir que nous pouvons facilement additionner pour chaque mois:
SET @running5 := 0;
SET @running7 := 0;
SELECT
LAST_DAY(CONCAT(year,'-',month,'-',1)),
(@running5 := @running5 + (CASE WHEN type=5 THEN monthtotal ELSE 0 END)) as running5,
(@running7 := @running7 + (CASE WHEN type=7 THEN monthtotal ELSE 0 END)) as running7
FROM
(SELECT SUM(amount) as monthtotal,type,MONTH(date) as month,YEAR(date) as year FROM A LEFT JOIN B on A.B_id=B.id GROUP BY type,month,year) AS totals
ORDER BY year,month
À partir de ces données, nous pouvons utiliser une variable pour obtenir le total cumulé. Il est préférable de le faire en initialisant la variable, mais ce n'est pas nécessaire. Nous pouvons obtenir les données nécessaires comme ceci
SET @running := 0; SELECT (@running := @running + monthtotal) as running, type, LAST_DAY(CONCAT(year,'-',month,'-',1)) FROM (SELECT SUM(amount) as monthtotal,type,MONTH(date) as month,YEAR(date) as year FROM A LEFT JOIN B on A.B_id=B.id GROUP BY type,month,year) AS totals ORDER BY year,month
Vous devez vraiment avoir un connecteur qui prend en charge plusieurs instructions, ou faire plusieurs appels pour initialiser la variable. Bien que vous puissiez annuler la vérification de la variable et la valeur par défaut à 0, vous rencontrez toujours un problème si vous exécutez la requête une deuxième fois.
Dernière chose, si vous voulez vraiment que les types soient additionnés séparément: p >
SELECT SUM(amount) as monthtotal,
type,
MONTH(date) as month,
YEAR(date) as year
FROM A LEFT JOIN B on A.B_id=B.id
GROUP BY type,month,year
Nous n'affichons toujours pas les mois sans données. Je ne suis pas sûr que ce soit une exigence. Mais cela ne devrait nécessiter qu'un seul passage de la table A.
Assurez-vous également que l'identifiant de la table B est indexé.
Il est bon que cette réponse note que le modèle de requête ne garantit pas qu'une ligne pour chaque mois sera renvoyée. Et oui, c'est une noix plus facile à casser. Nous devons également noter que le Manuel de référence de MySQL donne un avertissement spécifique sur l'affectation et l'évaluation des variables définies par l'utilisateur dans la même instruction; La documentation MySQL indique que le comportement n'est pas défini, ce qui signifie que le comportement n'est pas garanti, mais nous observons un comportement cohérent avec des requêtes soigneusement conçues.
Comment votre table est-elle indexée? Veuillez nous montrer le schéma. À tout le moins, vous devriez avoir un index sur toutes ces colonnes.
l'index de A a la date, l'id, B_id et l'index de B inclut l'id et le type les deux.
Quelle version de MySQL utilisez-vous? Ces totaux en cours d'exécution sont beaucoup plus faciles à obtenir avec les fonctions de fenêtre, disponibles à partir de MySQL 8.
Vos lignes de résultats semblent un peu surprenantes. Je m'attendais à ce que les valeurs augmentent chaque mois, mais il semble y avoir des mois avec des soldes négatifs réduisant les totaux en cours. Alors, est-ce que peut-il manquer des mois dans votre tableau? (Sinon, la requête de ma réponse deviendrait beaucoup plus simple sans qu'il soit nécessaire de créer des lignes de mois.)
pouvez-vous faire dans une procédure stockée? L'utilisation d'une variable utilisateur pour accumuler un total cumulé résoudrait ce problème.
il est très peu probable qu'il y ait des mois manquants dans les données .. mais oui, il a des valeurs négatives.