Několika úrovňové forum

Zdravím,
obracím se na vás s problémem, na kterej bohužel asi nestačím. Dal jsem tomu už hodně přemýšlení a nemůžu vymyslet syntaxi pro několika úrovňové forum (míněno na každý post je možné reagovat časově nezávisle).
Můj DB vzor je takovejto:
| f_posts | CREATE TABLE `f_posts` (
`poid` int(11) NOT NULL auto_increment,
`thr_id` int(11) NOT NULL default '0',
`rekid` int(11) NOT NULL default '0',
`nick` varchar(200) collate utf8_czech_ci NOT NULL default '',
`timestamp` int(15) NOT NULL default '0',
`text` text collate utf8_czech_ci NOT NULL,
PRIMARY KEY (`poid`),
KEY `rekid` (`rekid`,`nick`),
KEY `thr_id` (`thr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci |

| f_threads | CREATE TABLE `f_threads` (
`thrid` int(11) NOT NULL auto_increment,
`nazev` varchar(200) collate utf8_czech_ci NOT NULL default 'Unnamed',
PRIMARY KEY (`thrid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_czech_ci |

, kde rekid je rakce na určitou zprávu (poid).

Nicméně nejsem schopen dogoumat, jak mám z DB dostat ty posty.
Moje zabedněnost končí u :
Př.
Základní zpráva:
poid = 5, rekid = 0
Reakce 1 na ZZ
poid = 6, rekid = 5
Reakce 2 na Reakce 1
poid = 7, rekid = 6
Reakce 3 na ZZ:
poid = 8, rekid = 5

V tom případě,

1) když zavolám metodu, která mě vypíše reakce na ZZ, tak je můžu vypsat, ale už mezi ně nevepíšu tu Reakci na 1.
2) když zavolám metodu, která mě vepíše reakce na ZZ, a při vypísu první bych zavolal další pro reakci na první z těch vypsaných. To bych ale musel mít vzlášt metodu pro každou úroveň, což je nesmysl.
3) když zavolám metodu, která mě vepíše reakce na ZZ, a při vypísu první zavolám tu samou metodu atd. Nicméně krom toho, že se mě nedaří volat sebe sama (ač by to mělo jít ne? Jak jinak mít něco rekurzivně?), tak si nemyslím, že by po dosáhnutí nejvyžší úrovně se vracela zpět k vypsání druhé reakce na ZZ (v tomto případě).

Doufám, že se v tom dá vyznat. Dělám asi z komára velblouda nebo co. Hledal jsem to aji na netu, ale když člověk moc neví, jak to vlastně nazvat, tak se to blbě hledá:))

Jakkoukoli pomoc velice ocením!
hlavni tema = 1
vetev = 3
uroven = 2

hlavni tema = 1
vetev = 4
uroven = 2

hlavni tema - rozlisujes hlavni strom (autoincrement)
vetev - rozlisujes kazde vetveni (autoincrement)
uroven - rozlisujes uroven vetveni (hloubka)

Cili treba wz.cz
hlavni temata = HTML PHP ...
vetev v tematu = kdykoliv nekdo vytvori vetev, cili "Několika úrovňové forum" a pak dalsi reagovat na prispevek ...
uroven = uroven rozbaleni, aby jsi mohl rozbalit jen podvetve

