2010-03-15 15 views
5

Ich habe eine große Tabelle (von Websites) mit mehreren numerischen Spalten - sagen a bis f. (Dies sind Site-Rankings von verschiedenen Organisationen, wie Alexa, Google, Quantcast, usw.) Jede hat einen anderen Bereich und ein anderes Format; sie sind gerade Dumps von den externen DBs.)mysql: Durchschnitt über mehrere Spalten in einer Zeile, Nullen ignorieren

Für viele der Datensätze, eine oder mehrere dieser Spalten ist null, da die externe Datenbank keine Daten dafür enthält. Sie alle decken verschiedene Teilbereiche meiner Datenbank ab.

Ich möchte Spalte t zu ihren gewichteten Durchschnitt (jeder von a..f haben statische Gewichte, die ich zuweisen), Null-Werte ignorieren (die in jedem von ihnen auftreten können), außer null, wenn sie alle null sind .

Ich würde es vorziehen, dies mit einer einfachen SQL-Berechnung zu tun, anstatt es in App-Code zu tun oder einen riesigen hässlichen verschachtelten if-Block zu verwenden, um jede Permutation von Nullen zu behandeln. (Angesichts der Tatsache, dass ich eine zunehmende Anzahl von Spalten als Durchschnitt habe, wenn ich mehr außerhalb der DB-Quellen hinzufüge, wäre dies exponentiell hässlicher und fehleranfällig.)

Ich würde AVG verwenden, aber das ist nur für Gruppe von, und das ist w/in einem Datensatz. Die Daten sind semantisch nullbar, und ich möchte keinen "durchschnittlichen" Wert anstelle der Nullen mitteln. Ich möchte nur die Spalten zählen, für die Daten vorhanden sind.

Gibt es einen guten Weg, dies zu tun?

Im Idealfall, was ich will, ist etwas wie UPDATE sites SET t = AVG(a*@a_weight,b*@b_weight,...) wo alle Null-Werte einfach ignoriert werden und keine Gruppierung passiert.

EDIT: Was ich am Ende mit, bezogen auf van der Zugabe und in der richtigen gewichteten Mittelwert (unter der Annahme, dass a bereits nach Bedarf normalisierte, in diesem Fall mit einem Schwimmer 0-1 (1 = besser):

UPDATE sites 
SET t = (@a_weight * IFNULL(a, 0) + ...)/(IF(a IS NULL, 0, @a_weight) + ...) 
WHERE (IF(a IS NULL, 0, 1) + ...) > 0 

Antwort

3
UPDATE sites 
     --// TODO: you might need to round it depending on your type 
SET  t =(COALESCE(a, 0) + 
      COALESCE(b, 0) + 
      COALESCE(c, 0) + 
      COALESCE(d, 0) + 
      COALESCE(e, 0) + 
      COALESCE(f, 0) 
      )/
      ((CASE WHEN a IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN b IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN c IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN d IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN e IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN f IS NULL THEN 0 ELSE 1 END CASE) 
      ) 
WHERE 0<>((CASE WHEN a IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN b IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN c IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN d IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN e IS NULL THEN 0 ELSE 1 END CASE) + 
      (CASE WHEN f IS NULL THEN 0 ELSE 1 END CASE) 
      ) 

Sie könnten COALESCE verwenden auch in den anderen Teilen, aber das wird nicht der Fall behandeln, wenn Sie eine Bewertung mit dem Wert haben 0 richtig, weil sie ausgeschlossen werden. Der WHERE Klausel vermeidet DivideByZero, aber Sie müssen möglicherweise zu haben zusätzliche UPDATE Anweisung, um diesen Fall zu behandeln wenn es keine Bewertung für den Eintrag gibt.

+0

Ich denke, IFNULL ist eine klarere Alternative zu COALESCE, aber etwa gleichwertig. IF (a IS NULL, 0,1) ist ähnlich einfacher als Ihr CASE. Ansonsten denke ich, dass das alles tut, was ich wollte - im Grunde genommen nimmst du die Nullspalten auf und nimmst sie aus dem Nenner, was das schlaue Ding ist und etwas, an das ich hätte denken sollen. :-P – Sai