Hallo Mathias,
In http://aktuell.de.selfhtml.org/weblog/aufloesung-viewport habe ich ein Script beschrieben, dass mithilfe von JavaScript Viewport-Breiten in eine PostgreSQL-Datenbank einträgt.
Ich habe also beliebige Zahlen zwischen 0 und ichweißnichtwieviel in der Datenbank. Daraus will ich letztlich eine solche Statistik erstellen:
http://aktuell.de.selfhtml.org/weblog/files/2006-04-viewport-gesamt-ausschnitt.png
Das ist so zu lesen: 78,9% aller Zahlen sind unter oder gleich 1000.Jetzt frage ich mich, wie ich die Zahlen für eine solche Statistik performant berechne mit möglichst wenig Datenbankaufwand.
Zwei Methoden habe ich gefunden:
- Ich mache (vereinfacht):
[...]
Vorteil dieser Methode: Sie ist schnell und kommt mit einer Datenbankabfrage aus.
Nachteil: Ich blicke nicht ganz durch,
ich auch nicht :-)
ob ich meine Berechnungen wirklich aussagen: Soundsoviel Prozent haben eine innerwidth kleiner gleich x.
Diese Vorgehensweise bietet bestimmt den Ansatz, um mit möglichst geringem Aufwand zum Ergebnis zu kommen.
Gibts da irgendwelche eleganten Möglichkeiten? Vielleicht eine SQL-Query die mir annähernd direkt meine gewünschte Statistik ausgibt? Ich bin völlig PostgreSQL-unerfahren und ansonsten kenne ich auch nur MySQL oberflächlich.
Aus Deiner Problemstellung geht für mich eindeutig hervor, dass Du eine Funktion benötigst. Schließlich können sich die Einträge für die minimale und maximale Viewportbreite im Laufe der Zeit ändern. Ich habe dieses Problem dazu genutzt, meine ersten Schritte in PL/pgSQL zu machen. Das Ergebnis sollte sich nutzen lassen.
Alles in allem benutze ich einen[1] View, eine zusätzliche Tabelle und eine Funktion. Anschließend kann das Ergebnis mit einer einfachen Abfrage ermittelt werden. Ich bitte zu berücksichtigen, dass es meine erste Funktion ist, die ich mit PL/pgSQL erstellt habe, sicherlich gehört noch einiges an Fehlerbehandlung dazu, wahrscheinlich sind auch einige Casts nicht unbedingt erforderlich.
Ausgangspunkt ist die Tabelle "test" mit den Spalten
id, (SERIAL)
innerwidth (INTEGER)
in der die auszuwertenden Daten gespeichert sind.
Mit
CREATE VIEW gruppierung AS
SELECT
CAST(10*CEIL(CAST(innerwidth AS real)/10) AS int) AS abschnitt,
COUNT(id) AS summe
FROM test
GROUP BY abschnitt;
erstelle ich einen View, der mir die Treffer in den 10-er-Intervallen anzeigt. Die Casts sind erforderlich, da ansonsten eine Division ohne Rest erfolgt - und somit das Ergebnis nicht das gewünschte ist.
Meine Ergebnistabelle "ergebnis" enthält folgende Spalten
abschnitt -- die Intervalle
wert -- die Treffer im Intervall
summe -- die kumulierte Summe
total -- die Gesamtzahl der Einträge
CREATE TABLE ergebnis (
abschnitt int,
wert int DEFAULT NULL,
summe int,
total int);
Im nächsten Schritt wird eine Funktion erstellt, die die Ergebnistabelle mit den kumulierten Daten füllt - und nicht besetzte Intervalle einfügt.
CREATE FUNCTION aktualisiere_ergebnis() RETURNS bigint AS $$
DECLARE
beginn bigint; -- Startwert der Intervalle
gesamt bigint; -- Gesamtzahl der Datensätze
aktuell bigint; -- aktuelles Intervall
k_summe bigint; -- kumulierte Summe
datensatz RECORD; -- aktueller Datensatz
BEGIN
-- Leere die Ergebnistabelle
TRUNCATE ergebnis;
-- Ermittle den Startwert
beginn := 10 * MIN(cast(CEIL(cast(innerwidth as real)/10) as int)) FROM test;
-- Ermittle die Gesamtzahl der Datensätze
gesamt := COUNT(*) FROM test;
-- Setze die Startwerte
aktuell := beginn;
k_summe := 0;
-- Durchlaufe alle Datensätze
FOR datensatz IN SELECT abschnitt, summe FROM gruppierung ORDER BY abschnitt LOOP
-- Falls Intervalle nicht besetzt sind, müssen sie eingetragen werden
WHILE cast(aktuell as int) < cast(datensatz.abschnitt as int) LOOP
INSERT INTO temptest (abschnitt, summe, total)
VALUES (aktuell, k_summe, gesamt);
-- nächstes Intervall, Schrittweite ist 10
aktuell := aktuell + 10;
END LOOP;
-- verarbeite aktuellen Datensatz
-- Berechne die kumulierte Summe neu
k_summe := k_summe + datensatz.summe;
-- Schreibe den Datensatz weg
INSERT INTO temptest (abschnitt, wert, summe, total) VALUES (datensatz.abschnitt, datensatz.summe, k_summe, gesamt);
-- setze das nächste Intervall (sonst gibt es Doppeleinträge)
aktuell := cast(datensatz.abschnitt as int) + 10;
-- Am Ende ist grundsätzlich ein besetztes Intervall :-)
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
Nach diesen Vorarbeiten ist für die Auswertung nur noch folgendes zu tun:
1. Aufruf der Funktion
SELECT aktualisiere_ergebnis()
2. Abfragen der Werte
SELECT
abschnitt,
summe,
ROUND(100 * CAST(total - summe AS numeric) / total, 1) AS anteil
FROM ergebnis
ORDER BY abschnitt
Die Funktion aktualisiere_ergebnis() - nach meinen rudimentären Messungen (10000, 100000 und 1000000 Datensätze) - arbeitet mit O(N), auf meinem Testsystem bei 1 Million Datensätzen im einstelligen Sekundenbereich. Laut PostreSQL-Doku wird für COUNT(*) ein kompletter Tablescan benötigt, so dass sich hier nicht viel optimieren lässt :-(
Das Abfragen der Werte erfolgt in konstanter Zeit (es sei denn, die Anzahl der zu betrachtenden Intervalle stiege deutlich an - was doch eher unwahrscheinlich ist), die Ausführungszeit dieser Abfrage ist vernachlässigbar.
Freundliche Grüße
Vinzenz
[1] Mein Sprachempfinden sagt mir, dass es sich bei View um ein Maskulinum handelt, weder "die View" (da "die Sicht") noch "das View" hört sich meiner Meinung nach richtig an. Ich halte zudem nichts davon, diesen Begriff zu übersetzen.