MySQL层次结构数据提取

发布于 2024-10-02 08:32:04 字数 1944 浏览 3 评论 0原文

我现在已经在一个查询上苦苦挣扎了大约 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

它存储在 this 中推荐的结构中我想要做的是选择一个论坛板和所选论坛板下一级的所有子论坛(不低于),理想情况下,查询将获得所选论坛的级别,同时仅传递板的 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

我希望你能明白我的意思

吗?现在这真的让我很恼火: (

詹姆斯

I've been struggling for about 2 hours on one query now. Help? :(

I have a table like this:

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

It is stored in the structure recommended in this tutorial. What I want to do is select a forum board and all sub forums ONE level below the selected forum board (no lower). Ideally, the query would get the selected forum's level while only being passed the board's ID, then it would select that forum, and all it's immediate children.

So, I would hopefully end up with:

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     

Or

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     

The top rows here are the parent forums, the others sub forums. Also, I'd like something where a depth value is given, where the depth is relative to the selected parent form. For example, taking the last table as some working data, we would have:

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

Or

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

I hope you get my drift here.

Can anyone help with this? It's really getting me annoyed now :(

James

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

未蓝澄海的烟 2024-10-09 08:32:04

最简单的方法是添加一列来保留深度。
否则查询将非常低效 - 您将必须获得整个层次结构,按左编号排序(这将把第一个子放在第一个),将其连接到自身以确保对于每个下一个节点左编号等于前一个节点右数 + 1

一般来说,嵌套间隔算法很好,但有一个严重的缺点 - 如果向树添加某些内容,则需要进行大量重新计算。
一个不错的替代方案是带有连分数的 Tropashko 嵌套间隔算法 - 只需谷歌即可找到它。使用此算法获得低于父级的单个级别是非常自然的。此外,给定一个孩子,您可以计算其所有父母的所有数字,而无需访问数据库。

The easiest way for you to do it - just add a column where you keep the depth.
Otherwise the query will be very inefficient - you will have to get a the whole hierarchy, sorted by left number (that will put very first child be first), join it to itself to make sure that for each next node left number is equal to previous node right number + 1

In general, nested intervals algorithm is nice, but has a serious disadvantage - if you add something to tree, a lot of recalculations required.
A nice alternative for this is Tropashko Nested intervals algorithm with continued fractions - just google for it. And getting a single level below the parent with this algorithm is done very naturally. Also, given a child, you can calculate all numbers for all its parents without hitting a database.

痴意少年 2024-10-09 08:32:04

还需要考虑的一件事是,关系数据库实际上并不是存储分层数据的最优化和最自然的方式。像这里这样的结构(本质上是二叉树)会更容易用 XML blob 表示,您可以将其保留或作为对象存储在面向对象的数据库中。

One more thing to consider is that relational databases really are not the most optimal and natural way to store hierarchical data. A structure like you have here - a binary tree, essentially - would be much easier to represent with an XML blob that you can persist, or store as an object in an object-oriented database.

海螺姑娘 2024-10-09 08:32:04

我自己更喜欢邻接列表方法。以下示例使用非递归存储过程返回树/子树,然后将其转换为 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);

I prefer the adjacency list approach myself. The following example uses a non-recursive stored procedure to return a tree/subtree which I then transform into an XML DOM but you could do whatever you like with the resultset. Remember it's a single call from PHP to MySQL and adjacency lists are much easier to manage.

full script here : 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 Output

<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 Stuff

-- 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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文