Tim chci rici, ze 2 polozky ti stacit nebudou, pokud chces v SQL rychle vypsat cely podstrom
Já to řeším jinak a stačí mi k tomu jedna jediná položka:
pro každé fórum (hlavní téma) mám jednu tabulku.
větev i úroveň řeším tím, že do jednoho sloupce ukládám pozici v tomto tvaru:
1 - první příspěvek tématu
11 - první odpověď na první příspěvek
12 - druhá odpověď na první příspěvek
2 - druhý příspěvek tématu
21 - první odpověď na druhý příspěvek
... atd ...
já to řeším podobně jako tom, ale do té položky ukládám id rodičovského příspěvku. Pokud tam je null tak je to 1. úroveň.
Ano, tu se omlouvam, prakticka zkusenost schazi a zda se, ze jsem to cele domotal.
- staci ukladat ID prispevku na ktery se reagovalo
- ale ukladal bych i hloubku zanoreni, pro rychlejsi vyhledavani v SQL, aby nemusel projit vsechny prispevky
- a take bych ukladal hlavni tema, aby nemusel prochazet vsechny temata
Jenom jsem to tam domotal nesrozumitelnym zpusobem. :)
to Peta:
hlavně tvoje představa fora je odlišná od té, na kterou jsem hledal řešení.
ad - staci ukladat ID prispevku.... ---> to delam v rekid
ad - ale ukladal bych i hloubku zanoreni ---> ve skutečnosti to všechny příspěvky opravdu neprojíždí. B-tree indexy tak nefungujou. Vyhledají si sekvenci a až tak do jednolivých položek, pokud si své překládání DBA Oraclu pamatuju dobře :))
ad - a take bych ukladal hlavni tema, aby nemusel prochazet vsechny temata
---> temata jsou uložený v jiné tabulce a spojený klíčem...

to Tom:
bohužel se obávám, že i tvá nevystihuje, na co jsem se tak neohrabaně ptal. Nebo aspoň tak se mě to nezdá...
Mám zájem o forum, kde by bylo možný:
1 - první příspěvek tématu
11 - první odpověď na první příspěvek
1(11) - odpověď na první odpověd
1(111) - odpověď na odpověď na první odpověĎ
12 - druhá odpověď na první příspěvek
... atd... s neomezenýma úrovněma
Jak říkám. Mohl jsem to tvé nastíněné špatně pochopit...

to Marek:
jedině od tebe mám pocit, že jdeš správným směrem. Akorát jsem ztracenej , jak jsem nastinoval v tom, jak to z té DB dostat. Viz jak popisuju...

To forum, který chci, je dost neobvyklý a prakticky jsem ho jinde než na jedné stránce neviděl, i přesto jsem fakt přesvědčenej, že je to, to co chci :)
Kdyby jste měli někdo login na www.vseborec.cz, tak pochopíte, co chci.

Jsem pořád dost ztracenej :)))
mám nápad, abych se tady dál nesnažil neohrabaně vysvětlovat o jaký forum mě jde, tak přikládám screenshot :))

http://zasobarna.wz.cz/forum.JPG
To Jiří Janák:
tak to jsi mě špatně pochopil. Moje řešení dokáže přesně to,co chceš a počet odpovědí na odpovědi odpovědí je omezený pouze délkou pole, do kterého údaj o pozici ukládáš.
Příklad:
dejme tomu, že sloupec, s tím 1, 11, 12, 111, 2, 21, 22, ... se bude jmenovat "vlakno".
Délku pole "vlakno" nastavíš na 100. Tím docílíš toho, že odpovědi budou moci jít až do devadesátédeváté úrovně, což se mi zdá už dost ujetý =)

Toto fórum funguje i u mě na webu, jenom kvůli designu neodsazuji odpovědi, ale nechávám je v jedné lajně. Pokud by jsi chtěl, napiš mi na mejl (najdeš ho na mých stránkách) a případně podám více informací.

BTW: 99. úroveň znamená asi toto: re:re:re:...("re:" celkem 99 krát)...re: první přípěvek.
Jsem si říkal, že právě tebe asi musím špatně chápat, páč si myslím, že si to byl právě ty, kdo tady kdysi psal, že v rámci nějaký práce tě právě takový forum nechali napsat :)) ale možná si to pletu...
můj email je celou dobu v nicku :) ozvu se ti, páč si ještě pořád nejsem jistej, jak na to. Zkusím nad tím, ale prvně zapřemýšlet, ať tě zbytečně neotravuju.
ahaaaaa... a já to dám prostě akorát seřadit podle toho sloupce co ? bože to jee easyyyyy :)))
To jsem musel nějak špatně pochopit zas:) Páč kdyby to tak bylo, tak to bude za 1) mírná "prasečina" (odpadá fce indexů)
za 2) a hlavně by to přestalo od vlákna vyžšího než 10 fungovat :)

