[sql] Beitragszahl-Spalten in Foren unbegrenzter Tiefe aktualisieren

Holla.
Ich habe eine Frage, wie ich etwas bestimmtes am Besten durchführe
und habe gerade mal extra dafür ein Skript geschrieben, der sich nur
auf mein Problem konzentriert.

index.php
Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta http-equiv="Content-Style-Type" content="text/css" />
<meta name="robots" content="no-index, no-follow" />
<style type="text/css">
table
{
	margin: 10px auto;
	border: 3px solid #000;
}
</style>
<title>Problem-Beispiel</title>
<body>

<?php
// Datenbankverbindung
mysql_connect('localhost', 'root', '') or die('Konnte nicht zum Datenbankserver verbinden');
mysql_select_db('test') or die('Datenbank kann nicht benutzt werden');

error_reporting(E_ALL);

function die_errorquery($sql)
{
	echo sprintf('<p>Datenbankfehlermeldung [%d]</p>', mysql_errno());
	echo "<p>$sql</p>";
	echo '<p>' . mysql_error() . '</p>';
	exit;
}


if (!empty($_GET['t']))
	include('topic.php');
else
	include('forum.php');
?>

</body>
</html>

forum.php
Code:
<?php
// id des Aufenthaltsforums festlegen
$forum_id = !empty($_GET['f']) ? intval($_GET['f']) : 0;


// Daten des Aufenthaltsforums
$sql = "SELECT `sub`, `title` FROM `up_forums` WHERE `id`=$forum_id";

if (($res_this_forum = @mysql_query($sql)) === false)
	die_errorquery($sql);

if (mysql_num_rows($res_this_forum) != 1)
	die( ($forum_id == 0) ? 'Wurzelforum fehlt!' : 'Aufenthaltsforum existiert nicht!' );

$this_forum = mysql_fetch_assoc($res_this_forum);


// Navigationsleiste ausgeben
$next_run = $this_forum;
$next_run_id = $forum_id;

while (true)
{
	$navi[] = array
	(
		'title'	=>	$next_run['title'],
		'url'		=>	"index.php?f=$next_run_id"
	);


	// Nach dem Wurzelforum geht es nicht höher
	if ($next_run_id == 0) break;


	// Daten des nächst-übergeordneten Forums abfragen
	$next_run_id = $next_run['sub'];

	$sql = "SELECT `sub`, `title` FROM `up_forums` WHERE `id`=$next_run_id;";
	if (($res_next = @mysql_query($sql)) === false)
		die_errorquery($sql);

	if (mysql_num_rows($res_next) != 1)
	{
		// Das übergeordnete Forum existiert nicht -> Loser Ast
		$navi[] = array
		(
			'title'	=>	'Loser Ast',
			'url'		=>	"index.php?f=$next_run"
		);

		// Damit wäre der Aufbau der Navigationsleiste beendet
		break;
	}

	$next_run = mysql_fetch_assoc($res_next);
}

// und das Ganze in richtiger Unterordnungs-Reihenfolge ausgeben
echo '<fieldset><legend>Navigationsleiste</legend>
<label>';

$i = 0;
foreach (array_reverse($navi) as $item)
	echo '<div style="padding-left: ' . (20 * $i++) . 'px;"><a href="' . $item['url'] . '">' . $item['title'] . "</a></div>\n";

echo '</label>
</fieldset>';


// Unterforen des Aufenthaltsforums auflisten
echo '<table width="100%" border="1" cellpadding="2" cellspacing="2">
<tr>
	<th colspan="3">Unterforen</th>
</tr>
<tr>
	<th align="left">
		Forum
	</th>
	<th width="100" align="center">
		Themen
	</th>
	<th width="100" align="center">
		Beiträge
	</th>
</tr>';

$sql = "SELECT `id`, `stat_topics`, `stat_posts`, `title` FROM `up_forums` WHERE `sub`=$forum_id;";

if (($res_forums = @mysql_query($sql)) === false)
	die_errorquery($sql);

if (mysql_num_rows($res_forums) == 0)
	echo '<tr>
		<td colspan="3">Keine weiteren Unterforen</td>
	</tr>';

