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

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

/* Diese Lösung ist nicht sehr performant und ist bei großen Datenmengen
   nicht mehr einsetzbar. */

/* alle Minuten des Tages in die Minutentabelle eintragen */

TRUNCATE TABLE zeitintervall_minute;

WITH zahlen_0_bis_9 AS 
(
          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
)

INSERT INTO zeitintervall_minute
(minute)
SELECT
    1000 * t1.zahl + 100 * t2.zahl + 10 * t3.zahl + t4.zahl AS minute
FROM (
    SELECT zahl 
    FROM zahlen_0_bis_9
    WHERE
    zahl <= 1
) t1 INNER JOIN zahlen_0_bis_9 t2
ON
1 = 1
INNER JOIN zahlen_0_bis_9 t3
ON
1 = 1
INNER JOIN zahlen_0_bis_9 t4
ON
1 = 1
WHERE
1000 * t1.zahl + 100 * t2.zahl + 10 * t3.zahl + t4.zahl < 1440;

/* Dieser Befehl fügt 20.000 Tage (ca. 54 Jahre) ab 01.01.2000 in die
   Tages-Tabelle ein. Er braucht dafür weniger als 0,5 Sekunden. */

TRUNCATE TABLE zeitintervall_tag;

WITH zahlen_0_bis_9 AS 
(
          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
)

INSERT INTO zeitintervall_tag
(tag)
SELECT
DATEADD(DAY, 
        10000 * t1.zahl + 1000 * t2.zahl + 100 * t3.zahl + 10 * t4.zahl + t5.zahl, 
        CONVERT(DATETIME, '2000-01-01',120)
        ) AS tag
FROM (
    SELECT zahl 
    FROM zahlen_0_bis_9
    WHERE
    zahl <= 1
) t1 INNER JOIN zahlen_0_bis_9 t2
ON
1 = 1
INNER JOIN zahlen_0_bis_9 t3
ON
1 = 1
INNER JOIN zahlen_0_bis_9 t4
ON
1 = 1
INNER JOIN zahlen_0_bis_9 t5
ON
1 = 1;

/* Nun Aufsplittung der Zeitintervalle in einzelne Datensätze pro Minute.
   Dafür wird ein JOIN aufgebaut, der in der ON-Bedingung keine Gleichheit 
   abfragt, sondern Ungleichheit. */

TRUNCATE TABLE zeitintervall_testdaten_pro_minute;

INSERT INTO zeitintervall_testdaten_pro_minute
(
    row_id,
    fallnr,
    datum_plus_minute,
    duplikat
)
SELECT
    t1.row_id,
    t1.fallnr, 
    DATEADD(MINUTE, t3.minute, t2.tag) AS datum_plus_minute, 
    0 AS duplikat
FROM (
    SELECT
        row_id,
        fallnr,
        beginn,
        CONVERT(DATETIME, CONVERT(VARCHAR(10), beginn, 120), 120) beginntag,
        ende,
        CONVERT(DATETIME, CONVERT(VARCHAR(10), ende, 120), 120) endetag
    FROM zeitintervall_testdaten
) t1 INNER JOIN zeitintervall_tag t2
ON
    t1.beginntag <= t2.tag
AND t1.endetag   >= t2.tag
INNER JOIN zeitintervall_minute t3
ON 1 = 1 /* zunächst die Minuten komplett als kartesisches Produkt nehmen,
            später werden Beginn- und Ende-Tag nachjustiert */
WHERE (
   /* für den Tag des Intervallbeginns muss sichergestellt werden, 
      dass nur die Minuten nach der Beginnzeit genommen werden */
   t1.beginntag < t2.tag
   OR 
   (t1.beginntag = t2.tag AND DATEDIFF(MINUTE,t1.beginntag,t1.beginn) <= t3.minute)
)
AND (
   /* für den Tag des Intervallendes muss sichergestellt werden, 
      dass nur die Minuten vor der Endezeit genommen werden
      (dabei muss die Grenzminute ausgeschlossen werden) */
   t1.endetag > t2.tag 
   OR 
   (t1.endetag = t2.tag AND DATEDIFF(MINUTE, t1.endetag, t1.ende) > t3.minute)
);

/*
- Kennzeichnung der Duplikate pro Fallnr und Minute
- nur ein Datensatz wird als gültig behalten
- die anderen werden als Duplikate gekennzeichnet
- damit der JOIN einigermaßen performant ist, muss der Index der Tabelle 
  die Spalten "fallnr", "datum_plus_minute", "row_id" genau in der gleichen Reihenfolge 
  enthalten, wie sie dann in der ON-Bedingung abgefragt werden
-> bei mehreren Millionen Datensätzen ist sonst kein JOIN mehr möglich
*/

UPDATE t1
SET t1.duplikat = 1
FROM zeitintervall_testdaten_pro_minute t1 INNER JOIN zeitintervall_testdaten_pro_minute t2
ON
    t1.fallnr = t2.fallnr
AND t1.datum_plus_minute = t2.datum_plus_minute
AND t1.row_id < t2.row_id;

/* ermittelt pro fallnr und Tag die Gesamtminuten */

TRUNCATE TABLE zeitintervall_ergebnis;

INSERT INTO zeitintervall_ergebnis
(
    fallnr,
    tag,
    minuten
)
SELECT
    fallnr,
    CONVERT(DATETIME, CONVERT(VARCHAR(10), datum_plus_minute, 120), 120) AS tag,
    COUNT(*) AS minuten
FROM zeitintervall_testdaten_pro_minute
WHERE
duplikat = 0
GROUP BY 
fallnr, CONVERT(DATETIME, CONVERT(VARCHAR(10), datum_plus_minute, 120), 120);

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

DELETE FROM zeitintervall_ergebnis
WHERE
minuten = 0;