MySQL: somma massima per diversi mesi con vincoli per più anni


9

Questa domanda è stato ispirato da questo [chiuso] ed è praticamente identico a questo uno , ma utilizzando diversi RDBMS di (PostgreSQL vs MySQL).

Supponiamo di avere un elenco di tumori (questi dati sono simulati da dati reali):

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

Vuoi scoprire quale particolare tumore era più comune in un determinato mese - finora tutto bene!

Ora noterai che per il mese 1 del 2017 c'è un pareggio - quindi non ha alcun senso sceglierne uno a caso e darlo come risposta - quindi i legami devono essere inclusi - questo rende il problema molto più impegnativo.

La risposta corretta è:

  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 ulteriore vantaggio sarebbe quello di far apparire il nome del mese come testo anziché come numero intero.

Ho una soluzione, ma è piuttosto complessa: mi piacerebbe sapere se la mia soluzione è ottimale o meno. Il violino di MySQL è qui !


Capisco che questa sia una domanda specifica per SQL, ma ciò può essere reso molto più semplice utilizzando un database di serie temporali.
Sash

2
@Sash, può essere fatto molto più semplice con la maggior parte dei DBMS SQL, comprese le versioni più recenti di MySQL / MariaDB. MySQL 5.6 non implementa molte funzionalità inventate dopo SQL92.
Lennart

Risposte:


4

Il mio tentativo di risolvere questo è il seguente. Gradirei qualche consiglio su come migliorare questa query:

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à il risultato corretto, come si può vedere nel violino qui !


Non penso che sia possibile fare molto più semplice. Un'alternativa che viene in mente è una sottoselezione invece di un join per ottenere conteggi che equivalgono al conteggio massimo per l'anno e la data. Possibile, ma difficilmente più semplice. Un'altra opzione è usare le variabili per imitare il rank () sulla partizione per ...) e sperare che tu abbia trovato un nuovo lavoro prima che la query debba essere cambiata ;-)
Lennart

Speriamo di essere su MySQL 8 prima che accada qualcosa del genere :-). Porta finalmente MySQL nel 21 ° secolo! Analytics, CTE, REGEXP appropriati - sembrano buoni - anche se non puoi fare INTERSECT e qualche altra lamentela, ma sembra che Oracle abbia davvero messo molto in questa versione.
Vérace,

0

Usando MySQL-8.0 e CTE creiamo prima tmpcome raggruppamento dei conteggi aggregati per anno / mese / nature_of_illness, RANK()assegna valori identici allo cstesso valore in modo da tenere conto del massimo duplicato:

 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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.