MySQL - máximo de soma em meses diferentes com vínculos por vários anos


9

Esta questão foi inspirado por este um [fechada] e é virtualmente idêntica a esta uma mas utilizando diferentes RDBMS de (PostgreSQL vs. MySQL).

Suponha que eu tenha uma lista de tumores (esses dados são simulados a partir de dados reais):

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

Você quer descobrir qual tumor em particular foi o mais comum em um determinado mês - até agora tudo bem!

Agora, você notará que, no primeiro mês de 2017, há um empate - portanto, não faz sentido escolher um aleatoriamente e dar isso como resposta - para que os empates tenham que ser incluídos - isso torna o problema muito mais desafiador.

A resposta correta é:

  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

Um bônus adicional seria fazer com que o nome do mês aparecesse como texto em vez de um número inteiro.

Eu tenho uma solução, mas é bastante complexa - eu gostaria de saber se minha solução é ótima ou não. O violino do MySQL está aqui !


Entendo que essa é uma pergunta específica do SQL, mas isso pode ser muito mais simples usando um banco de dados de séries temporais.
Sash

2
@Sash, isso pode ser feito de maneira muito mais simples com a maioria dos DBMS SQL, incluindo versões mais recentes do MySQL / MariaDB. O MySQL 5.6 não implementa muita funcionalidade inventada após o SQL92.
Lennart

Respostas:


4

Minha tentativa de resolver isso é a seguinte. Eu gostaria de receber algum conselho sobre como essa consulta poderia ser melhorada:

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

E dá o resultado correto, como pode ser visto no violino aqui !


Eu não acho que é possível fazer muito mais simples. Uma alternativa que vem à mente é uma sub-seleção em vez de uma junção para obter contagens iguais à contagem máxima para o ano e a data. Possível, mas dificilmente mais simples. Outra opção é usar variáveis para classificação mímico () sobre partição ...) e espero que você tenha encontrado um novo emprego no momento em que a consulta tem que ser mudado ;-)
Lennart

Espero que estejamos no MySQL 8 antes que algo assim aconteça :-). Ele finalmente traz MySQL para o século 21! Analytics, CTE, REGEXPs adequados - parece bom - mesmo que você não possa fazer INTERSECTs e algumas outras queixas, mas parece que a Oracle realmente colocou muito nessa versão.
Vérace

0

Usando o MySQL-8.0 e CTEs, primeiro criamos tmpcomo o agrupamento de contagem agregada por ano / mês / nature_of_illness, RANK()atribui valores idênticos ao cmesmo valor para que o máximo duplicado seja contabilizado:

 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.