繁体   English   中英

计算同一表中的后代数量

[英]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。

看到: http : //sqlfiddle.com/#!9/a2d5e/1

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM