在父子表单中打印分层数据无序列表php?

时间:2010-11-30 20:06:05

标签: php mysql parent-child hierarchical-data html-lists

我在父子层次结构中的mysql表中有数据,如;

|---------+-----------+-------------|
| msg_id  | parent_id |    msg      |
|---------+-----------+-------------|
|       1 | NULL      |   msg1      |
|       2 | NULL      |   msg2      |
|       3 | NULL      |   msg3      |
|       4 | 1         | msg1_child1 |
|       5 | 1         | msg1_child2 |
|       6 | 3         | msg3_child1 |
|---------+-----------+-------------|

我需要以父子无序列表格式显示它,如

 -msg1 
   -msg1-child1
   -msg2-child2
 -msg2
 -msg3
   -msg3-child1

我该怎么办?我需要帮助,尤其是如何在表单的层次结构中显示它。

2 个答案:

答案 0 :(得分:1)

好的从后端到前端工作......

您可以从php脚本中调用单个非递归存储过程(sproc),为您生成消息层次结构。这种方法的优点是你只需要从php到你的数据库进行 SINGLE 调用,而如果你使用内联SQL那么你将调用尽可能多的调用(至少) 。另一个优点是,因为它是一个非递归的sproc,它非常高效,它还可以让你的PHP代码保持良好和干净。最后,我不得不说这个记录,调用存储过程比任何其他方法更安全,更有效,因为你只需要GRANT执行权限给你的应用程序用户和存储过程需要更少的往返数据库往返其他方法,包括参数化查询,需要至少2次调用单个查询(1在db中设置查询模板,另一个用于填充参数)

所以这就是你如何从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

好的,现在我们可以通过简单地调用我们的sproc来获取完整或部分消息树,无论我们需要什么起始节点,但我们将如何处理结果集?

在这个例子中,我已经决定用它生成一个XML DOM,然后我需要做的就是转换(XSLT)XML,我们将有一个嵌套的消息网页。

PHP脚本

php脚本非常简单,它只是连接到数据库,调用sproc并循环结果集来构建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脚本

这是一个完整的脚本,包括表格,sprocs和测试数据。

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。正如您已经注意到的那样,我已经省略了XSLT,但您可能不会使用XML路由,如果您这样做,那么Web上就会有大量示例。

希望您觉得这有用:)

修改

添加了更多数据,因此您有多条根消息(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

但是,如果你想要所有根的所有消息,那么你可以调用我创建的这个新的sproc,如下所示:

call message_hier_all(); -- returns all messages for all roots.

这个问题的主要问题是当你的消息表增长时它会返回大量数据,这就是为什么我专注于一个更具体的sproc,它只为给定的根节点提取消息或启动msg_id。

我不会发布新的代码,因为它几乎与原始代码相同,但你可以在这里找到所有的修改:http://pastie.org/1339618

您需要进行的最终更改是在php脚本中,现在将调用新的sproc,如下所示:

//$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();

答案 1 :(得分:0)

function get_list($parent='NULL', $counter=0, $spaces=""){

    $sql = "SELECT * FROM t1 WHERE parent_id = ".parent;
    $rs[$counter] = mysql_query($sql) or die(mysql_error());
    while($row[$counter] = mysql_fetch_array($rs[$counter])){
        echo $spaces.$row[$counter]['msg']."<br />";
        get_list($row[$counter]['parent_id'], $counter+1, "&nbsp;&nbsp;".$spaces);
    }
    mysql_free_result($rs[$counter]);
}

或者,接近那个。