asi to bude ještě jinak...
výsledek by měl být takovýto :

http://karaya1.wz.cz/fronta/

ten kámoš, co to naprogramoval není až tak moc můj kámoš, a tak mi jen řekl, že mu to zabralo mega času, ač je to údajně jednoduchá rekurze...

podle vzhledu to odpovídá mýmu návrhu databáze a aji on mě ho odsouhlasil. Nicméně stále tápám, jak to z toho vytáhnout, takže kdyby jste měli ještě nějaké nápady...
bože já se zblázním, jak to bylo primitivní... a já vůl to tak hned na začátku zkoušel, ale tehdá se mě to nechtělo rozjet a spadl mě u toho vždycky webserver, tak jsem usoudil, že to tak nejde...

vypis(1);
function vypis($rekid) {
$result = mysql_query("SELECT poid,rekid FROM post WHERE rekid='$rekid';");
if (mysql_num_rows($result) > 0) {
while ($data = mysql_fetch_assoc($result)) {
$poid = $data['poid'];
$rekid = $data['rekid'];
echo $poid."-".$rekid."<br>";
vypis($poid);
}
} else {
echo "FINISHED";
}
}

kdyby to někoho zajímalo...
Tak hlavně že to máš ... mě to nedalo a otevřel jsem si zdrojáky mého fóra a už jsem přišel na to, jak jsem to řešil: SELECT `to`,`a`,`tohle` FROM `forum_table` ORDER BY `tree` & `colision` ASC;

Tudíž: mám ještě jeden sloupec, ve kterém mám určeno, ke kterému příspěvku kolizní odpověď patří.

BTW: máš to lepší, asi to použiju do nějakého updatu mého skriptu ;)
Aby jste neřekli, že jsem svině...nevadí mě sdílet ;-)

