Struktur eines Forums

  • Themenstarter Themenstarter P_F
  • Beginndatum Beginndatum
Definieren wir doch mal n als Anzahl der Kategorien und m als Anzahl der Foren

In der Array-Variante hast Du als erste Schleife das Auslesen der Kategorien.
Sie besteht aus simplen Schreiboperationen in ein Array (1 x n)
Du hast als zweite Schleife das Auslesen der Foren.
Sie besteht aus 1 x n Operationen zum Anlegen eines Arrays, 1 x m if-Abfragen und 1 x m Schreiboperationen
Du hast die verschachtelte Schleife die zur Ausgabe da ist.
Sie besteht aus m + n Ausgaben.

Das macht im PHP-Code ein Laufzeitverhalten von 3 n + 3 m

Bei dem Code mit einem SQL-Statement gibt es eine Schleife.
Sie besteht aus n Ausgaben und n Zuweisungen. Dazu kommen m if-Abfragen und m Ausgaben.

Das macht im PHP-Code ein Laufzeitverhalten von 2 n + 2 m.

Da MySQL mittlerweile Selects cachen kann (wenn es entsprechend konfiguriert ist), ist das Laufzeitverhalten auf diesen zwei relativ statischen Tabellen gering (die Ergebnismenge ist m Zeilen lang).
Die Ergebnismengen mit 2 SQL-Statements sind zusammen m + n Zeilen lang und es werden zwei Querys gestartet.

Die größte Unbekannte bleibt, wie Du sagst, das Übertragen der Daten und ist, denke ich, sehr von der Serverinfrastruktur abhängig (PHP und DB auf einem Rechner oder auf verschiedenen mit 100 MBit, 1 GBit oder gar online mit noch langsameren Verfahren angebunden).

Du hast recht. Auch wenn es in diesem konkreten Fall nicht wichtig ist, sollte man immer auf die Performanz achten (ich hoffe, das meintest Du in diesem Fall mit optimiert Programmieren). Ich würde mich freuen, wenn das noch mal jemand testen kann. Ich habe derzeit noch kein Webserver, PHP und MySQL auf meinem frisch installierten Rechner.



Entschuldige bitte im Vorwege, wenn ich mich jetzt etwas ungepflegt ausdrücke, aber eine redundanzfreie Tabelle als Ergebnis eines Querys erhalten zu wollen ist Blödsinn.
Dann müsste man JOIN aus der Syntax verbannen.
SQL dient doch dazu, eine normalisierte Datenbank, die nicht besonders leicht zu lesen ist, mit der Abfrage in eine einzige darstellbare Tabelle zu überführen.
Das dient doch auch dazu, unabhängig von der jeweiligen Programmiersprache bereits ein brauchbares Ergebnis zu erhalten, welches direkt aus der dafür optimierten (z.B. durch Indizes und Caches) Datenbank generiert wird.
Stell Dir mal eine Adressdatenbank vor, in der die Länder in einer eigenen Tabelle abgelegt sind. Willst Du diese nicht direkt über einen JOIN einbinden, sondern hinterher mit der Anwendungs-Programmiersprache heraussuchen, damit Du redundanzfreie Ergebnistabellen erhältst?

Die Normalisierung dient in erster Linie dem redundanzfreien Speichern, um Dateninkosistenzen zu vermeiden. Tabellen, die zum Zweck der Ausgabe erzeugt werden haben damit nichts zu tun.

Das Ergebnis der SQL-Abfrage ist eben keine Tabelle in der Datenbank. Es ist eine Tabelle aus der Datenbank. Diese Tabelle steht alleine als Auszug der Datenbank und gehört nicht zu einem normalisierten Datenbankschema.

Gruß hpvw

PS: Der Begriff Sortieren war blöd, aber mit Zuordnen meinte ich das:
PHP:
        if(!isset($board[$forum['cat_id']])) { $board[$forum['cat_id']] = array(); }
        $board[$forum['cat_id']][] = $forum;
in der while-Schleife im Code von Bob dem Meister
 
so ich habe noch ein wenig gebenchmarkt.

bei 9 Kategorien mit je 5 Foren:
query Methode 0.0125s
array Methode 0.0058s
1 query Methode 0.0023s

bei 10 Kategorien mit insgesamt 200 Foren:
query Methode 0.0137s
array Methode 0.0071s
1 query Methode 0.0397s

bei 10 Ketegorien mit insgesamt 400 Foren:
query Methode 0.0212s
array Methode 0.0251s
1 query Methode 0.0356s

