Sven Rautenberg: Array nach Werten filtern

Beitrag lesen

Moin!

Also es soll eine Leistungs-Auswertung der Mitarbeiter werden. Die geleisteten Stunden kommen aus der Zeiterfassung. Dafür mache ich als erstes eine SQL-Abfrage (tabelle stunden), in der ich auch mit den nötigen Einschränkungen einige zig-tausend Datensätze bekomme. Das Beispiel mit der Stadt hatte ich nur zum besseren Verständnis, es geht eigentlich um die "uid".

Das ist dein SELECT * from stunden WHERE datum>='$von' and datum<='$bis' and ((tid>=200 and tid<300) or (tid>=800 and tid<900)) order by uid?

Kosmetischer Hinweis am Rande: SQL kennt auch den BETWEEN-Vergleich, damit kriegt man viele Abfragen deutlich lesbarer hin.

SELECT * FROM stunden WHERE datum BETWEEN '$von' AND '$bis' AND (tid BETWEEN 200 AND 299 OR tid BETWEEN 800 AND 899)) ORDER BY uid

Ich gehe mal davon aus, dass 'tid' eine Integerspalte ist, und tid < 900 äquivalent zu tid <=899.

Mehr Performance wegen weniger Datenmenge kriegt man auch, wenn man kein "SELECT *" abfragt, sondern alle Spalten explizit auflistet, die man haben will. Das macht's auch zum Helfen leichter, weil die relevanten Spalten bekannt sind, insbesondere wenn es um JOINs geht. Und das sollte es wahrscheinlich auch bei diesem Problem.

Und nur so als Eingangsbemerkung: Meine Ahnung war korrekt, dass dein Problem nicht ist, dass dein Array gefiltert werden muss, denn deine erste foreach-Schleife hat genausoviel Code, wie eine Array-Filterung haben würde. In Wirklichkeit geht es um effektive Anordnung von Daten in Arrays und das Vermeiden von sinnloser Doppelarbeit.

Jeder dieser Datensätze muss nun mit Daten des entsprechenden Benutzers erweitert werden zur Anzeige bzw. Berechnung (name, kostenstelle, stundensatz, etc).

Das ist die klassische Aufgabe eines JOINs, würde also von der Datenbank gelöst werden können. Ebenfalls wäre zu prüfen, ob die Berechnung nicht schon direkt in der Datenbank erledigt werden könnte.

Dazu habe ich mir vorher ein Benutzer-Array mit allen relevanten Daten der Mitarbeiter aufgebaut, um nicht bei jedem Datensatz der Leistungen eine SQL-Abfrage auf Benutzertabelle + Kostenstellentabelle + Lohntabelle durchführen zu müssen. In diesem Benutzer-Array können sogar Benutzer mehrfach auftauchen, wenn dieser Benutzer mehrere Kostenstellen hat (z.B. mit Kst1 -> Anteil 70% und Kst2 -> Anteil 30%).

Das ist halt die andere Alternative, wenn man komplexere Berechnungen durchführt, die man nicht in die Datenbank tun kann oder will.

Die berechneten Daten schreibe ich in eine Zwischentabelle, was natürlich auch eine Menge Zeit kostet. Das hat aber den Vorteil, wenn die Auswertung einmal berechnet ist, kann der Benutzer mit Hilfe von Links Detaildaten auf- und zuklappen, ohne das die ganze Berechnung nochmal durchgeführt werden muss.

Das ist genau der Punkt: Ich vermute mal, deine Berechnungslogik verbraucht deshalb soviel Zeit, weil sie insgesamt viel Zeit braucht - nicht weil das Benutzerarray so groß ist und gefiltert die Sache schneller laufen würde.

Bei der Berechnung machen die mehrfach auftretenden Benutzer pro Kostenstelle die Sache komplizierter, ich kann leider nicht das Benutzerarray so aufbauen $user_array[$uid][name], damit könnte ich direkt auf die Werte zugreifen, das geht nun aber leider nicht. Deshalb möchte ich das array auf den entsprechenden Benutzer eingrenzen, und dann durchlaufen.

[Aus deinem Ergänzungsposting:]

Zugriffe auf das Array sehen dann so aus:
$user_array[$i][uid]
und
$user_array[$i][kst]
etc.

Offenbar ist die Organisation deines Benutzerdatenarrays suboptimal. Gib mal Beispieldaten und die Struktur genau an. PHP-Arrays sind eine ganz wunderbare Datenstruktur, die sich sehr flexibel einsetzen lässt, wenn man weiß wie.

$q = "SELECT * from stunden WHERE datum>='$von' and datum<='$bis' and ((tid>=200 and tid<300) or (tid>=800 and tid<900)) order by uid";
$r = mysql_query($q, $cid);

while ($row = mysql_fetch_object($r)) {

Benutzerdaten ermitteln;
  Werte berechnen;

Satz in Zwischentabelle schreiben;

}


> Ich hoffe, das war soweit verständlich.  
  
Ja, dein Pseudocode lässt aber leider den relevanten Part komplett unbeleuchtet, nämlich "Benutzerdaten ermitteln" und "Werte berechnen", ggf. auch noch "Satz in Zwischentabelle schreiben".  
  
Das sind die Dinge, die hier die Zeit verbrauchen. Vernünftiges Profiling wäre hier angesagt, also die Ermittlung der tatsächlich in jedem dieser Schritte verbrauchten Zeit. Denn nur wenn du weißt, bei welcher Aktion die meiste Zeit draufgeht, kannst du entscheiden, wo eine Optimierung das meiste Potential hat.  
  
Mal plastisch gesprochen: Wenn du ermittelst, dass "Benutzerdaten ermitteln" pro Datensatz 0,1 Sekunde braucht, aber "Satz in Zwischentabelle schreiben" 1,0 Sekunden, dann bringt es dir ganz wenig, wenn du bei den Benutzerdaten optimierst.  
  
Deshalb musst du als allererstes mal Rechenzeit messen. In PHP 5 liefert dir die Funktion `microtime(true)`{:.language-php} die aktuelle Mikrosekundenuhrzeit als Float-Wert. Wenn du die Zeit vor und nach einer Funktion nimmst, ist deren Differenz die Laufzeit. Mit diesem Ergebnis kannst du dann etwas Statistik betreiben, indem du in deiner while-Schleife die Laufzeit für jeden der dort betriebenen Schritte ermittelst und immer weiter summierst.  
  
Am Ende erhältst du dann die Summe an Laufzeit für jeden Schritt. Bei Bedarf kannst du dir auch noch den Durchschnitt ausrechnen, und ggf. auch noch Minimum und Maximum der einzelnen Laufzeiten speichern.  
  
Solch eine Laufzeitanalyse hilft dir dann, zu identifizieren, welcher Schritt bei dir am meisten Zeit verbraucht. Und die Erfassung von Minimum und Maximum würde dir erlauben zu sehen, ob ein Schritt höchst unterschiedliche Laufzeiten hat.  
  
Sowas geht auch automatisierter mit Xdebug und anderen Extensions, aber die muss man auf dem Server auch einbinden und aktivieren dürfen. Definitiv nix für den Live-Server.  
  
 - Sven Rautenberg