Datenbanktheorie » MySQL 02 Daten aufbereiten
/* <-- zurück zu Zeitintervalle in SQL verarbeiten */

/* MySQL: Schritte für die Datenaufbereitung in einem Skript */

/* Dieses Skript ist sehr performant. Es braucht nur wenige Sekunden. */

/* Zahlen von 0 bis 9 in eine Hilfstabelle einfügen.
   Diese können später dafür genutzt werden, die Tage in 
   die Tagestabelle einzutragen. */
   
TRUNCATE TABLE zeitintervall_zahlen_0_bis_9;

INSERT INTO zeitintervall_zahlen_0_bis_9
(zahl)
      SELECT 0 AS zahl
UNION SELECT 1 AS zahl
UNION SELECT 2 AS zahl
UNION SELECT 3 AS zahl
UNION SELECT 4 AS zahl
UNION SELECT 5 AS zahl
UNION SELECT 6 AS zahl
UNION SELECT 7 AS zahl
UNION SELECT 8 AS zahl
UNION SELECT 9 AS zahl;

/* alle Tage von 2001 bis 2019 in die Tagestabelle eintragen (6939 Tage) */

TRUNCATE TABLE zeitintervall_tag;

INSERT INTO zeitintervall_tag
(tag)
SELECT
  ADDDATE('2001-01-01',1000 * t1.zahl + 100 * t2.zahl + 10 * t3.zahl + t4.zahl) AS tag
FROM zeitintervall_zahlen_0_bis_9 t1 INNER JOIN zeitintervall_zahlen_0_bis_9 t2
ON
1 = 1
INNER JOIN zeitintervall_zahlen_0_bis_9 t3
ON
1 = 1
INNER JOIN zeitintervall_zahlen_0_bis_9 t4
ON
1 = 1
WHERE
ADDDATE('2001-01-01',1000*t1.zahl + 100*t2.zahl + 10*t3.zahl + t4.zahl) < '2020-01-01';

/* Nun die Zeitintervalle sortieren.
   Dabei müssen diejenigen Zeitintervalle ausgeschlossen werden, die komplett 
   innerhalb von einem anderen liegen. Diese Intervalle zählen für das Ergebnis 
   sowieso nicht, und für die nächsten Schritte brauchen wir pro Fall eine Folge 
   von Zeitintervallen, wo Beginn des Nachfolgers >= Beginn des Vorgängers 
   und auch das Ende des Nachfolgers >= Ende des Vorgängers ist. */

TRUNCATE TABLE zeitintervall_sortiert;

INSERT INTO zeitintervall_sortiert 
(fallnr, beginn, ende)
SELECT fallnr, beginn, ende
FROM zeitintervall_testdaten
WHERE 
row_id NOT IN (   
    SELECT DISTINCT t1.row_id    
    FROM zeitintervall_testdaten t1 INNER JOIN zeitintervall_testdaten t2   
    ON           
        t1.fallnr = t2.fallnr   
    AND t1.beginn > t2.beginn    
    AND t1.ende   < t2.ende 
)
ORDER BY fallnr, beginn, ende;

/* Als nächstes werden die Stellen gekennzeichnet, wo der Beginn des
   Nachfolgeintervalls nach dem Ende des Vorgängers liegt, denn bis dorthin
   kann man alle Intervalle einer Fallnr als zusammenhängend betrachten und
   damit diesen Zeitraum vom Anfang bis zum Ende zählen. Mit dem nächsten
   Intervall beginnt ein neuer zusammenhängender Intervall. */

UPDATE zeitintervall_sortiert t1 INNER JOIN zeitintervall_sortiert t2 
ON 
    t1.fallnr = t2.fallnr
AND t1.row_id + 1 = t2.row_id
AND t1.ende < t2.beginn
SET 
t1.next_beginn = t2.beginn;

/* Die Länge der zusammenhängenden Intervalle wird nun nicht direkt ermittelt, 
   sondern indirekt. Dafür wird erstens die Gesamtzeit eines Falles vom ersten
   Beginn bis zum letzten Ende eingetragen. Zweitens werden die Lücken in der
   Gesamtzeit eingetragen, so dass später die Längen der zusammenhängenden
   Intervalle daraus berechnet werden können. */

TRUNCATE TABLE zeitintervall_gesamt_und_luecken;

INSERT INTO zeitintervall_gesamt_und_luecken 
(fallnr, beginn, beginntag, ende, endetag, luecke, faktor)
SELECT 
    fallnr, 
    min_beginn, 
    DATE(min_beginn) AS beginntag,
    max_ende, 
    DATE(max_ende) AS endetag,
    luecke,
    faktor
FROM ( 
    /* zuerst den Gesamt-Zeitraum jedes Falles vom ersten Beginn bis zum letzten
       Ende eintragen */
    SELECT
        fallnr, 
        MIN(beginn) AS min_beginn, 
        MAX(ende) AS max_ende, 
        '' AS luecke, 
        1 AS faktor
    FROM zeitintervall_sortiert
    GROUP BY fallnr

    UNION ALL
    /* danach die Lücken zusätzlich eintragen und mit dem Faktor -1 versehen, 
       damit sie subtrahiert werden können */
    SELECT
        fallnr, 
        ende, 
        next_beginn, 
        'X' AS luecke, 
        -1 AS faktor
    FROM zeitintervall_sortiert
    WHERE
    next_beginn IS NOT NULL
) abfrage;

/* Für das Ergebnis zeigt der Faktor (1 oder -1) an, ob der jeweilige Zeitraum
   positiv oder negativ einfließen muss. */

TRUNCATE TABLE zeitintervall_ergebnis;

INSERT INTO zeitintervall_ergebnis
(fallnr, tag, minuten)
SELECT 
    t1.fallnr, 
    t2.tag, 
    SUM(t1.faktor *
        CASE
            WHEN t1.beginntag = t1.endetag THEN TIMESTAMPDIFF(MINUTE, t1.beginn, t1.ende)
            WHEN t1.beginntag = t2.tag THEN 1440 - TIMESTAMPDIFF(MINUTE, t2.tag, t1.beginn)
            WHEN t1.endetag = t2.tag THEN TIMESTAMPDIFF(MINUTE, t2.tag, t1.ende)
            ELSE 1440
        END
    ) AS minuten
FROM zeitintervall_gesamt_und_luecken t1 INNER JOIN zeitintervall_tag t2 
ON 
    t1.endetag   >= t2.tag 
AND t1.beginntag <= t2.tag
GROUP BY t1.fallnr, t2.tag
ORDER BY t1.fallnr, t2.tag;


/* Zeilen mit Minuten = 0 löschen */

DELETE FROM zeitintervall_ergebnis
WHERE
minuten = 0;