OK arbetar från backend mot front-end...
Du kan anropa en enda icke-rekursiv lagrad procedur (sproc) från ditt php-skript som genererar meddelandehierarkin åt dig. Fördelen med detta tillvägagångssätt är att du bara behöver göra en SINGEL anrop från php till din databas, medan om du använder inline SQL kommer du att göra lika många anrop som det finns nivåer (minst). En annan fördel är att eftersom det är en icke-rekursiv sproc är den extremt presterande och den håller också din php-kod fin och ren. Slutligen, och jag måste säga det här för protokollet, att anrop av lagrade procedurer är säkrare och effektivare än någon annan metod eftersom du bara behöver GRANTA exekvera behörigheter till din appanvändare och lagrade procedurer kräver färre rundresor till databasen än någon annan andra metoder inklusive parametriserade frågor som kräver minst 2 anrop för en enda fråga (1 för att ställa in frågemallen i db, den andra för att fylla i parametrarna)
Så här är hur du skulle anropa den lagrade proceduren från MySQL-kommandoraden.
call message_hier(1);
och här är resultatuppsättningen den skapar.
msg_id emp_msg parent_msg_id parent_msg depth
====== ======= ============= ========== =====
1 msg 1 NULL NULL 0
2 msg 1-1 1 msg 1 1
3 msg 1-2 1 msg 1 1
4 msg 1-2-1 3 msg 1-2 2
5 msg 1-2-2 3 msg 1-2 2
6 msg 1-2-2-1 5 msg 1-2-2 3
7 msg 1-2-2-1-1 6 msg 1-2-2-1 4
8 msg 1-2-2-1-2 6 msg 1-2-2-1 4
Ok, så nu har vi möjlighet att hämta ett helt eller delvis meddelandeträd genom att helt enkelt anropa vår sproc med vilken startnod vi än behöver, men vad ska vi göra med resultatuppsättningen ??
I det här exemplet har jag bestämt att vi ska generera en XML DOM med den, sedan behöver jag bara transformera (XSLT) XML och vi kommer att ha en webbsida för kapslade meddelanden.
PHP-skript
PHP-skriptet är ganska enkelt, det ansluter bara till databasen, anropar sproc och loopar resultatuppsättningen för att bygga XML DOM. Kom ihåg att vi bara ringer in i db en gång.
<?php
// i am using the resultset to build an XML DOM but you can do whatever you like with it !
header("Content-type: text/xml");
$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);
// one non-recursive db call to get the message tree !
$result = $conn->query(sprintf("call message_hier(%d)", 1));
$xml = new DomDocument;
$xpath = new DOMXpath($xml);
$msgs = $xml->createElement("messages");
$xml->appendChild($msgs);
// loop and build the DOM
while($row = $result->fetch_assoc()){
$msg = $xml->createElement("message");
foreach($row as $col => $val) $msg->setAttribute($col, $val);
if(is_null($row["parent_msg_id"])){
$msgs->appendChild($msg);
}
else{
$qry = sprintf("//*[@msg_id = '%d']", $row["parent_msg_id"]);
$parent = $xpath->query($qry)->item(0);
if(!is_null($parent)) $parent->appendChild($msg);
}
}
$result->close();
$conn->close();
echo $xml->saveXML();
?>
XML-utgång
Detta är XML som php-skriptet genererar. Om du sparar denna XML i en fil och öppnar den i din webbläsare kommer du att kunna expandera och komprimera nivåerna.
<messages>
<message msg_id="1" emp_msg="msg 1" parent_msg_id="" parent_msg="" depth="0">
<message msg_id="2" emp_msg="msg 1-1" parent_msg_id="1" parent_msg="msg 1" depth="1"/>
<message msg_id="3" emp_msg="msg 1-2" parent_msg_id="1" parent_msg="msg 1" depth="1">
<message msg_id="4" emp_msg="msg 1-2-1" parent_msg_id="3" parent_msg="msg 1-2" depth="2"/>
<message msg_id="5" emp_msg="msg 1-2-2" parent_msg_id="3" parent_msg="msg 1-2" depth="2">
<message msg_id="6" emp_msg="msg 1-2-2-1" parent_msg_id="5" parent_msg="msg 1-2-2" depth="3">
<message msg_id="7" emp_msg="msg 1-2-2-1-1" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
<message msg_id="8" emp_msg="msg 1-2-2-1-2" parent_msg_id="6" parent_msg="msg 1-2-2-1" depth="4"/>
</message>
</message>
</message>
</message>
</messages>
Nu kan du avstå från att bygga XML DOM och använda XSL för att rendera en webbsida om du vill och kanske bara loopa resultatuppsättningen och rendera meddelandena direkt. Jag har helt enkelt valt den här metoden för att göra mitt exempel så omfattande och informativt som möjligt.
MySQL-skript
Detta är ett komplett skript inklusive tabeller, sprocs och testdata.
drop table if exists messages;
create table messages
(
msg_id smallint unsigned not null auto_increment primary key,
msg varchar(255) not null,
parent_msg_id smallint unsigned null,
key (parent_msg_id)
)
engine = innodb;
insert into messages (msg, parent_msg_id) values
('msg 1',null),
('msg 1-1',1),
('msg 1-2',1),
('msg 1-2-1',3),
('msg 1-2-2',3),
('msg 1-2-2-1',5),
('msg 1-2-2-1-1',6),
('msg 1-2-2-1-2',6);
drop procedure if exists message_hier;
delimiter #
create procedure message_hier
(
in p_msg_id smallint unsigned
)
begin
declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);
create temporary table hier(
parent_msg_id smallint unsigned,
msg_id smallint unsigned,
depth smallint unsigned
)engine = memory;
insert into hier select parent_msg_id, msg_id, v_dpth from messages where msg_id = p_msg_id;
/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */
create temporary table tmp engine=memory select * from hier;
while not v_done do
if exists( select 1 from messages e inner join hier on e.parent_msg_id = hier.msg_id and hier.depth = v_dpth) then
insert into hier select e.parent_msg_id, e.msg_id, v_dpth + 1
from messages e inner join tmp on e.parent_msg_id = tmp.msg_id and tmp.depth = v_dpth;
set v_dpth = v_dpth + 1;
truncate table tmp;
insert into tmp select * from hier where depth = v_dpth;
else
set v_done = 1;
end if;
end while;
select
m.msg_id,
m.msg as emp_msg,
p.msg_id as parent_msg_id,
p.msg as parent_msg,
hier.depth
from
hier
inner join messages m on hier.msg_id = m.msg_id
left outer join messages p on hier.parent_msg_id = p.msg_id;
drop temporary table if exists hier;
drop temporary table if exists tmp;
end #
delimiter ;
-- call this sproc from your php
call message_hier(1);
Den fullständiga källan för detta svar finns här :http://pastie.org/1336407 . Som du redan har noterat har jag utelämnat XSLT men du kommer förmodligen inte att gå XML-vägen och om du gör det finns det massor av exempel på webben.
Hoppas du tycker att detta är till hjälp :)
EDIT:
Lade till lite mer data så att du har mer än ett rotmeddelande (msg_ids 1,9,14).
truncate table messages;
insert into messages (msg, parent_msg_id) values
('msg 1',null), -- msg_id = 1
('msg 1-1',1),
('msg 1-2',1),
('msg 1-2-1',3),
('msg 1-2-2',3),
('msg 1-2-2-1',5),
('msg 1-2-2-1-1',6),
('msg 1-2-2-1-2',6),
('msg 2',null), -- msg_id = 9
('msg 2-1',9),
('msg 2-2',9),
('msg 2-3',9),
('msg 2-3-1',12),
('msg 3',null); -- msg_id = 14
Om du nu bara vill få de meddelanden som är specifika för en rotnod (startmeddelande) kan du anropa den ursprungliga lagrade proceduren som passerar i start-msg_id för roten du behöver. Att använda den nya datan ovan skulle vara msg_ids 1,9,14.
call message_hier(1); -- returns all messages belonging to msg_id = 1
call message_hier(9); -- returns all messages belonging to msg_id = 9
call message_hier(14); -- returns all messages belonging to msg_id = 14
du kan skicka in vilket msg_id du vill, så om jag vill ha alla meddelanden under msg 1-2-2-1 så skickar du in msg_id =6:
call message_hier(6); -- returns all messages belonging to msg_id = 6
Men om du vill ha alla meddelanden för alla rötterna kan du kalla denna nya sproc som jag har skapat enligt följande:
call message_hier_all(); -- returns all messages for all roots.
Det största problemet med detta är att när din meddelandetabell växer kommer den att returnera massor av data, vilket är anledningen till att jag fokuserade på en mer specifik sproc som bara hämtade meddelanden för en given rotnod eller startande msg_id.
Jag lägger inte upp den nya sproc-koden eftersom den är praktiskt taget densamma som originalet men du kan hitta alla ändringar här:http://pastie.org/1339618
Den sista ändringen du behöver göra är i php-skriptet som nu kommer att anropa den nya sproc enligt följande:
//$result = $conn->query(sprintf("call message_hier(%d)", 1)); // recommended call
$result = $conn->query("call message_hier_all()"); // new sproc call
Hoppas detta hjälper :)
call message_hier_all();