Falls einer Datenreihe eine schiefe Verteilung zugrunde liegt oder extreme Ausreisser vorhanden sind kann sich die Betrachtung des Medians – anstelle des auch als Durchschnitt bezeichneten, allgegenwärtigen arithmetischen Mittels – lohnen.
Während man das arithmetische Mittel mit der SQL Aggregatfunktion AVG leicht ermitteln kann, gibt es in den meisten SQL Dialekten keine Aggregatfunktion zur Bestimmung des Medians. Man muß daher „zu Fuß“ die Datenreihe wertmäßig sortieren, und dann im Fall einer ungeraden Anzahl von Werten den den genau in der Mitte liegenden Wert auswählen bzw. im Fall einer geraden Anzahl von Werten das arithmetische Mittel der beiden genau in der Mitte liegenden Werte bestimmen. Diese Aufgabenstellung kann in Teradata SQL recht elegant mit Hilfe von OLAP Funktionen gelöst werden.
Ermitteln wir zur Veranschaulichung den Median-Rechnungsbetrag jedes Kunden aus einer hypothetischen, denormalisierten Rechnungstabelle:
CREATE SET TABLE Invoice
,NO FALLBACK
,NO BEFORE JOURNAL
,NO AFTER JOURNAL
(
ItemId INTEGER NOT NULL
,Quantity DECIMAL(12,3) NOT NULL
,InvoiceAmount DECIMAL(11,2) NOT NULL
,InvoiceId INTEGER NOT NULL
,CustomerId INTEGER NOT NULL
)
PRIMARY INDEX(InvoiceId)
UNIQUE INDEX(InvoiceId, ItemId)
;
Verkaufte Waren werden durch die ItemId identifiziert, und für jeden Verkauf werden Verkaufsmenge und verrechneter Betrag gespeichert. Alle gemeinsam verkauften Waren erkennt man über die InvoiceId. Die Zuordnung zu einem Kunden erfolgt via CustomerId.
Fügen wir einige Rechnungen in diese Tabelle ein:
-- customer 7001
INSERT INTO Invoice(100, 1, 30, 222, 7001) -- invoice 222
;INSERT INTO Invoice(101, 5, 55, 222, 7001)
;INSERT INTO Invoice(102, 2, 10, 222, 7001)
;INSERT INTO Invoice(100, 2, 60, 298, 7001) -- invoice 298
;INSERT INTO Invoice(101, 1, 11, 298, 7001)
;INSERT INTO Invoice(188, 1, 500, 755, 7001) -- invoice 755
-- customer 7444
;INSERT INTO Invoice(100, 7, 210, 225, 7444) -- invoice 225
;INSERT INTO Invoice(102, 50, 250, 225, 7444)
;INSERT INTO Invoice(188,0.5, 250, 679, 7444) -- invoice 679
;INSERT INTO Invoice(102, 10, 50, 679, 7444)
;
Zunächst ermitteln wir je Kunde eine sortierte Liste aller Rechnungsbeträge:
SELECT
InvoiceId
,CustomerId
,SUM(InvoiceAmount) AS InvoiceAmount
FROM Invoice
GROUP BY InvoiceId, CustomerId
ORDER BY CustomerId, SUM(InvoiceAmount)
;
InvoiceId |
CustomerId |
InvoiceAmount |
298 |
7001 |
71,00 |
222 |
7001 |
95,00 |
755 |
7001 |
500,00 |
679 |
7444 |
300,00 |
225 |
7444 |
460,00 |
Diesem Zwischenergebnis entnehmen wir nun die relevanten Zeilen. Für Kundennummer 7001 ist dies (ungerade Anzahl an Rechnungen!) die Rechnung 222. Da für Kundennummer 7444 eine gerade Anzahl an Rechnungen vorliegt benötigen wir sowohl Rechnung 679 als auch Rechnung 225, um das arithmetische Mittel derer Rechnungsbeträge zu bilden.
Unter Zuhilfenahme ganzzahliger Division kann man die Position der relevanten Zeile(n) im sortierten Zwischenergebnis mit n Zeilen berechnen:
Sowohl die Gesamtanzahl an Rechnungen als auch Position jeder einzelnen Zeile lassen sich mit OLAP Funktionen bestimmen. Gleichzeitig eliminiert die QUALIFY Klausel alle nicht benötigten Zeilen des Zwischenergebnisses:
SELECT
InvoiceId
,CustomerId
,SUM(InvoiceAmount) AS InvoiceAmount
FROM Invoice
GROUP BY InvoiceId, CustomerId
QUALIFY
COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
BETWEEN (COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) + 1) / 2
AND (COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) + 2) / 2
;
InvoiceId |
CustomerId |
InvoiceAmount |
222 |
7001 |
95,00 |
679 |
7444 |
300,00 |
225 |
7444 |
460,00 |
Zuletzt bilden wir noch je Kunde das arithmetische Mittel aller verbliebenen Rechnungsbeträge. Die gesamte Abfrage lautet:
SELECT
CustomerId
,SUM(InvoiceAmount) / COUNT(*) AS MedianInvoiceAmount -- or simply AVG(InvoiceAmount)
FROM
(
SELECT
InvoiceId
,CustomerId
,SUM(InvoiceAmount) AS InvoiceAmount
FROM Invoice
GROUP BY InvoiceId, CustomerId
QUALIFY
COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
BETWEEN (COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) + 1) / 2
AND (COUNT(*) OVER (PARTITION BY CustomerId ORDER BY SUM(InvoiceAmount) ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) + 2) / 2
) t
GROUP BY 1
ORDER BY 1
;
CustomerId |
MedianInvoiceAmount |
7001 |
95,00 |
7444 |
380,00 |