Tabellenstruktur/Forum [PHP/mySQL]

Performant, datenbankunabhängig, normalisiert, kurz die eierlegende Woll-Milch-Sau ;) Du hast schon recht, wenn Du sagst:
Thomas Darimont hat gesagt.:
Es ist eben wie so oft die Mischung die's macht
Meine Mischung ist dann vermutlich etwas normaler und Deine etwas performanter.

@Datic: Wenn Du an der DB auch üben willst, ist ein extrem normalisiertes Modell vielleicht gar nicht so verkehrt, da die Querys schon etwas verzwickter werden und die Normalisierung nun mal zu den Datenbanken dazu gehört.

Gruß hpvw
 
Wenn Du an der DB auch üben willst, ist ein extrem normalisiertes Modell vielleicht gar nicht so verkehrt [..]
Stimmt schon, allerdings bin ich im augenblicklichen Stadium ("Handbuch auf den Knien" ;) ) schon froh, wenn ich einfache Abfragen hinbekomme. Die Normalisierung ist auf jeden Fall das Ziel (und Dein vorheriges Posting im Hinterkopf), aber damit sich überhaupt mal was "auf dem Bildschirm tut", habe ich vorläufig a) zusätzlich eine forum_id in den Postings und die ID des letzten Postings in der Forum-Tabelle (schon klar, gaanz böse und nicht normalisiert). :)

Gruß
.
 
Ich bin gerade neugierig, ob das Statement oben funktioniert und baue daher die DB (ähnlich) nach.
Beim Anlegen ist mir dann aufgefallen, dass ggf. der Titel im Thread derselbe ist, wie der des ersten Postings im Thread und damit redundant wäre. Allerdings wird das Statement ohne dieses Feld vermutlich noch wilder, wenn man den Titel des ersten Post im Thread auch haben möchte.

Ansonsten: Viel Erfolg!

Gruß hpvw
 
Beim Anlegen ist mir dann aufgefallen, dass ggf. der Titel im Thread derselbe ist, wie der des ersten Postings im Thread
Nicht unbedingt: Wenn ich z.B. bei Tutorials.de mit den Admin-Funktionen den Titel eines Threads ändere, so ändert sich auch der Titel des ersten Postings, soweit stimmts schon. Wenn ich aber den ersten Beitrag direkt editiere und mich dort am Titel zu schaffen mache, ändert sich der Titel in der Threadansicht nicht.

... aus diesem Grund gibts bei "meinem Forum" auch keine Spalte "title" in den Postings (war ein Fehler in meinem Ausgangsposting), da ich das für überflüssig halte. ;)

Gruß

P.S.und etwas offtopic: Inzwischen habe ich übrigens noch ein ganz anderes Problem: Da ich ein Flash-FrontEnd baue, habe ich eine Klasse, die genau eine Abfrage zur Zeit an mein Backend stellen kann und per Recall-Funktion das Resultat zurückgibt. So oder so brauche ich in der Forenansicht mehrere Abfragen pro Forum... und es ist nicht grade performant, etliche Requests nacheinander an den Server zu schicken. Da muss glaub ich ein Array aus Abfragen her. ;)

P.P.S.: Wenn Du mit Deiner Konstruktion Erfolg hast, würde mich das interessieren. Ich werde mich in das Thema JOIN erst noch einlesen müssen.
.
 
Datic hat gesagt.:
P.P.S.: Wenn Du mit Deiner Konstruktion Erfolg hast, würde mich das interessieren. Ich werde mich in das Thema JOIN erst noch einlesen müssen.
.
Hatte ich, aber es ist verdammt schmutzig.
Meine Idee mit dem ORDER BY war zwar nett, aber hilft kein Stück.
Die Optimierung von MySQL greift beim JOIN einfach irgendeine Zeile aus der Posting-Tabelle (in meinem Testcase war es in dem einen Forum die erste, in dem anderen die letzte), aus der für die durch GROUP BY entstehende einzelne Ergebniszeile die Werte genommen werden.

Erstmal die Struktur:

forum
  • ID
  • Titel
thread
  • ID
  • ForumID
posting
  • ID
  • ThreadID
  • Titel
  • Inhalt
  • Zeitpunkt
  • AutorID
user
  • ID
  • Name

Da ich jetzt den Titel nur in den Postings habe und den zusätzlich zum Datum auch gern hätte, ebenso, wie die PostingID und den Autor des letzten Post, war ein ganz dreckiger Workaround nötig.

