递归检查数据库中子项的父项

时间:2010-10-26 08:10:21

标签: php mysql kohana parent-child

我正在开发一个接收这样的网址的CMS系统:

  

/ parent1 / parent2 /儿童/

现在很容易只检查孩子,但在我看来,你也应该检查父母是否正确并且顺序正确。问题是我不确定如何做到这一点。

我正在使用mysql。这就是该表的外观:

CREATE TABLE IF NOT EXISTS `pages` (
  `id` int(11) NOT NULL auto_increment,
  `parent` int(11) NOT NULL default '0',
  `title` varchar(255) NOT NULL,
  `deleted` tinyint(1) NOT NULL default '0',
  PRIMARY KEY  (`id`),
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;

父字段保留在父字段中将用作父级的其他页面ID。

4 个答案:

答案 0 :(得分:1)

最好的方法是在一个查询中检索整个表并构建一个嵌套数组。使用php中的整个树结构,检查它们是否正确更容易。

在这篇博客上,有关于仅使用一个查询格式化多级菜单的信息:http://crisp.tweakblogs.net/blog/317/formatting-a-multi-level-menu-using-only-one-query.html

它背后的想法是你在php中递归地构建菜单。如果您能够更改数据库结构,还可以查看MPTT或嵌套集。使用此机制,可以更轻松地跟踪树中的父/子关系。缺点是插入或更新节点时MPTT较慢。更多信息:http://articles.sitepoint.com/article/hierarchical-data-database

答案 1 :(得分:1)

您可以更改SQL表结构以使用树的嵌套集模型;它可以更容易地测试包含可能深深嵌套在特定父级下的子项。

This page对邻接列表模型和嵌套集进行了很好的描述和比较。

您可能会发现嵌套设置问题的以下答案也很有用:Help with writing a SQL query for Nested Sets

拿起Joe Celko's Trees and Hierarchies in SQL for Smarties的副本。我一直推荐这本书,因为当我使用嵌套集在SQL中构建树结构时,它给了我极大的帮助。

答案 2 :(得分:0)

如果我理解你的要求你可以做这样的事情(只打电话给DB,而不是全部!)

可在此处找到完整脚本:http://pastie.org/1250062

希望它有帮助...:)

示例存储过程调用

call page_parents(5);

call page_parents(7);

示例PHP脚本

  <?php

function hasValidParents($conn, $urls, $pageID){
    $parents = array();
    $valid = true;

    //needs additional validation

    $sproc = sprintf("call page_parents(%d)", $pageID);

    $result = $conn->query($sproc);

    while($row = $result->fetch_assoc()) $parents[] = $row["page_id"];
    $result->close();   

    foreach($urls as $url)
        if($url && !in_array($url,$parents)){ $valid=false; break; }

    return $valid;
}

$urls = explode("/", "1/3/5"); // trim leading /

$conn = new mysqli("localhost", "foo_dbo", "pass", "foo_db", 3306);

echo hasValidParents($conn, $urls, $urls[count($urls)-1]) ? "true" : "false";

$conn->close();

?>

<强> SQL

-- TABLES

drop table if exists pages;
create table pages
(
page_id smallint unsigned not null auto_increment primary key,
title varchar(255) not null,
parent_page_id smallint unsigned null,
key (parent_page_id)
)
engine = innodb;

-- TEST DATA

insert into pages (title, parent_page_id) values
('Page 1',null), 
('Page 2',null), 
   ('Page 1-2',1), 
      ('Page 1-2-1',3), 
      ('Page 1-2-2',3), 
   ('Page 2-1',2), 
   ('Page 2-2',2);


-- STORED PROCEDURES

drop procedure if exists page_parents;

delimiter #

create procedure page_parents
(
in p_page_id smallint unsigned
)
begin

declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;

create temporary table hier(
 parent_page_id smallint unsigned, 
 page_id smallint unsigned, 
 depth smallint unsigned default 0
)engine = memory;

insert into hier select parent_page_id, page_id, v_depth from pages where page_id = p_page_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 pages pg inner join hier on pg.page_id = hier.parent_page_id and hier.depth = v_depth) then

        insert into hier 
            select pg.parent_page_id, pg.page_id, v_depth + 1 from pages pg
            inner join tmp on pg.page_id = tmp.parent_page_id and tmp.depth = v_depth;

        set v_depth = v_depth + 1;          

        truncate table tmp;
        insert into tmp select * from hier where depth = v_depth;

    else
        set v_done = 1;
    end if;

end while;

select 
 pg.page_id,
 pg.title as page_title,
 b.page_id as parent_page_id,
 b.title as parent_page_title,
 hier.depth
from 
 hier
inner join pages pg on hier.page_id = pg.page_id
left outer join pages b on hier.parent_page_id = b.page_id
order by
 hier.depth, hier.page_id;

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

-- TESTING (call this stored procedure from php)

call page_parents(5);
call page_parents(7);

答案 3 :(得分:0)

好的,我制定了自己的方法,请不要使用kohana,所以我正在使用kohana的查询构建器:

这段代码构建了我想要使用的数组。

public function build_array($parent = 0, $data = null)
{
    if(!$data)
    {
        $result = db::select('*')
            ->from($this->_table_name)
            ->as_assoc()
        ->execute($this->_db);

        foreach($result as $page)
        {
            $data['items'][$page['id']] = $page;
            $data['parents'][$page['parent']][] = $page['id']; 
        }
    }

    if (isset($data['parents'][$parent]))
    {
        $array = array();
        foreach ($data['parents'][$parent] as $item)
        {
            $array[$data['items'][$item]['slug']] = array(
                'id' => $data['items'][$item]['id'],
                'subitems' => $this->build_array($item, $data)
            );
        }
        return $array;
    }
}

如果父母错了,这段代码会通过数组运行url:

public function get_id($page, $parents)
{    
    $array = $this->build_array();

    if(!empty($parents[0]))
    {
        foreach($parents as $parent)
        {
            $array = $array[$parent]['subitems'];
        }
    }

    return $array[$page]['id'];
}

注意:您需要发送到此功能的数据是:

$page = 'child'; 
$parent = 'parent1/parent2';