MySQL: máximo de suma durante diferentes meses con lazos durante varios años

9

Esta pregunta se inspiró en éste [cerrada] y es virtualmente idéntica a ésta uno pero utilizando diferentes RDBMS de (PostgreSQL vs MySQL).

Supongamos que tengo una lista de tumores (estos datos se simulan a partir de datos reales):

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');

Desea averiguar qué tumor en particular fue más común en un mes determinado, ¡hasta ahora tan bueno!

Ahora, notará que para el mes 1 de 2017, hay un empate, por lo que no tiene ningún sentido elegir uno al azar y darlo como respuesta, por lo que los lazos deben incluirse, esto hace que el problema sea mucho más desafiante.

La respuesta correcta es:

  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

Una ventaja adicional sería que el nombre del mes aparezca como texto en lugar de como un entero.

Tengo una solución pero es bastante compleja: me gustaría saber si mi solución es óptima o no. ¡El violín de MySQL está aquí !

Vérace
fuente
Entiendo que esta es una pregunta específica de SQL, pero esto se puede hacer mucho más simple usando una base de datos de series de tiempo.
Banda
2
@Sash, se puede hacer mucho más simple con la mayoría de SQL DBMS, incluidas las versiones más recientes de MySQL / MariaDB. MySQL 5.6 no implementa mucha funcionalidad inventada después de SQL92.
Lennart

Respuestas:

4

Mi intento de resolver esto es el siguiente. Agradecería cualquier consejo sobre cómo podría mejorarse esta consulta:

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

¡Y da el resultado correcto, como se puede ver en el violín aquí !

Vérace
fuente
No creo que sea posible hacer mucho más simple. Una alternativa que viene a la mente es una selección secundaria en lugar de una combinación para obtener conteos que equivalen al conteo máximo para el año y la fecha. Posible, pero apenas más simple. Otra opción es usar variables para imitar el rango () sobre la partición por ...) y esperar que haya encontrado un nuevo trabajo para cuando la consulta deba cambiarse ;-)
Lennart
Esperemos que estemos en MySQL 8 antes de que ocurra algo así :-). ¡ Finalmente trae MySQL al siglo XXI! Analytics, CTE, REGEXP adecuados, se ve bien, a pesar de que no puede hacer INTERSECT y algunas otras quejas, pero parece que Oracle realmente ha puesto mucho en esta versión.
Vérace
0

Usando MySQL-8.0 y CTEs, primero creamos tmpcomo la agrupación de conteo agregado por año / mes / nature_of_illness, RANK()asigna valores idénticos al cmismo valor para que se tenga en cuenta el máximo duplicado:

 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
fuente