class Forum extends Graphic implements vykreslovaci{
protected $content;
protected $label = "<a href='?t=forum'>Diskuze</a>";
private $kolikrat;
private $DB;

function __construct() {
$this->DB = DB::getInstance();
}

public function isInitialized() {
$this->processVars();
if (Flag::ok()) {return TRUE; } else {return FALSE; }
}
private function isInputs($nick,$nadpis,$text) {
if (empty($nick) || empty($nadpis) || empty($text) ) {
$udaje[] = "Chybí následující údaje:";
if (empty($nick)) { $udaje[] = "Nevyplněný nick";}
if (empty($nadpis)) { $udaje[] = "Nevyplněný nadpis"; }
if (empty($text)) { $udaje[] = "Chybí text"; }
return $udaje;
} else {
return true;
}
}
protected function processVars() {
switch ($_GET['a']) {
case 1:
$this->showThreads();
break;
case 2:
$this->showPosts();
break;
case 3:
$this->showNovy();
break;
case 4:
$this->submitNovy();
break;
case 5:
$this->showNovyP();
break;
case 6:
$this->submitPost();
break;
default:
$this->showThreads();
break;
}
}

private function submitNovy() {
$nick = $_GET['nick'];
$nadpis = $_GET['nadpis'];
$text = $_GET['text'];
$inputcheck = $this->isInputs($nick,$nadpis,$text);
if ($inputcheck === true) {
$tid = $this->newThread($nadpis);
$this->insPost($tid,0,$nick,$text);
header("Location: index.php?t=forum");
} else {
$error = $inputcheck;
foreach ($error as $line) {
$this->content .= $line."<br>";
}
$this->content .= "<hr size=1>";
}
$this->showNovy();
}
private function submitPost() {
$tid = $_GET['tid'];
$nick = $_GET['nick'];
$poid = $_GET['poid'];
$text = $_GET['text'];
$inputcheck = $this->isInputs($nick,'nothere',$text);
if ($inputcheck === true) {
$this->insPost($tid,$poid,$nick,$text);
header("Location: index.php?t=forum&a=2&tid={$tid}");
} else {
$error = $inputcheck;
foreach ($error as $line) {
$this->content .= $line."<br>";
}
$this->content .= "<hr size=1>";
}
$this->showNovy();
}

// GET METHODS
private function getThreads() {
$result = $this->DB->qry("SELECT * FROM f_threads;");
if ($result) {
return $result;
} else {
Log::write("GET THREADS NULL");
Flag::stop();
}
}
private function getMaxPosts($tid) {
$result = $this->DB->qry("SELECT count(thr_id) as Amount FROM f_posts WHERE thr_id='$tid';");
$data = mysql_fetch_assoc($result);
return $data['Amount'];
}
private function getLastTime($tid) {
$result = $this->DB->qry("SELECT MAX(timestamp) as Last FROM f_posts WHERE thr_id='$tid';");
$data = mysql_fetch_assoc($result);
return $data['Last'];
}
private function getFirstPost($tid) {
$result = $this->DB->qry("SELECT * FROM f_posts WHERE thr_id='$tid' ORDER BY timestamp ASC LIMIT 1;");
$data = mysql_fetch_assoc($result);
return $data;
}
private function getPosts($tid) {
$result = $this->DB->qry("SELECT * FROM f_posts WHERE thr_id='$tid' ORDER BY timestamp ASC;");
return $result;
}
private function getTnazev($tid) {
$result = $this->DB->qry("SELECT nazev FROM f_threads WHERE thrid='$tid' LIMIT 1;");
$data = mysql_fetch_assoc($result);
return $data['nazev'];
}
//END GET
//SQL ORIENTED
private function newThread($subject) {
$subject = addslashes($subject);
$this->DB->qry("INSERT INTO f_threads VALUES('','$subject');");
$result = $this->DB->qry("SELECT thrid FROM f_threads WHERE nazev='$subject';");
$thrid = mysql_fetch_assoc($result);
return $thrid['thrid'];
}
private function insPost($tid,$rekid,$nick,$text) {
$nick = addslashes($nick);
$text = addslashes($text);
$cas = time();
$this->DB->qry("INSERT INTO f_posts VALUES('','$tid','$rekid','$nick','$cas','$text');");
}
//END SQL
//SHOW METHODS
private function showThreads() {
$result = $this->getThreads();
$this->content .= "
";
while ($thread = mysql_fetch_assoc($result)) {
$tid = $thread['thrid'];
$posts = $this->getMaxPosts($tid);
$tstamp = $this->getLastTime($tid);
$lastmsg = $this->getFirstPost($tid);

$lastmsgcas = date("H:i:s - m.d.Y",$lastmsg['timestamp']);
$cas = date("H:i:s - m.d.Y",$tstamp);

$this->content .= "<div class='threads'>
<h3>{$thread['nazev']}</h3>
<span class=podnadpis>(reakcí: ".($posts-1).", naposled: $cas)<span><br>
<i>{$lastmsg['text']}</i><br>
<a href='?t=forum&a=2&tid=$tid'>Prohlížet</a></div>";
}
$this->content .= "<a href='?t=forum&a=3>Nový příspěvek</a>";
}
private function showNovy() {
$nick = $_GET['nick'];
$nadpis = $_GET['nadpis'];
$text = $_GET['text'];
$this->content .= "
<table cols=2 width=500>
<form action='' method=GET>
<input type=hidden name=t value=forum>
<input type=hidden name=a value=4>
<tr>
<td width=60>Nick:</td>
<td><input type=text name=nick value='$nick' size=40></td>
</tr>
<tr>
<td>Nadpis:</td>
<td><input type=text name=nadpis value='$nadpis' size=40></td>
</tr>
<tr>
<td>Text:</td>
<td><textarea cols=50 rows=10 name=text>$text</textarea></td>
</tr>
<tr>
<td colspan=2><input type=submit value='Vložit nový'></td>
</tr>
</table>";
}
private function showNovyP() {
$tid = $_GET['tid'];
$poid = $_GET['poid'];
$nick = $_GET['nick'];
$text = $_GET['text'];
$this->content .= "
<table cols=2 width=500>
<form action='' method=GET>
<input type=hidden name=t value=forum>
<input type=hidden name=a value=6>
<input type=hidden name=poid value=$poid>
<input type=hidden name=tid value=$tid>
<tr>
<td width=60>Nick:</td>
<td><input type=text name=nick value='$nick' size=40></td>
</tr>
<td>Text:</td>
<td><textarea cols=50 rows=10 name=text>$text</textarea></td>
</tr>
<tr>
<td colspan=2><input type=submit value='Reagovat'></td>
</tr>
</table>";
}
private function showPosts() {
$tid = $_GET['tid'];
$tnazev = $this->getTnazev($tid);
$this->content .= "<h2>$tnazev</h2>";
$this->getReplies(0,$tid);
}

private function getReplies($rekid,$tid) {
$query = "SELECT * FROM f_posts WHERE rekid='$rekid' AND thr_id='$tid';";
$result = $this->DB->qry($query);
if (@mysql_num_rows($result) > 0) {
while ($post = mysql_fetch_assoc($result)) {
$poid = $post['poid'];
$rekid = $post['rekid'];
$time = $post['timestamp'];
$cas = date("H:i:m - d.m.Y",$time);
if ($rekid == 0) {
$this->content .= "\x0D\x0A"."<div id='base'> {$post['text']} <br>Autor: {$post['nick']},
<i>vloženo: $cas</i><br><a href='?t=forum&a=5&poid={$post['poid']}&tid=$tid'>
Reaguj</a>";
} else {
$this->content .= "\x0D\x0A"."<div class='navic'>{$post['text']} <br>Autor: {$post['nick']},
<i>vloženo: $cas</i><br><a href='?t=forum&a=5&poid={$post['poid']}&tid=$tid'>
Reaguj</a>";
}
$this->kolikrat++;
$this->getReplies($post['poid'],$tid);
}
} else {
for ($i=0;$i<$this->kolikrat;$i++) {
$this->content .= "</div>"."\x0D\x0A";
}
$this->kolikrat=0;
}
}
//END SHOW
}
jo není to bug proofed....evidentně jsem to zrovna rozjel :))
nj, jedna věc je datová reprezentace (jak teorie praví) n-nárního stromu v db, druhá věc je jak z tohoto chaosu rozumně dostat to, co chci vidět. I když nejsem zastáncem zapisování polohy uzlu přímo k uzlu, v tomto případě to razantně zjednoduší výpis.
I když mě teď napadá, že bude (možná) problém s tím, že si chci rozbalit jenom nějaké uzly.... ale asi ne.
= Jiří Janák (janak.webz.cz) =
V pripade velkych zdrojaku je dobre davat odkaz na stranku.
aaa.php -> aaa.txt
Jinak jako binarni strom nepouzivam, proto jsem to nekomentoval.
Pánové já jsem jenom student ekonomie, kterej má fanatickou zálibu v programování, takže tady s těma vašema teorie bohužel nemůžu polemizovat, jelikož jim takto podaným nerozumím. Holt jsem měl jít tenkrát do prváku jinam na VŠ :))
ad. velké zdrojáky - oká může být. Osobně teda nevidím moc důvod(zaplacávání DB?), páč se podobný zdrojáky normálně na sajtech vkládají, ale tak budiž...
Binární strom bude asi to, co jsem hledal co?

