好的,从后端到前端工作......
您可以从 php 脚本调用单个非递归存储过程 (sproc),它会为您生成消息层次结构。这种方法的优点是你只需要制作一个SINGLE从 php 调用数据库,而如果您使用内联 SQL,那么您将进行与级别一样多的调用(至少)。另一个优点是,由于它是一个非递归存储过程,因此性能非常好,并且还可以使您的 php 代码保持美观和干净。最后,我必须郑重声明,调用存储过程比任何其他方法更安全、更高效,因为您只需向应用程序用户授予执行权限,并且存储过程需要的数据库往返次数比任何其他方法都要少。其他方法,包括参数化查询,单个查询需要至少 2 次调用(1 个用于在数据库中设置查询模板,另一个用于填充参数)
以下是从 MySQL 命令行调用存储过程的方法。
call message_hier(1);
这是它创建的结果集。
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
好的,现在我们有能力通过简单地使用我们需要的任何起始节点调用我们的存储过程来获取完整或部分消息树,但是我们将如何处理结果集?
在这个例子中,我决定用它生成一个 XML DOM,然后我需要做的就是转换 (XSLT) XML,我们将拥有一个嵌套消息网页。
PHP脚本
php 脚本相当简单,它只是连接到数据库,调用存储过程并循环结果集来构建 XML DOM。请记住,我们只调用数据库一次。
<?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输出
这是 php 脚本生成的 XML。如果您将此 XML 保存在文件中并在浏览器中打开它,您将能够展开和折叠级别。
<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>
现在,如果您愿意,您可以放弃构建 XML DOM 并使用 XSL 呈现网页,也许只是循环结果集并直接呈现消息。我选择这种方法只是为了使我的示例尽可能全面且信息丰富。
MySQL脚本
这是一个完整的脚本,包括表、存储过程和测试数据。
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);
这个答案的完整来源可以在这里找到:http://pastie.org/1336407 http://pastie.org/1336407。正如您已经注意到的,我省略了 XSLT,但您可能不会采用 XML 路线,如果您这样做,网络上也会有大量示例。
希望您觉得这有帮助:)
EDIT:
添加了更多数据,以便您拥有多个根消息 (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
现在,如果您只想获取特定于根节点的消息(起始消息),您可以调用原始存储过程,传入所需根节点的起始 msg_id。使用上面的新数据将是 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
您可以传入任何您喜欢的 msg_id,因此如果我想要 msg 1-2-2-1 下面的所有消息,那么您将传入 msg_id = 6:
call message_hier(6); -- returns all messages belonging to msg_id = 6
但是,如果您想要所有根的所有消息,那么您可以调用我创建的这个新存储过程,如下所示:
call message_hier_all(); -- returns all messages for all roots.
这样做的主要问题是,随着消息表的增长,它将返回大量数据,这就是为什么我专注于更具体的存储过程,该存储过程仅获取给定根节点或启动 msg_id 的消息。
我不会发布新的存储过程代码,因为它实际上与原始代码相同,但您可以在这里找到所有修改:http://pastie.org/1339618 http://pastie.org/1339618
您需要进行的最后更改是在 php 脚本中,该脚本现在将调用新的存储过程,如下所示:
//$result = $conn->query(sprintf("call message_hier(%d)", 1)); // recommended call
$result = $conn->query("call message_hier_all()"); // new sproc call
希望这可以帮助 :)
call message_hier_all();