das sind jeweils die Durchschnittszeiten nach 76 Testdurchläufen (fragt nicht warum 76)
 
Danke für den Test, dann ist Query-Cache in diesem Fall ja scheinbar voll nutzlos (du benutzt doch MySQL > 4.0.1 entsprechend konfiguriert?).
Man kann das Cachen auch für einzelne Abfragen "erzwingen" bzw. verhindern:
Code:
select SQL_CACHE ...
select SQL_NO_CACHE ...
Link zum Query-Cache.
Eigentlich hätte ich erwartet, dass durchs Cachen das Bilden des Kreuzprodukts vermieden wird, aber anbetracht der Ergebnisse scheint das ja nicht so zu sein.
 
auf dem Rechner läuft MySQL in der Standard config - also vermutlich ohne query Cache. werde das heute aben nochmal mit query Cache probieren.
 
Ok, da bin ich - Ich muss sagen, ich bin ganz positiv überrascht über die Resonanz auf meine doch eigendlich sehr schlichte Frage.
Wenn ich euch richtig verstehe gibt es prinzipiell 3 Möglichkeiten zur Lösung des Problems:

1) DIe erste Möglichkeit mit 2 while-schleifen und enthaltenen querys
2) 2 querys und die ergebnisse in arrays speichern und dann zuordnen.
3) Dieser MySQL Befehl mit JOIN

Nun, genau dieses JOIN hatte ich im Bezug auf mein Problem irgendwo im hinterkopf, also ich wusste keineswegs wie ich das problem löse :D
Darum würde ich hpvw (oder die, die sich damit auskennen) noch einmal bitten diese Lösung ein wenig zu erläutern - also mich interessiert speziell wie ich damit in php umgehe und mein forum generiere.

Danke für die vielen Antworten, Philipp
 
@Bob: interessant wäre auch noch zu wissen, wie sich die Zahlen verändern, wenn es viele Kategorien gibt und wenige unterforen. Und naja, da gibt es ja noch einige Szenarien die man benchmarken kann... :-)

@P_F: Sorry, dass wir deinen Thread so vollspammen, aber das Thema ist halt doch recht interessant!
 
eine lösung wäre schon klasse, aber (gings in diesem thread ja auch schon drum) eine "sauber" programmierte. Und wenns halt n bissle dauert, bis was ordentliches (das ich auch verstehe :-)) rauskommt ist das in ordnung
 
Dann werde ich mal versuchen das näher zu erklären:
Noch mal die Tabellen etwas detaillierter:
Kategorie
ID (INT(11) PRIMARY KEY UNIQUE AUTOINCREMENT NOT NULL)
Bezeichner (CHAR(255) NOT NULL)
sortIndex (INT(11) NOT NULL)

Forum
ID (INT(11) PRIMARY KEY UNIQUE AUTOINCREMENT NOT NULL)
Bezeichner (CHAR(255) NOT NULL)
kategorieID (INT(11) NOT NULL "verweist" auf Kategorie.ID)
sortIndex (INT(11) NOT NULL)

Dann pflücken wir mal das SQL-Statement auseinander:

Teil 1:
Code:
SELECT Forum.ID as forumID, 
Kategorie.ID AS kategorieID,
Forum.Bezeichner AS forum, 
Kategorie.Bezeichner AS kategorie
Wir nehmen uns aus den Tabellen die jeweilige ID (für Links) und die Bezeichner (für den darzustellenden Text).
Da diese in den verschiedenen Tabellen die gleichen Namen haben, müssen wir mit AS einen neuen Namen für das Feld definieren, damit der zuletztgenannte nicht den zuerstgenannten im letztendlichen PHP-Array überschreibt.
Die Ergebnistabelle, die wir erhalten werden enthält also folgende vier Felder:
forumID (INT(11))
kategorieID(INT(11))
forum (CHAR(255))
kategorie (CHAR(255))

2. Teil des Statements:
Code:
FROM Forum LEFT JOIN Kategorie ON Kategorie.ID=Forum.KategorieID
Wir bilden das sogenannte Kreuzprodukt der Tabellen Forum und Kategorie, wobei durch das LEFT sichergestellt ist, dass aus Forum jede Zeile übernommen wird (die Felder werden im Zweifel mit NULL aufgefüllt).
Jedes Element aus Kategorie wird an jedes Element aus Forum angehängt.
Dann werden über die Bedingung hinter ON alle Zeilen gestrichen, die diese Bedingung nicht erfüllen. Jetzt käme das LEFT ins Spiel. Wenn es zu einem Eintrag aus Forum keine der Bedingung genügenden Zeile gibt, werden die Felder aus Kategorie mit NULL gefüllt und die Zeile bleibt einmal in der Ergebnistabelle stehen.