Každ potom, co jsem toto řešení ukázal týpkovi, co to řešil viz ten odkaz na jeho stránku, tak mě řekl, že to moje není použitelný nad velkýma db, ptž asi vidíte, že je to mrtě dotazů na databázi. Tak nevím o kolik rychlejší je to narvat skrz pár dotazů do několika rozměrnýho pole a vypsat to třeba z něho, bohužel mě chybí to teoretický pozadí, kde bych věděl, co jak a kde se děje:)) kdyby jste měli nějakej odkaz na sajt, kterej popisu procesy v pozadí, tak uvítám...
No, binární strom asi nechceš, protože binární strom, je něco co má kmínek a dvě větvičky, z každé větvičky jdou zase 2 větvičky (myšleno max 2), takové víceúrovňové fórum, je v podstatě úplně stejný systém jako systém adresářů na disku. Takže ne binární, ale n-nární strom. Co je strom víš, to vidíš každý den. To že se v určité výšce větví, taky vidíš každý den. Tak žádné výmluvy :-)
Velké zdrojáky jsou nepříjemné hlavně pro čtení. Víš kolikrát musím otočit kolečkem myši, abych ho přeskočil? ;-)
Navíc pokud provedeš výstup zdrojáku přes highlight_file(), dopřeješ divákům komfort zvýrazněných klíčových slov atd...