Wenn dafür noch jemand eine Idee hat, immer her damit!
(@Thomas: So werden normalisierte Tabellen inperformant, um mal ein konkretes Beispiel zu zeigen. :-( Kannst Du gerne verwenden, wenn Du mal eins brauchst.)

Das größte Datum, also das des letzten Posting, eines Forums kann man relativ einfach durch MAX(posting.Zeitpunkt) ermitteln.
Da damit immer noch nicht die zur Posting-Zeile mit größtem Datum gehörenden anderen Spalten augewählt werden, habe ich folgendes gemacht:
Ich setze einen String aus dem Datum und dem jeweiligen Feld zusammen, nehme davon mit MAX den höchsten Wert (definiert durch das voranstehende Datum) und schneide davon das Datum mit SUBSTR wieder ab.
Das kostet mit Sicherheit enorm Zeit.

Hier die Datenbank zum nachbauen:
Code:
# phpMyAdmin MySQL-Dump
# version 2.2.3
# http://phpwizard.net/phpMyAdmin/
# http://phpmyadmin.sourceforge.net/ (download page)
#
# Host: localhost
# Erstellungszeit: 22. Mai 2005 um 02:00
# Server Version: 4.01.10
# PHP Version: 5.0.3
# Datenbank : `forumtest`
# --------------------------------------------------------

#
# Tabellenstruktur für Tabelle `forum`
#

CREATE TABLE forum (
  ID int(11) unsigned NOT NULL auto_increment,
  Titel char(255) NOT NULL default '',
  PRIMARY KEY  (ID),
  UNIQUE KEY ID_2 (ID),
  KEY ID (ID)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

#
# Daten für Tabelle `forum`
#

INSERT INTO forum VALUES (1, 'MySQL');
INSERT INTO forum VALUES (2, 'PHP');
# --------------------------------------------------------

#
# Tabellenstruktur für Tabelle `posting`
#

CREATE TABLE posting (
  ID int(11) unsigned NOT NULL auto_increment,
  ThreadID int(11) unsigned NOT NULL default '0',
  Titel varchar(255) NOT NULL default '',
  Inhalt text NOT NULL,
  Zeitpunkt datetime NOT NULL default '0000-00-00 00:00:00',
  AutorID int(11) unsigned NOT NULL default '0',
  PRIMARY KEY  (ID),
  UNIQUE KEY ID_2 (ID),
  KEY ID (ID)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

#
# Daten für Tabelle `posting`
#

INSERT INTO posting VALUES (1, 1, 'Datenbank exportieren?', 'Wie geht das?', '2005-05-22 00:36:20', 1);
INSERT INTO posting VALUES (2, 1, 'Re: Datenbank exportieren?', 'Ganz einfach!', '2005-05-22 00:37:18', 2);
INSERT INTO posting VALUES (3, 2, 'CSV in DB?', 'Helft mir!', '2005-05-22 00:38:05', 2);
INSERT INTO posting VALUES (4, 2, 'Re: CSV in DB?', 'Einlesen, Eintragen, Fertig', '2005-05-22 00:38:37', 1);
INSERT INTO posting VALUES (5, 3, 'Variable ausgeben?', 'Wie denn?', '2005-05-22 01:06:05', 1);
INSERT INTO posting VALUES (6, 3, 'Re: Variable ausgeben', 'echo $var;', '2005-05-22 01:06:31', 2);
# --------------------------------------------------------

#
# Tabellenstruktur für Tabelle `thread`
#

CREATE TABLE thread (
  ID int(11) unsigned NOT NULL auto_increment,
  ForumID int(11) unsigned NOT NULL default '0',
  PRIMARY KEY  (ID),
  UNIQUE KEY ID_2 (ID),
  KEY ID (ID)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

#
# Daten für Tabelle `thread`
#

INSERT INTO thread VALUES (1, 1);
INSERT INTO thread VALUES (2, 1);
INSERT INTO thread VALUES (3, 2);
# --------------------------------------------------------

#
# Tabellenstruktur für Tabelle `user`
#

CREATE TABLE `user` (
  ID int(11) unsigned NOT NULL auto_increment,
  Name char(255) NOT NULL default '',
  PRIMARY KEY  (ID),
  UNIQUE KEY ID_2 (ID),
  KEY ID (ID)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

#
# Daten für Tabelle `user`
#

INSERT INTO user VALUES (1, 'hpvw');
INSERT INTO user VALUES (2, 'Datic');
#Der Esel nennt sich immer zuerst, oder so ähnlich.
Und hier das Query:
Code:
SELECT 

#Auswahl der jeweiligen Spalten
#Mit Aliasnamen, da sich
#Titel und ID in den Tabellen
#überschneiden.
forum.ID AS ForumID,
forum.Titel AS ForumTitel,

#Hier kommt der erklärte schmutzige Teil
#Wenn man auf die Klammern achtet, steigt 
#man da auch hinter.
SUBSTR(MAX(CONCAT(posting.Zeitpunkt,thread.ID    ))
    ,LENGTH(posting.Zeitpunkt)+1) AS ThreadID,
SUBSTR(MAX(CONCAT(posting.Zeitpunkt,posting.ID   ))
    ,LENGTH(posting.Zeitpunkt)+1) AS PostingID,
SUBSTR(MAX(CONCAT(posting.Zeitpunkt,posting.Titel))
    ,LENGTH(posting.Zeitpunkt)+1) AS PostingTitel,
MAX(posting.Zeitpunkt) AS PostingDatum,
SUBSTR(MAX(CONCAT(posting.Zeitpunkt,user.ID      ))
    ,LENGTH(posting.Zeitpunkt)+1) AS AutorID,
SUBSTR(MAX(CONCAT(posting.Zeitpunkt,user.Name    ))
    ,LENGTH(posting.Zeitpunkt)+1) AS AutorName,

#Zähle alle Zeilen mit unterschiedlicher 
#PostingID in der Gruppierung
COUNT(DISTINCT posting.ID) AS AnzahlPostings,

#Zähle alle Zeilen mit unterschiedlicher 
#ThreadID in der Gruppierung
COUNT(DISTINCT thread.ID) AS AnzahlThreads

#Als Basis dient die Tabelle Forum
FROM forum 

#Kreuzprodukt mit Tabelle Thread unter beachtung
#des Kriteriums hinter ON
LEFT JOIN thread ON (thread.ForumID=forum.ID)

#Das gleiche zwischen Thread und Posting
LEFT JOIN Posting ON (posting.ThreadID=thread.ID)

#Und noch mal zwischen Posting und User
LEFT JOIN User ON (posting.AutorID=user.ID)

#Gruppiere nach der Forums-ID, da
#für jedes Forum nur eine Zeile dargetellt
#werden soll
GROUP BY Forum.ID

Bei Deiner Art, Titel zu vergeben, müsstest Du jedes posting.Titel in thread.Titel ändern. Das sollte eigentlich funktionieren.

Zu Deinem Flash-PHP-Interaktions-Problem kann ich Dir leider nichts konkretes sagen, da ich von Flash nichts verstehe. Aber einen Ansatz will ich trotzdem mal rausposaunen:
Normalerweise sendet das Frontend, sei es HTML oder Flash, nur die Inhalte eines Formulars. Das Frontend sendet keine Querys. Die Serverseitige Sprache, hier PHP, führt alle nötigen Querys durch und gibt ein Ergebnis zurück, normalerweise eine HTML-Seite, in Deinem Fall vermutlich nur Parameter für Flash. Vielleicht hilft Dir dieser Ansatz ja. Oder habe ich am Problem vorbeigeredet?

Gruß hpvw

EDIT: PS: Wenn Du ein Ergebnis hast, würde mich mal interessieren, wie ein Forum in Flash aussieht und zu benutzen ist. Könntest Du es dann Online stellen?
 
Zuletzt bearbeitet:
... jetzt weiß ich, wie man sich als Anfänger in einem Gebiet fühlt, der hofft, eine "einfache" Lösung für ein nicht ganz so simples Problem angeboten zu bekommen. ^^

Spaß beiseite: Ich verstehe Deine Abfrage, aber für den Anfang ist mir das noch etwas zu unübersichtlich.

Genau genommen sendet das Frontend auch keine Abfragen, aber da bei html-Seiten das Backend (PHP) ja Einfluss auf die Ausgabe hat (echo), habe ich normalerweise mehrere Scripte, die mehrere Abfragen und die dazugehörigen Ausgaben vornehmen (showthread.php, editpost.php, etc.). Die Darstellung muss ich bei Flash aber ausschliesslich dem Frontend überlassen, mir also nur die nackten Daten vom Backend besorgen.

Meine Idee war nun, nur sehr wenige (möglichst nur ein) PHP-Script(e) zu haben, das eine Abfrage ausführt und das Ergebnis als XML-Baum zurückgibt. Dazu übergebe ich dem Script per POST einige Variablen, z.B.:
Code:
table = "thread";
fields = "id, title";
filter = "id > 17";
order = "date";
, mit denen das Script die Abfrage zusammenbaut. Dazu kommen noch flags wie count = true, insert = true, wenn Manipulationen durchgeführt werden sollen oder ich nur eine Anzahl zurückbekommen will.

Ist eigentlich auch kein Problem, wenn ich mehrere Abfragen in einem Abwasch durchführen will; dann übergebe ich ein Array aus Parametern, das in PHP wieder zusammengesetzt wird. Lediglich meine XML-Struktur muss ich noch einmal überarbeiten - im Moment sieht eine Rückgabe z.B. so aus:
Code:
<recordset>
    <record>
        <field key="id">12</field>
        <field key="title">Mein Thread</field>
    </record>
</recordset>
... was sich als etwas umständlich im Handling erwies.

Wie gesagt, ich stehe noch ganz am Anfang, aber wenn ein vorzeigbares Ergebnis herauskommt, poste ich es natürlich. :)

Gruß
.
 
Datic hat gesagt.:
Code:
table = "thread";
fields = "id, title";
filter = "id > 17";
order = "date";
Das schaut mir nach dem Ansatz einer Blätterfunktion aus?
Ich würde keine konkreten Bedingungen übergeben, sondern nur einfache Parameter.
Beispiele:
Auflisten der Threads eines Forums (mit Blätterfunktion):
action="getThreads"
forumID="1"
offset=17 (siehe LIMIT)
order="date"

Auflisten der Foren:
action="getForen"

Anzeigen eines Threads:
action="getThread"
threadID="10"

Anlegen eines neuen Threads:
action="newThread"
titel="Bla"
inhalt="Hallo, ich wollte mal fragen..."

Anlegen eines neuen Post:
action="newPost"
threadID="2"
inhalt="Dann frag doch..."

Dinge, wie den angemeldeten User, sollte PHP z.B. selber herausfinden, ebenso das Datum und die Uhrzeit beim Eintragen.

Anstatt vom Flash, dem Frontend, konkrete Dinge (Felder) verlangen zu lassen, würde ich mir eher "Protokolle" entsprechend der oben angesprochenen Aktionen überlegen.
Also, wenn eine bestimmte Aktion verlangt wird, ist klar, welche Daten ankommen werden.

Nur so als Idee zur klareren Trennung von Code und Design.

Gruß hpvw
 
hmm... das erfordert allerdings ein etwas umfangreicheres Script auf PHP-Ebene und die entsprechenden Interfaces im Frontend. Mein Ansatz war (das gebe ich zu), so viel wie möglich in Flash zu erledigen. Auf der Frontend-Ebene brauche ich sowieso recht viel Code, da ich die Darstellung und Aufbereitung der Daten auf jeden Fall von dort aus vornehmen muss.

Dafür habe ich eine Klasse "Request", die ein Array aus Parametern übergeben bekommt und einer Callbackfunktion ein XML-Objekt zurückgibt. Das funktioniert im Moment eigentlich schon ganz gut. Das dazugehörige PHP-Script, welches alle Funktionen ausführen kann, ist ca. 75 Zeilen lang und lässt sich noch optimieren.

Dein Vorschlag hat etwas für sich, aber ich sehe nicht unbedingt einen großen Unterschied, ob ich in Flash nun sage:
Code:
action="getThreads"
forumID="1"
offset=17
order="date"
, oder eben doch
Code:
target = "thread";
action = "SELECT";
fields = "id, title, date";
filter = "forum_id=1 AND id>17";
order = "date";
, wenn die Daten, die zurückgegeben werden, doch immer das selbe Format haben. Die Umwandlung in Abfrage-"Brocken" übernimmt die Klasse. Statt im Backend verschiedene Blöcke wie "show_thread", "edit_post" anzulegen, lasse ich das von den entsprechenden MovieClips in Flash miterledigen; einen "show_thread_mc" und einen "edit_post_mc" benötige ich sowieso.

Allerdings: Ich bin im Moment durchaus noch in der Experimentier-Phase und es kann gut sein, dass ich den Aufbau noch ein paar Mal komplett ändere, um die Vor- und Nachteile der Modelle "in situ" testen zu können. ;)

Gruß

P.S.: Was ankommt, ist entweder so etwas:
Code:
<recordset source="thread">
  <record id="0">
    <id>17</id>
    <title>Testthread</title>
  </record>
  ...
<recordset>
...
, oder auch mal (bzw. zusätzlich) so etwas:
Code:
<amt source="thread">24</amt>
Was ankommen soll, wissen die jeweiligen Klassen/MCs im Frontend.
.
 
Zuletzt bearbeitet:
Datic hat gesagt.:
...wenn die Daten, die zurückgegeben werden, doch immer das selbe Format haben.
Das ist der Punkt. Daher würde ich eine Schnittstelle festlegen und die Felder nicht jedesmal angeben.
In Deinem Beispiel übergibst Du im Prinzip ja ein SQL-Query (es benötigt nur noch FROM und WHERE). Nach meiner Auffassung ist das Frontend für die Repräsentation der Daten und das Backend für die Beschaffung der Daten zuständig. Je schlanker die Schnittstelle ist, desto leichter lässt sich eins von beiden austauschen.
Kann ich daraus eigentlich schließen, dass Flash keine Datenbankverbindungen aufbauen kann?
Auch, wenn es sehr theoretisch klingt, will ich mal ein Beispiel nennen, warum ich diese striktere Trennung vornehmen würde:
Dein Flash-Frontend gefällt einem Bekannten oder Kunden von Dir super, aber er hat keinen Server mit Datenbankunterstützung. Du denkst: "Na gut, dann schreiben wir halt eine Datenhaltung auf XML-Basis." Da kannst du mit Schlüsselwörtern, wie SELECT nichts mehr anfangen. Die einfache Schnittstelle ist aber auch mit XML-Datenhaltung sinnvoll zu lesen.
Das ist für das erste gößere DB-Projekt (Da habe ich Dich doch richtig verstanden?) natürlich nicht so wichtig, aber wenn Du Code schreibst, den Du wiederverwenden willst, ist es hilfreich, etwas stärker zu abstrahieren.

Gruß hpvw
 
Hast ja recht.

Das Ding ist allerdings nicht nur mein erstes, sondern nicht mal ein großes Datenbankprojekt und soll mir eigentlich nur dazu dienen, zu sehen, ob und wie so etwas funktioniert. Im Moment bin ich weit davon entfernt, ein wiederverwertbares Flash-Frontend für ein Forum zu entwickeln (was sicher auch interessant wäre, aber dann eben ein etwas größeres Projekt würde).

Zum Umwandeln der Parameter in Queryschnipsel habe ich mir eine Profiler-Klasse überlegt, die je nach Anforderung die übergebenen Parameter für die Request-Klasse aufbereitet. Wenn jemand wirklich ein völlig anderes Datenmodell verwenden möchte (und ich gebe zu, zuerst in der Tat mit einer XML-Datenhaltung experimentiert zu haben ;) ), müssen so zumindest nur diese beiden Klassen angepasst werden.

Flash MX2004 professional stellt schon Klassen und Komponenten zur Datenanbindung zur Verfügung, mit denen ich mich aber noch nicht beschäftige habe. Ausserdem habe ich grade Lust, was simples eigenes zu schreiben. ;)

Das ganze ist zum einen eine Übung für mich und zum anderen eher auf dem Niveau eines bessern Gästebuchs als auf dem einer praxistauglichen Forensoftware.

Gruß

P.S.:
Da kannst du mit Schlüsselwörtern, wie SELECT nichts mehr anfangen
hehe, kann ich doch, da ich die Unterscheidung ja selbst im BackEnd vornehme. Was das Backend macht (ob SQL oder XML/Textbasiert), kann dem Frontend egal sein; bei Übergabe von "SELECT" erwartet es eine Auswahl Datensätze, bei "COUNT" eine Anzahl und bei "INSERT" bestenfalls eine OK-Meldung. Eigentlich habe ich nur der Übersicht wegen Schlüsselwörter gewählt, die mit SQL-Statements übereinstimmen.
.
 
Zuletzt bearbeitet:
Zurück