while ($forum = mysql_fetch_assoc($res_forums))
	echo '<tr>
		<td><a href="index.php?f=' . $forum['id'] . '">' . $forum['title'] . '</a></td>
		<td align="center">' . $forum['stat_topics'] . '</td>
		<td align="center">' . $forum['stat_posts'] . '</td>
	</tr>';

echo '</table>';


// Themen im Aufenthaltsforum auflisten
echo '<table width="100%" border="1" cellpadding="2" cellspacing="2">
<tr>
	<th colspan="2">Themen</th>
</tr>
<tr>
	<th align="left">
		Thema
	</th>
	<th width="100" align="center">
		Beiträge
	</th>
</tr>';

$sql = "SELECT `id`, `stat_posts`, `title` FROM `up_topics` WHERE `sub`=$forum_id;";

if (($res_topics = @mysql_query($sql)) === false)
	die_errorquery($sql);

if (mysql_num_rows($res_topics) == 0)
	echo '<tr>
		<td colspan="3">Keine Themen im Aufenthaltsforum</td>
	</tr>';

while ($topic = mysql_fetch_assoc($res_topics))
	echo '<tr>
		<td><a href="index.php?t=' . $topic['id'] . '">' . $topic['title'] . '</a></td>
		<td align="center">' . $topic['stat_posts'] . '</td>
	</tr>';

echo '</table>';
?>

topic.php
Code:
<?php
// Aufenthaltsthema abrufen
$topic_id = intval($_GET['t']);

$sql = "SELECT `sub`, `title` FROM `up_topics` WHERE `id`=$topic_id;";

if (($res_topic = @mysql_query($sql)) === false)
	die_errorquery($sql);

if (mysql_num_rows($res_topic) != 1)
	die('Thema existiert nicht');

$topic = mysql_fetch_assoc($res_topic);


// Navigationsleiste setzen
$navi = array(array(
	'title'	=>	$topic['title'],
	'url'		=>	"index.php?t=$topic_id"
));

$this_run = $topic['sub'];

while (true)
{
	// Jedes einzelne übergeordnete Forum nacheinander
	$sql = "SELECT `sub`, `title` FROM `up_forums` WHERE `id`=$this_run;";

	if (($res_sub = @mysql_query($sql)) === false)
		die_errorquery($sql);

	if (mysql_num_rows($res_sub) != 1)
	{
		// Das übergeordnete Forum existiert nicht -> Loser Ast
		$navi = array
		(
			'title'	=>	'Loser Ast',
			'url'		=>	"index.php?f=$this_run"
		);

		// Damit wäre der Aufbau der Navigationsleiste beendet
		break;
	}

	$sub = mysql_fetch_assoc($res_sub);


	$navi[] = array
	(
		'title'	=>	$sub['title'],
		'url'		=>	"index.php?f=$this_run"
	);

	// Nach dem Wurzelforum geht es nicht höher
	if ($this_run == 0) break;

	// Für den nächsten Durchlauf Id des von diesem Forum übergeordneten Forums setzen
	$this_run = $sub['sub'];
}

// und das Ganze in richtiger Unterordnungs-Reihenfolge ausgeben
echo '<fieldset><legend>Navigationsleiste</legend>
<label>';

$i = 0;
foreach (array_reverse($navi) as $item)
	echo '<div style="padding-left: ' . (20 * $i++) . 'px;"><a href="' . $item['url'] . '">' . $item['title'] . "</a></div>\n";

echo '</label>
</fieldset>';


// Vor dem Auflisten der Beiträge im Thema eventuell einen hinzufügen, falls Parameter mitgebracht
if (!empty($_POST['text']))
{
	$sql = "INSERT INTO `up_posts` (`sub`, `text`) VALUES ($topic_id, '" . addslashes($_POST['text']) . "');";

	if (@mysql_query($sql) === false)
		die_errorquery($sql);
}


// Beiträge im Thema auflisten
$sql = "SELECT `text` FROM `up_posts` WHERE `sub`=$topic_id ORDER BY `id` ASC;";

if (($res_posts = @mysql_query($sql)) === false)
	die_errorquery($sql);

echo '<table width="100%" border="1" cellpadding="2" cellspacing="2">';

