代码之家  ›  专栏  ›  技术社区  ›  Bojangles Vincent Baillet

MySQL层次结构数据抽取

  •  1
  • Bojangles Vincent Baillet  · 技术社区  · 15 年前

    我已经为一个问题挣扎了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 tutorial . 我想做的是选择一个论坛董事会和所有子论坛一级以下选定的论坛董事会(没有低)。理想情况下,查询将获得所选论坛的级别,而只通过董事会的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
    

    我希望你能理解我的意思。

    有人能帮忙吗?现在真让我恼火:(

    詹姆斯

    3 回复  |  直到 15 年前
        1
  •  0
  •   Alex Weinstein    15 年前

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

        2
  •  3
  •   mkj java seeker    9 年前

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

    通常,嵌套间隔算法很好,但有一个严重的缺点-如果您向树中添加一些内容,则需要大量重新计算。 一个很好的替代方法是Tropashko嵌套区间算法和连续分数-只是谷歌为它。用这种算法得到低于父级的一个级别是很自然的。此外,给定一个子对象,可以计算其所有父对象的所有数字,而无需访问数据库。

        3
  •  0
  •   Jon Black    15 年前

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

    完整脚本如下: http://pastie.org/1294143

    菲律宾比索

    <?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);