[英]Count number of descendants in the same table
我有一个带有ID的对象表,其中一些是基于其他对象的。
为此,我使用一个名为path的字段,其中列出了父母的ID字符串
对象D(path =“ A,B,C”)基于对象C,该对象C基于基于A的B。
现在,我要从所有对象中选择*,再加上一列:count(descendants)
(A有3个(B,C和D),B有2个(C和D),C只有一个(D)-D有零
“我的”后代是其路径= myPath + myID(+ more?)的对象数。
-这仅在SQL中可用(在PHP中不循环)吗?
O id = a .... path =“” .......... a有5个后代
O id = b .... path =“ a” ........ b有3
O id = c .... path =“ a,b” ..... c有1
O id = d .... path =“ a,b,c” .. d有0
O id = n .... path =“ a,b” ..... n为0
O id = x .... path =“ a” ........ x具有0
如果您需要经常查询该表结构,则可能会出现问题。 尽管MySQL具有读取它们内部的基本方法,但很少建议在单个列中存储多个值。
但是,考虑到您现有的需求,查询就不会很难产生想要的结果。 使用LEFT JOIN
使用不同的别名将表与其自身进行连接,您可以使用MySQL的FIND_IN_SET()
字符串函数将path
内的object
定位为连接条件。
连接之后,您可以从FIND_IN_SET()
进行COUNT()
匹配FIND_IN_SET()
,并且由于您使用了LEFT JOIN
,对于没有后代的人,它将返回0
。
SELECT
o.*,
-- Count matches from the joined table
COUNT(odesc.object) AS num_descendants
FROM
paths o
-- Self join with FIND_IN_SET()
LEFT JOIN paths odesc ON FIND_IN_SET(o.object, odesc.path)
GROUP BY o.object
给定您的示例行,这是一个演示示例,它可以正常工作并产生预期的结果。 http://sqlfiddle.com/#!9/1fae7/1
现在,如果您的数据不如样本规则,则可能仍允许路径不完全遵循,而只是将对象作为成员。 添加附加的LIKE
条件可以强制LEFT JOIN
两侧的路径以相同的方式开始,这意味着一条路径延伸了另一条路径。
LEFT JOIN paths odesc ON
FIND_IN_SET(o.object, odesc.path)
-- Additional condition to ensure paths start the same
AND odesc.path LIKE CONCAT(COALESCE(o.path, ''), '%')
只是为了验证结果是否相同, http://sqlfiddle.com/#!9 / 1fae7 / 15
请注意,使用FIND_IN_SET()
永远不会很快。 这就是造成这种困难的原因-MySQL没有很好的本机功能来拆分字符串,并且将无法很好地利用索引。
我针对FIND_IN_SET()
查询运行了EXPLAIN
,并在两列中的每列上都有一个索引:
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
| 1 | SIMPLE | o | index | NULL | path | 20 | NULL | 6 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | ox | index | NULL | path | 20 | NULL | 6 | Using where; Using index; Using join buffer (flat, BNL join) |
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
在更正源数据以使用尾部逗号和空字符串而不是NULL
之后,这是注释中从属子查询的解释:
EXPLAIN select paths.*, (select count(object) from paths ox where LEFT(ox.path,char_length( concat( paths.path, paths.object))) = concat(paths.path, paths.object ) )as descendants from paths;
+------+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
| 1 | PRIMARY | paths | index | NULL | path | 20 | NULL | 6 | Using index |
| 2 | DEPENDENT SUBQUERY | ox | index | NULL | path | 20 | NULL | 6 | Using where; Using index |
+------+--------------------+-------+-------+---------------+------+---------+------+------+--------------------------+
最后,将带有子选择的修改后数据表示为LEFT JOIN
,MySQL可能会更好地对其进行优化:
EXPLAIN SELECT
paths.*,
COUNT(ox.object)
FROM
paths
LEFT JOIN paths ox
ON LEFT(ox.path,char_length(concat(paths.path, paths.object))) = concat(paths.path, paths.object)
GROUP BY paths.object;
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
| 1 | SIMPLE | paths | index | NULL | path | 20 | NULL | 6 | Using index; Using temporary; Using filesort |
| 1 | SIMPLE | ox | index | NULL | path | 20 | NULL | 6 | Using where; Using index; Using join buffer (flat, BNL join) |
+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+
这三个似乎都可以使用索引,但是您需要将它们与真实的行集进行基准比较,以找出最有效的行集。 重要的是,这些是针对最新的MariaDB版本运行的。 如果您使用的是较旧的MySQL,则结果可能会有很大差异。
我发现修改原始数据以满足尾随逗号的要求有些令人讨厌。
我假设您有一个带有ID的keyy列和带父母的双亲的表。 我还假定父母列中的每个父母都以“,”结尾。 然后:
select t.*,
(select count(*)
from t tt
where tt.parents between concat( t.parents , t.keyy ,',' )
and concat(t.parents , t.keyy ,',zzzzzzzzzz' ) )as descendants
from t
如果在父级列上有索引,则可以使用它。 也许您应该用更合理的替代zz。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.