繁体   English   中英

MySQL层次结构数据提取

[英]MySQL Hierarchical Structure Data Extraction

我已经在一个查询上苦苦挣扎了大约2个小时。 救命? :(

我有一张这样的桌子:

id    name                     lft      rgt        
35    Top level board          1        16     
37    2nd level board 3        6        15     
38    2nd level board 2        4        5     
39    2nd level board 1        2        3     
40    3rd level board 1        13       14     
41    3rd level board 2        9        12     
42    3rd level board 3        7        8     
43    4th level board 1        10       11

它存储在本教程推荐的结构中。 我要做的是选择一个论坛板,然后在所选论坛板下方选择所有子论坛(不低于此级别)。 理想情况下,该查询将在仅通过董事会ID的同时获得所选论坛的级别,然后将选择该论坛及其所有直接子级。

因此,我希望最终会得到:

id    name                     lft      rgt        
35    Top level board          1        16  

37    2nd level board 3        6        15     
38    2nd level board 2        4        5     
39    2nd level board 1        2        3

要么

id    name                     lft      rgt            
37    2nd level board 3        6        15  

40    3rd level board 1        13       14     
41    3rd level board 2        9        12     
42    3rd level board 3        7        8

这里最上面的行是父论坛,其他子论坛。 另外,我想要一个给出深度值的东西,其中深度是相对于所选父窗体的。 例如,将最后一张表作为一些工作数据,我们将有:

id    name                     lft      rgt      depth      
37    2nd level board 3        6        15       0

40    3rd level board 1        13       14       1
41    3rd level board 2        9        12       1
42    3rd level board 3        7        8        1

要么

id    name                     lft      rgt     depth      
35    Top level board          1        16      0

37    2nd level board 3        6        15      1
38    2nd level board 2        4        5       1
39    2nd level board 1        2        3       1

我希望你能得到我的帮助。

有人能帮忙吗? 现在真的让我很烦:(

詹姆士

最简单的方法-只需添加一列即可保持深度。 否则查询将非常无效率-您将必须获取按左数字排序的整个层次结构(将第一个子代放在第一位),将其连接到自身以确保每个下一个节点的左数等于上一个节点的权利号+1

通常,嵌套间隔算法不错,但有一个严重的缺点-如果将某些内容添加到树中,则需要进行大量重新计算。 一个不错的替代方法是使用Tropashko嵌套区间算法(带有连续分数)-只需使用google。 使用此算法将父级降至父级以下是很自然的事情。 同样,对于一个孩子,您可以计算其所有父母的所有数字,而无需访问数据库。

需要考虑的另一件事是关系数据库确实不是存储分层数据的最理想,最自然的方法。 像您这样的结构-本质上是一棵二叉树-可以用可以持久保存或作为对象存储在面向对象的数据库中的XML blob表示,要容易得多。

我更喜欢自己的邻接表方法。 以下示例使用非递归存储过程返回树/子树,然后将其转换为XML DOM,但您可以对结果集执行任何操作。 记住,这是从PHP到MySQL的一次调用,邻接表更易于管理。

完整脚本在这里: http : //pastie.org/1294143

PHP

<?php

header("Content-type: text/xml");

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

// one non-recursive db call to get the tree

$result = $conn->query(sprintf("call department_hier(%d,%d)", 2,1));

$xml = new DomDocument;
$xpath = new DOMXpath($xml);

$dept = $xml->createElement("department");
$xml->appendChild($dept);

// loop and build the DOM

while($row = $result->fetch_assoc()){

    $staff = $xml->createElement("staff");
    // foreach($row as $col => $val) $staff->setAttribute($col, $val); 

    $staff->setAttribute("staff_id", $row["staff_id"]); 
    $staff->setAttribute("name", $row["name"]); 
    $staff->setAttribute("parent_staff_id", $row["parent_staff_id"]); 

    if(is_null($row["parent_staff_id"])){
        $dept->setAttribute("dept_id", $row["dept_id"]); 
        $dept->setAttribute("department_name", $row["department_name"]); 
        $dept->appendChild($staff);
    }
    else{
        $qry = sprintf("//*[@staff_id = '%d']", $row["parent_staff_id"]);
        $parent = $xpath->query($qry)->item(0);
        if(!is_null($parent)) $parent->appendChild($staff);
    }
}
$result->close();
$conn->close();

echo $xml->saveXML();
?>

XML输出

<department dept_id="2" department_name="Mathematics">
    <staff staff_id="1" name="f00" parent_staff_id="">
        <staff staff_id="5" name="gamma" parent_staff_id="1"/>
        <staff staff_id="6" name="delta" parent_staff_id="1">
            <staff staff_id="7" name="zeta" parent_staff_id="6">
                <staff staff_id="2" name="bar" parent_staff_id="7"/>
                <staff staff_id="8" name="theta" parent_staff_id="7"/>
            </staff>
        </staff>
    </staff>
</department>

SQL资料

-- TABLES

drop table if exists staff;
create table staff
(
staff_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null
)
engine = innodb;

drop table if exists departments;
create table departments
(
dept_id tinyint unsigned not null auto_increment primary key,
name varchar(255) unique not null
)
engine = innodb;

drop table if exists department_staff;
create table department_staff
(
dept_id tinyint unsigned not null,
staff_id smallint unsigned not null,
parent_staff_id smallint unsigned null,
primary key (dept_id, staff_id),
key (staff_id),
key (parent_staff_id)
)
engine = innodb;

-- STORED PROCEDURES

drop procedure if exists department_hier;

delimiter #

create procedure department_hier
(
in p_dept_id tinyint unsigned,
in p_staff_id smallint unsigned
)
begin

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

create temporary table hier(
 dept_id tinyint unsigned,
 parent_staff_id smallint unsigned, 
 staff_id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select dept_id, parent_staff_id, staff_id, v_dpth from department_staff 
    where dept_id = p_dept_id and staff_id = p_staff_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 department_staff e 
            inner join hier on e.dept_id = hier.dept_id and e.parent_staff_id = hier.staff_id and hier.depth = v_dpth) then

        insert into hier select e.dept_id, e.parent_staff_id, e.staff_id, v_dpth + 1 from department_staff e 
            inner join tmp on e.dept_id = tmp.dept_id and e.parent_staff_id = tmp.staff_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 
 hier.dept_id,
 d.name as department_name,
 s.staff_id,
 s.name,
 p.staff_id as parent_staff_id,
 p.name as parent_name,
 hier.depth
from 
 hier
inner join departments d on hier.dept_id = d.dept_id
inner join staff s on hier.staff_id = s.staff_id
left outer join staff p on hier.parent_staff_id = p.staff_id;

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

end #

delimiter ;

-- TEST DATA

insert into staff (name) values 
    ('f00'),('bar'),('alpha'),('beta'),('gamma'),('delta'),('zeta'),('theta');

insert into departments (name) values
 ('Computing'),('Mathematics'),('English'),('Engineering'),('Law'),('Music');

insert into department_staff (dept_id, staff_id, parent_staff_id) values
(1,1,null), 
    (1,2,1), 
    (1,3,1), 
        (1,4,3),
            (1,7,4),
(2,1,null), 
    (2,5,1), 
    (2,6,1), 
        (2,7,6),
            (2,8,7),
            (2,2,7);

-- TESTING (call this sproc from your php)

call department_hier(1,1);

call department_hier(2,1);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM