MySQL - maximale Summe über verschiedene Monate mit Bindungen über mehrere Jahre

9

Diese Frage wurde inspiriert diese [geschlossen] und ist auf diese nahezu identisch ein , jedoch unter Verwendung der verschiedenen RDBMS (PostgreSQL vs. MySQL).

Angenommen, ich habe eine Liste von Tumoren (diese Daten werden aus realen Daten simuliert):

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

Sie möchten herausfinden, welcher bestimmte Tumor in einem bestimmten Monat am häufigsten auftrat - bisher so gut!

Jetzt werden Sie feststellen, dass es für den 1. Monat 2017 ein Unentschieden gibt - es macht also überhaupt keinen Sinn , eines zufällig auszuwählen und dies als Antwort zu geben - also müssen Unentschieden einbezogen werden -, was das Problem viel schwieriger macht.

Die richtige Antwort ist:

  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

Ein weiterer Bonus wäre, dass der Monatsname als Text und nicht als Ganzzahl angezeigt wird.

Ich habe eine Lösung, aber sie ist ziemlich komplex - ich würde gerne wissen, ob meine Lösung optimal ist oder nicht. Die MySQL-Geige ist da !

Vérace
quelle
Ich verstehe, dass dies eine SQL-spezifische Frage ist, aber dies kann durch die Verwendung einer Zeitreihendatenbank viel einfacher gemacht werden.
Schärpe
2
@Sash, es kann mit den meisten SQL DBMS viel einfacher gemacht werden, einschließlich neuerer Versionen von MySQL / MariaDB. MySQL 5.6 implementiert nicht viele Funktionen, die nach SQL92 erfunden wurden.
Lennart

Antworten:

4

Mein Versuch, dies zu lösen, ist wie folgt. Ich würde mich über Ratschläge freuen, wie diese Abfrage verbessert werden könnte:

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

Und es gibt das richtige Ergebnis, wie in der Geige hier zu sehen ist !

Vérace
quelle
Ich denke nicht, dass es möglich ist, viel einfacher zu machen. Eine Alternative, die mir in den Sinn kommt, ist eine Unterauswahl anstelle eines Joins, um Zählungen zu erhalten, die der maximalen Zählung für das Jahr und das Datum entsprechen. Möglich, aber kaum einfacher. Eine andere Option ist die Verwendung von Variablen, um rank () über Partition durch ...) nachzuahmen und zu hoffen, dass Sie einen neuen Job gefunden haben, wenn die Abfrage geändert werden muss ;-)
Lennart
Hoffentlich sind wir auf MySQL 8, bevor so etwas passiert :-). Es bringt MySQL endlich ins 21. Jahrhundert! Analytics, CTEs, richtige REGEXPs - sehen gut aus - auch wenn Sie INTERSECTs und einige andere Probleme nicht ausführen können, aber es sieht so aus, als hätte Oracle wirklich viel in diese Version gesteckt.
Vérace
0

Mit MySQL-8.0 und CTEs erstellen wir zunächst eine tmpaggregierte Zählung nach Jahr / Monat / nature_of_illnessund RANK()weisen cdem gleichen Wert identische Werte zu , sodass das doppelte Maximum berücksichtigt wird:

 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
quelle