3. Teil des Statements
Code:
ORDER BY Kategorie.sortIndex, Forum.sortIndex
Die bis jetzt unsortierte Ergebnisliste wird zunächst nach dem in Kategorie definierten sortIndex sortiert (oder falls Du anderen Fall wählst Alphabetisch nach dem Bezeichner).
Sollte dieser Index zwei Zeilen gleich bewerten (=die Foren gehören zur selben Kategorie), wird nach dem sortIndex in Forum sortiert.

Zu guter letzt kommt noch das SELECT aus Teil 1 und reduziert die Ergebnistabelle auf die 4 gewünschten Felder.

Dieses Ergebnis sollte MySQL eigentlich Cachen, so dass die ganze Arbeit (in MySQL) sich darauf beschränken sollte, das Query wieder zu erkennen und diese Tabelle raus zu schieben. Nach jeder Änderung in einer der beiden Tabellen wird einmal das gesamte Prozedere durchlaufen.

Als Beispiel könnte das Ergebnis dann so aussehen:
Code:
forumID  kategorieID forum                    kategorie
 4        1           Forum 1 in Kategorie 1   Kategorie 1
 7        1           Forum 2 in Kategorie 1   Kategorie 1
 6        2           Forum 1 in Kategorie 2   Kategorie 2
 2        2           Forum 2 in Kategorie 2   Kategorie 2
 3        3           Forum 1 in Kategorie 3   Kategorie 3
 1        3           Forum 2 in Kategorie 3   Kategorie 3
 5        3           Forum 3 in Kategorie 3   Kategorie 3


Nun zum PHP-Code:
PHP:
//TODO: db verbinden

//Das Query zusammensetzen
$sql = "SELECT Forum.ID as forumID, 
Kategorie.ID AS kategorieID,
Forum.Bezeichner AS forum, 
Kategorie.Bezeichner AS kategorie 
FROM Forum LEFT JOIN Kategorie 
ON Kategorie.ID=Forum.KategorieID
ORDER BY Kategorie.sortIndex, Forum.sortIndex";

//Das Query ausführen
$result = mysql_query($sql);

//Hilfsvariable definieren
//Hier drin wird immer die Kategorie drin stehen, 
//zu der das zuletzt ausgegebene Forum gehört
//(Habe das vom letzten Post mal auf die ID geändert)
$lastCat="";

//wir hohlen uns jede Zeile aus der Ergebnistabelle
while ($row=mysql_fetch_array($result)) {
  //Wir müssen überprüfen, ob sich die Kategorie
  //geändert hat, (falls wir eine Überschrift
  //für die Kategorien ausgeben wollen)
  if($row['kategorieID'] != $lastCat) {
    //Die Kategorieüberschrift ausgeben
    echo "<b>".$row['kategorie']."</b><br>";
    //Speichern, dass wir uns in einer neuen Kategorie
    //befinden
    $lastCat=$row['kategorieID'];
  }

  //forum mit Link auf eine Forumdetailseite ausgeben
  echo "<a href=\"./forumanzeigen.php?forumID="
       .$row['forumID']."\">"
       .$row['forum']."</a><br>";
}
So, das wars eigentlich.

Wenn man der Datenbank ein bisschen Training verpassen will, kann man mit einem weiteren JOIN vorweg (FROM Threads LEFT JOIN Forum ON ... LEFT JOIN Kategorie ON ...) auch gleich die Threads mit übernehmen, aber, ob das Sinn macht?

Gruß hpvw

PS: In Threads, die auf Datenbankdesign oder -Abfragemöglichkeiten zielen, kommt es häufiger beinahe zu Glaubenskriegen und diese Threads werden dann halt etwas länger.
Einige mögen sich vielleicht an den DATETIME-, TIMESTAMP-, UNIX-Timestamp-Thread erinnern?
Das ist wie mit den Volkswirten - drei Volkswirte, vier Meinungen zur Wirtschaftsflaute; drei Datenbankdesigner, fünf Meinungen.zu Tabellenstruktur und sechs Meinungen zum Query. :D
 
Zuletzt bearbeitet:
Zurück