查询树的项目数

时间:2012-10-04 19:56:03

标签: php mysql sql count hierarchy

假设我有一张如下表:

CREATE TABLE IF NOT EXISTS `node_list` (
    `nid` int(11) NOT NULL AUTO_INCREMENT,
    `parent` int(11)
        COMMENT \'Node`s parent (nid).\',
    PRIMARY KEY (`nid`)
)

对于给定的节点id,我想得到所有后代的计数。但是:

SELECT COUNT(*) FROM `node_list` WHERE `parent`=?

仅返回直系孩子的数量。如果没有一堆for循环,这样做的好方法是什么?

4 个答案:

答案 0 :(得分:1)

鉴于这些限制,没有办法。在这种情况下,您可以检索 all 树并构建一个'hill'客户端,或者执行递归查询,无论在特定情况下哪些都是最高效的。

如果具有固定数量的层次结构级别,则可以使用多个JOIN执行此操作。

在一般情况下,有几种结构修改可以克服这些约束。在实践中,您放松了约束“这是我的表结构”,允许添加其他字段。

例如,您可以使用left_id值补充节点结构,并确保在访问树深度优先时所有节点ID都按顺序排列:

1 --- 2 -+- 3 -+- 4
         |     |
         |     +- 5
         +- 6 --- 7

在这种情况下,节点3将存储值“5”,节点6将存储值“7”,节点2也将存储值“7”。 每个节点在LeftID中存储其子项的LeftID与其自己的ID 之间的最大值。

因此,无子节点的LeftID等于其ID。节点1将具有LeftID 7,因为它的LeftID为2,从6开始。

在这种情况下,如果序列中没有空洞,计算节点很容易;节点的所有后代都是那些ID在起始节点的ID和它的LeftID之间的节点;通过使LeftID等于ID来识别和叶子。

所以“从节点id 17下降的所有叶子”将是

选择孩子。*        FROM表AS父级        JOIN表AS孩子            ON(child.id> parent.id AND child.id< = parent.leftid)/ * Descendant /        WHERE child.id = child.leftid / Leaf /              AND parent.id = 17; / 父母是17

如果您希望能够进行修剪和分支,这种结构很难维护,因为您需要重新编号修剪点到分支点之间的所有节点,以及移动的节点。

如果您只对计数感兴趣,另一种可能性是保留儿童计数器。这可以通过迭代更新,选择所有叶子并将其计数器设置为0(通过LEFT JOIN识别叶子)来维持;然后所有那些有NULL计数器的父母都有子计数器非空计数器,将他们的计数器更新为儿童计数器的SUM()加上儿童自己的COUNT();并继续直到更新的行数变为零,因为所有节点都有非NULL计数器。在剪枝和分支之后,您只需将所有计数器设置为NULL并重复。

最后一种方法需要为每个层次结构级别进行反射连接。

答案 1 :(得分:0)

再多挖掘......

With Nodes As 
( 
Select s.nid, s.parent
From nodelist s
Where s.nid = @ParentID 
Union All 
Select s2.nid, s2.parent
From nodelist s2
    Join Nodes
        On Nodes.nid = s2.parent 
) 
Select Count(*)
From Nodes

答案 2 :(得分:0)

对于具有已知最大层数的层次结构,您可以使用级联连接执行单个查询以查找记录计数。如果层数大于三或四,这可能不太好,但应该可以工作。

select count(*)
from node_list n1
outer join node_list n2 on n2.parent = n1.nid
outer join node_list n3 on n3.parent = n2.nid
outer join node_list n4 on n4.parent = n3.nid

...依此类推,可以根据需要选择多个级别。尽量不要让它太多,否则性能可能会受损。

在现实世界中,大多数等级系统的深度实际上相当有限;即使它们在理论上是无限的。例如,站点菜单可能允许无限级别的结构,但超过三或四个它很难使用。您是否对嵌套施加限制取决于您,但它可能会使事情变得更容易。

但是,如果你确实有一个开放式的层次结构,你不知道它有多深,或者如果上面的查询太慢,那么你将需要一个循环。该循环是否在MySQL存储过程中,或者它是否在PHP中是无关紧要的;你需要一个循环方式。不过,它肯定不需要是你担心的for循环的混乱。

我会用递归的PHP函数来做。也许是这样的:

function countDescendants($db, $nid) {
    $total = 0;
    $query = "select nid from Nodes where parent = ".(int)$nid;
    $res = $db->query($query);
    foreach($res as $data) {
        $total += countDescendants($db, $data['nid']);
    }
    $total += $res->num_rows;
    return $total;
}

然后你可以用一行代码来调用它并得到答案:

$number_of_descendants = countDescendants($starting_nid);

一个相当简单的递归函数(我假设你正在为你的数据库使用mysqli并且你已经对你的连接进行了排序以传递给函数)。

当然,如果你有一个非常庞大的层次结构或者你多次查询它,它可能会有点慢,但有一些方法可以通过改进我已经给出的这个基本例子来加速它。例如,您可以使用预准备语句查询,并使用不同的nid值填充相同的语句:这将节省大部分数据库工作。但是对于小型层次结构的简单使用,上面的代码应该没问题。

任何这些技术的一个重大缺陷就是你的节点结构中有一个循环 - 即一个节点有一个自己的后代作为它的父ID。此方案将导致上述PHP代码无限循环,并且在嵌套连接SQL查询的情况下将导致记录计数严重偏差。在任何一种情况下,如果您的系统可能出现这种情况,您将需要对其进行编码。但这确实使事情变得复杂,所以我不会在这里讨论它。

希望有所帮助。

(注意:以上代码没有经过测试:我直接输入答案而不运行它;如果有任何错别字,请道歉)

答案 3 :(得分:0)

$tree = array();
$tree[0][1] = 'Electronics';    
$tree[0][0][1] = 'Mobile';  
$tree[0][0][0][2] = 'Samsung';  
$tree[0][0][0][0][3] = ' Toppings Flip Cover for Samsung Galaxy S6';    
$tree[0][0][0][0][0][4] = 'Maac Online Flip Cover for Samsung Galaxy S6';   
$tree[0][0][0][0][0][0][5] = 'Shine Flip Cover for Samsung Galaxy S6';  
$tree[0][0][0][0][0][0][0][6] = 'SNE Flip Cover for Samsung Galaxy S6';

print_r($tree);