admin

查询树的项数

sql

假设我有一个这样的表:

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循环的好方法是什么样子?


阅读 136

收藏
2021-06-07

共1个答案

admin

对于具有最大最大层数的层次结构,您可以使用级联联接执行单个查询以查找记录数。如果层数大于三或四,这可能不是很漂亮,但应该可以。

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查询的情况下,记录计数将严重偏斜。无论哪种情况,如果您的系统都可能出现这种情况,则需要针对它进行编码。但这确实使事情复杂化,所以我在这里不再赘述。

希望能有所帮助。

(注意:以上代码未经测试:我直接输入答案而不运行它;如果有错字,我们深表歉意)

2021-06-07