Bietet Rezepte, Lösungen und ausführliche Beispiele rund um gesundes SQL und zufriedene Datenbanken.
Aus dem Bereich OLAP (Online Analytical Processing) haben es in den letzten Jahren auch einige spezielle Reporting-Befehle in die Datenbankwelt geschafft. Beispielweise der Befehl ROLLUP. Damit lassen sich komplexere Gruppierungen mit "Zwischensummen" erzeugen, die wichtig für eine übersichtliche Zahlendarstellung im Reporting sind. Mit ROLLUP werden also sogenannte Superaggregate für ausgewählte Gruppierungsspalten erzeugt.
* IBM DB2 9.1
* MySQL 5.1.30
* ORACLE 10g 10.2 EE
/**
* DB2 und MySQL
*/
CREATE TABLE kunde (
id INT NOT NULL,
name VARCHAR(30) NOT NULL,
plz INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE rechnung (
id INT NOT NULL,
kunde_id INT NOT NULL,
datum DATE NOT NULL,
betrag DEC(10,2) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_rechnung_01
FOREIGN KEY (kunde_id)
REFERENCES kunde (id)
);
/**
* ORACLE
*/
CREATE TABLE kunde (
id INT NOT NULL,
name VARCHAR2(30) NOT NULL,
plz INT NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE rechnung (
id INT NOT NULL,
kunde_id INT NOT NULL,
datum DATE NOT NULL,
betrag DEC(10,2) NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_rechnung_01
FOREIGN KEY (kunde_id)
REFERENCES kunde (id)
);
INSERT INTO kunde VALUES ( 1, 'Kunde-1', 20000);
INSERT INTO kunde VALUES ( 2, 'Kunde-2', 30000);
INSERT INTO kunde VALUES ( 3, 'Kunde-3', 40000);
/**
* DB2 und MySQL
*/
INSERT INTO rechnung VALUES
( 1, 1, '2010-01-01', 100),
( 2, 1, '2010-02-01', 500),
( 3, 2, '2010-03-01', 200),
( 4, 1, '2010-02-01', 100),
( 5, 1, '2010-05-01', 400),
( 6, 1, '2010-02-01', 500),
( 7, 3, '2010-01-01', 200),
( 8, 1, '2010-03-01', 200),
( 9, 2, '2010-02-01', 100),
(10, 3, '2010-06-01', 500),
(11, 1, '2010-04-01', 500),
(12, 3, '2010-07-01', 200),
(13, 1, '2010-06-01', 200),
(14, 2, '2010-04-01', 100),
(15, 3, '2010-03-01', 500);
/**
* ORACLE
*/
INSERT INTO rechnung VALUES ( 1, 1, TO_DATE('2010-01-01','YYYY-MM-DD'), 100);
INSERT INTO rechnung VALUES ( 2, 1, TO_DATE('2010-02-01','YYYY-MM-DD'), 500);
INSERT INTO rechnung VALUES ( 3, 2, TO_DATE('2010-03-01','YYYY-MM-DD'), 200);
INSERT INTO rechnung VALUES ( 4, 1, TO_DATE('2010-02-01','YYYY-MM-DD'), 100);
INSERT INTO rechnung VALUES ( 5, 1, TO_DATE('2010-05-01','YYYY-MM-DD'), 400);
INSERT INTO rechnung VALUES ( 6, 1, TO_DATE('2010-02-01','YYYY-MM-DD'), 500);
INSERT INTO rechnung VALUES ( 7, 3, TO_DATE('2010-01-01','YYYY-MM-DD'), 200);
INSERT INTO rechnung VALUES ( 8, 1, TO_DATE('2010-03-01','YYYY-MM-DD'), 200);
INSERT INTO rechnung VALUES ( 9, 2, TO_DATE('2010-02-01','YYYY-MM-DD'), 100);
INSERT INTO rechnung VALUES (10, 3, TO_DATE('2010-06-01','YYYY-MM-DD'), 500);
INSERT INTO rechnung VALUES (11, 1, TO_DATE('2010-04-01','YYYY-MM-DD'), 500);
INSERT INTO rechnung VALUES (12, 3, TO_DATE('2010-07-01','YYYY-MM-DD'), 200);
INSERT INTO rechnung VALUES (13, 1, TO_DATE('2010-06-01','YYYY-MM-DD'), 200);
INSERT INTO rechnung VALUES (14, 2, TO_DATE('2010-04-01','YYYY-MM-DD'), 100);
INSERT INTO rechnung VALUES (15, 3, TO_DATE('2010-03-01','YYYY-MM-DD'), 500);
Ziel ist es nun mit ROLLUP eine Umsatzauswertung über Jahr und Monat zu erzeugen.
/**
* ROLLUP über Jahr und Monat (DB2)
*/
SELECT YEAR(r.datum) AS jahr, MONTH(r.datum) AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN '2010-01-01' AND '2010-12-31'
GROUP BY ROLLUP(YEAR(r.datum), MONTH(r.datum))
ORDER BY YEAR(r.datum), MONTH(r.datum);
+------+-------+---------+
! JAHR ! MONAT ! UMSATZ !
+------+-------+---------+
! 2010 | 1 | 300,00 |
! 2010 | 2 | 1200,00 |
! 2010 | 3 | 900,00 |
! 2010 | 4 | 600,00 |
! 2010 | 5 | 400,00 |
! 2010 | 6 | 700,00 |
! 2010 | 7 | 200,00 | <== Umsatz 2010 im Monat 07
! 2010 | | 4300,00 | <== Umsatz 2010
! | | 4300,00 | <== Gesamtumsatz
+------+-------+---------+
Folgende Ergebnisse werden ausgegeben:
a) Der Umsatz pro Jahr und Monat gruppiert ( Monat 01 - 07 )
b) Der Umsatz pro Jahr ( Jahr 2010 )
c) Der Gesamtumsatz unabhängig vom Jahr und Monat.
Alle diese Ergebnisse und Summen werden "auf einen Schlag" ermittelt. Das ist schon mehr als praktisch.
/**
* ROLLUP über Jahr und Monat (ORACLE)
*/
SELECT TO_CHAR(r.datum,'YYYY') AS jahr, TO_CHAR(r.datum,'MM') AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN TO_DATE('2010-01-01','YYYY-MM-DD') AND TO_DATE('2010-12-31','YYYY-MM-DD')
GROUP BY ROLLUP(TO_CHAR(r.datum,'YYYY'), TO_CHAR(r.datum,'MM'))
ORDER BY TO_CHAR(r.datum,'YYYY'), TO_CHAR(r.datum,'MM');
+------+-------+---------+
! JAHR ! MONAT ! UMSATZ !
+------+-------+---------+
! 2010 | 1 | 300,00 |
! 2010 | 2 | 1200,00 |
! 2010 | 3 | 900,00 |
! 2010 | 4 | 600,00 |
! 2010 | 5 | 400,00 |
! 2010 | 6 | 700,00 |
! 2010 | 7 | 200,00 | <== Umsatz 2010 im Monat 07
! 2010 | | 4300,00 | <== Umsatz 2010
! | | 4300,00 | <== Gesamtumsatz
+------+-------+---------+
Der SQL muss zwar an ORACLE angepaßt werden, aber die Ergebnisse sind identisch mit dem aus Listing 6:
/**
* ROLLUP über Jahr und Monat (MySQL)
*/
SELECT YEAR(r.datum) AS jahr, MONTH(r.datum) AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN '2010-01-01' AND '2010-12-31'
GROUP BY YEAR(r.datum), MONTH(r.datum) WITH ROLLUP;
+------+-------+---------+
! JAHR ! MONAT ! UMSATZ !
+------+-------+---------+
! 2010 | 1 | 300,00 |
! 2010 | 2 | 1200,00 |
! 2010 | 3 | 900,00 |
! 2010 | 4 | 600,00 |
! 2010 | 5 | 400,00 |
! 2010 | 6 | 700,00 |
| 2010 | 7 | 200.00 | <== Umsatz 2010 im Monat 07
! 2010 | NULL | 4300,00 | <== Umsatz 2010
| NULL | NULL | 4300.00 | <== Gesamtumsatz
+------+-------+---------+
9 rows in set (0.01 sec)
Der SQL muss zwar wieder angepaßt werden (siehe WITH ROLLUP und ORDER BY). MySQL erlaubt leider keinen zusätzlichen ORDER BY bei einem ROLLUP. Probiert man es trotzdem, handelt man sich den Fehler "ERROR 1221 (HY000): Incorrect usage of CUBE/ROLLUP and ORDER BY" ein. Aber auch ohne ORDER BY liefert MySQL das korrekte Ergebnis.
Eigentlich besteht ein ROLLUP nur aus mehreren einzelnen GROUP BY Abfragen, deren Einzelergebnisse mit HILFE von UNION ALL aneinander gesetzt werden.
SELECT YEAR(r.datum) AS jahr, MONTH(r.datum) AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN '2010-01-01' AND '2010-12-31'
GROUP BY YEAR(r.datum), MONTH(r.datum)
UNION ALL
SELECT YEAR(r.datum) AS jahr, NULL AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN '2010-01-01' AND '2010-12-31'
GROUP BY YEAR(r.datum)
UNION ALL
SELECT NULL AS jahr, NULL AS monat, SUM(r.betrag) AS umsatz
FROM rechnung r
JOIN kunde k
ON k.id = r.kunde_id
WHERE r.datum BETWEEN '2010-01-01' AND '2010-12-31';
+------+-------+---------+
! JAHR ! MONAT ! UMSATZ !
+------+-------+---------+
! 2010 | 1 | 300,00 |
! 2010 | 2 | 1200,00 |
! 2010 | 3 | 900,00 |
! 2010 | 4 | 600,00 |
! 2010 | 5 | 400,00 |
! 2010 | 6 | 700,00 |
| 2010 | 7 | 200.00 | <== Umsatz 2010 im Monat 07
! 2010 | NULL | 4300,00 | <== Umsatz 2010
| NULL | NULL | 4300.00 | <== Gesamtumsatz
+------+-------+---------+
9 rows in set (0.01 sec)
Bei den aktuellen MySQL-Versionen sind noch Bugs im Zusammenhang mit ROLLUP gemeldet. Mit der neuen Version 5.5.6 sollen diese behoben sein. Siehe http://bugs.mysql.com/bug.php?id=54477
Was für Probleme es geben kann einen Report basieren auf ROLLUP bei verschiedenen Datenbanken zu realisieren, haben wir eben gesehen. Mal wieder wird der SQL-Standard nur teilweise eingehalten und jeder Datenbankhersteller hat so seine "L.. äh Probleme im Keller".