假设我有一张如下表:
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
循环,这样做的好方法是什么?
答案 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);