K samotné realizaci: (teď jenom taková zvědavost, co si myslí dotyčný, že je velká db?)
Vždycky je otázka, jak chceš, aby se výsledek zobrazoval. Rekurze nad SQL nebývá to pravé ořechové (i když třeba MSSQL 2005 už umí rekurzivní dotaz). Taky pokud bys to chtěl celé vypsat dostal by ses u většího počtu dat do problémů s nepřehledností.

Napadá mě třeba, vypsat všechna hlavní témata a na požádání rozvinout jeden uzel (uzel je to, kde se napojují odpovědi, kde se to celé větví, takové to [+])

Sám jsem realizoval jeno, kde se může rozbalit jenom jeden uzel (tedy i na více úrovní).

Pak podobný problém byl, když jem dělal menu, taky víceůrovňové. S tím jsem si pohrál a tohle umí rozbalit více úrovní. Problém je, že se vždycky načítá celé, což asi pro nějaké fórum nebývá rozumné.

Takže tak. Podle mě je ideální načíst jednu úrveň a pak na požádání rozbalit větvičku. Což může být dobré řešení i na terabytové db.
Hele hele. Psal jsem, že jsem student VŠE a ne, že jsem idiot, takže s větvičkama na mě laskavě nechoď :D...

Dotyčný velkou DB definoval od 4000 záznamů, což ve skutečnosti moc velký není. Co ale kritizoval byla forma, jakou to z té DB tahám. Páč 4000 záznamů by znamenalo 4000 dotazů (mysql_query()), což údajně není ono. Nicméně netušil, že moje řešení to opravdu nerozbaluje rovnou celý, ale mám to řešení tak, že zobrazuju jenom posty první úrovně, který mám ve formě uzlů a až po kliknutí na něj se to rozbalí. Více méně přesně, tak jak sis to představoval a nastínil řešení ty. Takže jsem rád, že alespoň někdo s mým řešením souhlasí ;-)
Větvičky: jednou jsme měli na ve škole nějakého zahraničního lektora a ten nám vysvětloval paralelní programování na prasečí farmě. Shodou okolností se tak jmenuje jedna z metod přidělování úkolů (pokud si dobře pamatuju, vy kdož to máte čerstvě v hlavě mě když tak opravte). Nevidím tedy nic špatného na stromečku z větvičkama, třeba i proto, že se podle stormečku ta struktura taky jmenuje. :-)

Řešení db: ono s počátku je důležité napsat něco, co funguje. Pak, pokud to nefunguje nebo je to pomalé, napsat něco elegantnějšího. Takto získané zkušenosti jsou k nezaplacení... Každé řešení, které funguje je dobré. Pravda, některá řešení jsou lepší. :-)