if (mysql_num_rows($res_posts) == 0)
	echo '<tr>
		<td>Keine Beiträge</td>
	</tr>';

while ($post = mysql_fetch_assoc($res_posts))
	echo '<tr>
		<td>' . $post['text'] . '</td>
	</tr>';

echo '</table>';
?>

<form action="index.php?t=<?=$topic_id;?>" method="post">
	<textarea name="text"></textarea><br />
	<input type="submit" />
</form>

Hier die Datenbanktabellen
Code:
-- phpMyAdmin SQL Dump
-- version 2.11.1
-- http://www.phpmyadmin.net
--
-- Host: localhost
-- Erstellungszeit: 17. Dezember 2007 um 19:44
-- Server Version: 5.0.45
-- PHP-Version: 4.4.7

SET SQL_MODE="NO_AUTO_VALUE_ON_ZERO";

--
-- Datenbank: `test`
--

-- --------------------------------------------------------

--
-- Tabellenstruktur für Tabelle `up_forums`
--

CREATE TABLE `up_forums` (
  `id` int(10) unsigned NOT NULL default '0',
  `sub` int(10) NOT NULL default '0',
  `stat_topics` int(10) unsigned NOT NULL default '0',
  `stat_posts` int(10) unsigned NOT NULL default '0',
  `title` text collate latin1_general_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

--
-- Daten für Tabelle `up_forums`
--

INSERT INTO `up_forums` (`id`, `sub`, `stat_topics`, `stat_posts`, `title`) VALUES
(0, -1, 0, 0, 'wurzelforum'),
(1, 0, 0, 0, 'Multimedia'),
(2, 0, 0, 0, 'Literatur'),
(3, 0, 0, 0, 'Sport'),
(4, 1, 0, 0, 'Musik'),
(5, 1, 0, 0, 'Spiele');

-- --------------------------------------------------------

--
-- Tabellenstruktur für Tabelle `up_posts`
--

CREATE TABLE `up_posts` (
  `id` int(10) unsigned NOT NULL auto_increment,
  `sub` int(10) unsigned NOT NULL default '0',
  `text` text collate latin1_general_ci NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci AUTO_INCREMENT=4 ;

--
-- Daten für Tabelle `up_posts`
--

INSERT INTO `up_posts` (`id`, `sub`, `text`) VALUES
(1, 1, 'gg'),
(2, 1, 'hh'),
(3, 2, 'Mediamarkt. Ich bin doch nicht blöd.');

-- --------------------------------------------------------

--
-- Tabellenstruktur für Tabelle `up_topics`
--

CREATE TABLE `up_topics` (
  `id` int(10) unsigned NOT NULL,
  `sub` int(10) unsigned NOT NULL,
  `stat_posts` int(10) unsigned NOT NULL,
  `title` text collate latin1_general_ci NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1 COLLATE=latin1_general_ci;

--
-- Daten für Tabelle `up_topics`
--

INSERT INTO `up_topics` (`id`, `sub`, `stat_posts`, `title`) VALUES
(1, 4, 0, 'Infected Mushroom - Deeply Disturbed'),
(2, 1, 0, 'Mediamarkt - Sonderangebote');

In diesem kleinen Forum soll (ohne registriert oder eingelogt zu sein)
jeder was in einem Thema dazuschreiben können ohne title,
Zeitlimit pro IP etc. Selbst html und javascript ist erlaubt.

Jedes Forum kann Themen und/oder weitere Unterforen beinhalten.
Die sub-Spalte ist der Vaterzeiger zum übergeordneten, beinhaltenden
Forum. Soweit ok. Ich kann bei der Auflistung auch die Anzahl der
Foren und Themen pro aufgelisteten Unterforum abfragen
und mit jedem Unterforum mitauflisten in der htmltabellenspalte
'Themen', 'Beiträge'. Nur bräuchte es bei der unbegrenzten Tiefe
dicke Schleifen pro aufgelistete untergeordnete Tabelle.

Und die Anzahl der Themen eines noch weiter untergeordneten
Unterforums gehört zur Anzahl der Themen des Unetrforums
dazuaddiert. [Absolute Menge an Themen im Forum]

Nehmen wir das hier als Beispiel, wie es zu meinem Beispielskript auch ist:
Code:
wurzelforum (id 0)
***
 |
 +-- Multimedia
 |   ***
 |   |
 |   +-- Musik
 |   |
 |   +-- Spiele
 |
 +-- Literatur
 |
 +-- Sport
Liegen im Unterforum Multimedia nun 1 Beitrag in der Tabelle up_topics,
worin wiederum 5 beiträge sind und in dem Forum Musik sind auf der
selben Art 10 Beiträge und in Spiele 2 Beiträge, soll bei der Auflistung
der Unterforen beim root-Forum als Aufenthaltsforum Mltimedia
aufgelistet sein mit 17 in der Tabellenspalte 'Beiträge',
Literatur mit 0 und Sport mit 0.

Geht man aber in Multimedia rein, wo man sich nun befindet als
Aufenthaltsforum (working directory ähnlich) soll Musik mit
10 und Spiele mit 2 in der Liste stehen.

Mit einer Rekursiven Abzählfunktion in der Auflistungs Parsingzeit
ist das ja ganz leicht möglich. Aber der Datenbanktraffic springt ins Höhe
und an das Nested Sets-Prinzip möchte ich gar nicht erst denken!

Deshalb hatte ich mir überlegt, ob, sobald ein neuer Beitrag in
irgendein Thema reinkommt nach dem inserten der Beitragszeile
die statistik-Spalte stat_posts des beinhaltenden Themas aktualisiert
wird (`stat_posts` auch unter up_topics zu finden)
und durch eine Schleife von sql-Abfragen von SUM()-Funktionen
wäre, glaube ich am Besten.

Kann mir jemand topic.php soweit umschreiben, dass diese
Statistikspalten am performantesten aktualisiert werden?
Und sie sollen nicht bei jedem übergeordneten Forum
einfach nur um 1 erhöht werden. Sondern richtig aktualisiert.
Falls jemand in den Tabenbanktabellen rumpfuscht, soll ein
neuer Beitrag reichen, um die Statistikspalten der
übergeordneten Foren vollkommen zu korrigieren.

Das größte Problem ist, dass die Statistiken der untergeordneten Foren
auch ein Faktor spielen. Hoffe, irgendwer versteht, was ich meine.
Ich bekomme das überhaupt nicht durchgesetzt...

// ach, hab ein bisschen überlegt und die Lösung gefunden...
Code:
	/* UPDATE STATISTIKEN - START */

	function set_forum_subs($forum_id, &$allsubs)
	{
		$sql = "SELECT `id` FROM `up_forums` WHERE `sub`=$forum_id;";

		if (($res_subs = mysql_query($sql)) === false)
			die_errorquery($sql);

		while ($sub = mysql_fetch_assoc($res_subs))
		{
			$allsubs[] = $sub['id'];

			set_forum_subs($sub['id'], $allsubs);
		}
	}

	set_forum_subs($topic['sub'], $allsubs);

	$sql = 'SELECT COUNT(*) FROM `up_forums` WHERE `id` IN (' . implode(', ', $allsubs) . ');';

	if (($res_sum_stat_posts = @mysql_query($sql)) === false)
		die_errorquery($sql);

	$sql = 'UPDATE `up_forums` SET `stat_posts`=' . mysql_result($res_sum_stat_posts, 0) . ' WHERE `id`=' . $topic['sub'] . ';';

	if (@mysql_query($sql) === false)
		die_errorquery($sql);

	/* UPDATE STATISTIKEN - ENDE  */
Das war eigentlich das größte Problem.
Die Reihe der übergeordneten progt sich wie von selbst.
 
LOL!

Also das finde ich mal echt witzig. Da versucht man sich in das Problem reinzudenken, die ganzen Codes durchzuackern und sogar Lösungsansätze gedanklich zu formulieren, da stößt man am Ende auf ein: "Ach, hab's schon gelöst." :D
So verschwendet man am heiligen Abend seine Zeit, Göttlich!!!

Was ich sagen will ist: Ändere dein Thmentitel und mache darin deutlich, dass das Problem bereits gelöst ist, damit du anderen wie mir die Arbeit ersparst. ^^
 
Zurück
Oben