MySQL - maximum de somme sur différents mois avec des liens sur plusieurs années

9

Cette question a été inspirée par celle-ci [fermée] et est pratiquement identique à celle- ci mais en utilisant différents SGBDR (PostgreSQL vs. MySQL).

Supposons que j'ai une liste de tumeurs (ces données sont simulées à partir de données réelles):

CREATE table illness (nature_of_illness VARCHAR(25), created_at DATETIME);

INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Cervix', '2018-01-03 15:45:40');
INSERT INTO illness VALUES ('Lung',   '2018-01-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2018-02-03 17:50:32');
INSERT INTO illness VALUES ('Cervix', '2018-02-03 17:50:32');
-- 2017, with 1 Cervix and Lung each for the month of Jan - tie!
INSERT INTO illness VALUES ('Cervix', '2017-01-03 15:45:40');
INSERT INTO illness VALUES ('Lung',   '2017-01-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Lung',   '2017-02-03 17:50:32');
INSERT INTO illness VALUES ('Cervix', '2017-02-03 17:50:32');

Vous voulez savoir quelle tumeur particulière était la plus courante au cours d'un mois donné - jusqu'ici tout va bien!

Maintenant, vous remarquerez que pour le mois 1 de 2017, il y a une égalité - il n'est donc pas logique d'en choisir une au hasard et de donner cela comme réponse - donc des égalités doivent être incluses - cela rend le problème beaucoup plus difficile.

La bonne réponse est:

  Year    Month  Tumour count      Type
  2017        1             1    Cervix  -- note tie
  2017        1             1      Lung  --   "   "
  2017        2             3      Lung
  2018        1             5    Cervix
  2018        2             3      Lung

Un autre avantage serait que le nom du mois apparaisse sous forme de texte plutôt que d'entier.

J'ai une solution mais elle est assez complexe - j'aimerais savoir si ma solution est optimale ou non. Le violon MySQL est !

Vérace
la source
Je comprends que c'est une question spécifique à SQL, mais cela peut être rendu beaucoup plus simple en utilisant une base de données de séries chronologiques.
Sash
2
@Sash, cela peut être fait beaucoup plus simplement avec la plupart des SGBD SQL, y compris les nouvelles versions de MySQL / MariaDB. MySQL 5.6 n'implémente pas beaucoup de fonctionnalités inventées après SQL92.
Lennart

Réponses:

4

Ma tentative pour résoudre ce problème est la suivante. J'apprécierais tout conseil sur la façon dont cette requête pourrait être améliorée:

SELECT 
  t3.c_year AS "Year",
  t3.c_month AS "Month", 
  t3.il_mc AS  "Tumour count", 
  t4.ill_nat AS "Type" FROM
(
  SELECT c_year, c_month, il_mc FROM
  (
    SELECT  
    c_year, 
    c_month,
    MAX(month_count) AS il_mc
  FROM
    (
      SELECT nature_of_illness as illness,
        EXTRACT(YEAR  FROM created_at) AS c_year,
        EXTRACT(MONTH FROM created_at) AS c_month,
        COUNT(EXTRACT(MONTH FROM created_at)) AS month_count
      FROM illness
      GROUP BY illness, c_year, c_month
      ORDER BY c_year, c_month
    ) AS t1
  GROUP BY c_year, c_month
  ) AS t2
) AS t3
JOIN
(
SELECT 
  EXTRACT(YEAR FROM created_at) AS t_year, 
  EXTRACT(MONTH FROM created_at) AS t_month,  
  nature_of_illness AS ill_nat, 
  COUNT(nature_of_illness) AS ill_cnt
FROM illness
GROUP BY t_year, t_month, nature_of_illness
ORDER BY t_year, t_month, nature_of_illness
) AS t4
ON t3.c_year = t4.t_year
AND t3.c_month = t4.t_month
AND t3.il_mc = t4.ill_cnt

Et cela donne le résultat correct, comme on peut le voir ici au violon !

Vérace
la source
Je ne pense pas qu'il soit possible de faire beaucoup plus simple. Une alternative qui vient à l'esprit est une sous-sélection au lieu d'une jointure pour obtenir des nombres égaux au nombre maximum pour l'année et la date. Possible, mais à peine plus simple. Une autre option consiste à utiliser des variables pour imiter le classement () par rapport à la partition d'ici ...) et espérons que vous avez trouvé un nouvel emploi au moment où la requête doit être modifiée ;-)
Lennart
J'espère que nous serons sur MySQL 8 avant que quelque chose comme ça ne se produise :-). Il fait enfin entrer MySQL dans le 21e siècle! Analytics, CTE, REGEXP appropriés - a l'air bien - même si vous ne pouvez pas faire d'INTERSECT et quelques autres reproches, mais il semble qu'Oracle ait vraiment mis beaucoup dans cette version.
Vérace
0

À l'aide de MySQL-8.0 et des CTE, nous créons d'abord tmple groupe de comptage agrégé par année / mois / nature_of_illness, RANK()attribue des valeurs identiques à cde la même valeur afin que le max en double soit pris en compte:

 SELECT y as 'Year',mon as 'Month',c as 'Tumor Count', nature_of_illness as 'Type'
 FROM (
   WITH tmp AS ( 
    SELECT YEAR(created_at) as y, MONTH(created_at) as mon, COUNT(*) as c, nature_of_illness
    FROM illness
    GROUP BY y, mon, nature_of_illness
   )
   SELECT y, mon, c, nature_of_illness,
   RANK() OVER (PARTITION BY y, mon ORDER BY c DESC) as `rank`
   FROM tmp
 ) AS tmp2 
WHERE `rank` = 1
ORDER BY y, mon
danblack
la source