计算同一表中

时间:2015-08-24 19:40:28

标签: php mysql sql inner-join

我有一张ID为ID的对象表,其中一些是基于其他对象的 为此我使用了一个名为path的字段,它列出了父母的字符串' ID

对象D(路径=" A,B,C")基于对象C,它基于B,基于A.

现在我想从所有对象中选择*,加上一个额外的列:count(后代)
(A有3(B,C和D)B有2(C和D),C有一(D) - D有零

"我"后代是path = 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

2 个答案:

答案 0 :(得分:2)

如果您需要经常查询,此表结构可能会出现问题。尽管MySQL有一种基本的读取方法,但很少建议在一个列中存储多个值。

根据您现有的要求,查询结果并非难以产生您想要的结果。使用LEFT JOIN使用不同的别名将本身加入表中,您可以使用MySQL的FIND_IN_SET() string function找到object内的path作为加入条件。

在加入之后,您可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

LIKE

在给出样本行的情况下,如果它正在运行并产生预期结果,则这是一个演示。 http://sqlfiddle.com/#!9/1fae7/1

现在,如果您的数据不像您的样本那样常规,那么可能仍然允许不完全遵循的路径,而只是将对象作为成员。添加额外的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, ''), '%') 两侧的路径以相同的方式启动,这意味着一条路径扩展另一条路径。

FIND_IN_SET()

只是为了验证结果是否相同,http://sqlfiddle.com/#!9/1fae7/15

请注意,使用EXPLAIN永远不会很快。这就是困难的原因 - MySQL没有很好的原生功能来分割字符串,也无法很好地利用索引。

附录:

我针对FIND_IN_SET()查询运行+------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+ | 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

最后,使用subselect的修改数据表示为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) | +------+-------------+-------+-------+---------------+------+---------+------+------+--------------------------------------------------------------+ ,MySQL可以更好地进行优化:

1) contents->add requirement
2) open the test case ->"test user-stories" tab
3) Links -> which adds a tested-by link for requirements

所有三个似乎都能够使用索引,但是您需要将它们与实际行集进行基准测试,以找出哪个最有效。重要的是,这些是针对最近的MariaDB版本运行的。如果你有一个较旧的MySQL,你的结果可能会有很大差异。

我发现修改原始数据以满足尾随逗号的要求有点令人反感。

答案 1 :(得分:1)

我假设你有一个表格,其中列为keyy,父亲为父母。我还假设父列中的每个父节点都被",#34;终止。 然后:

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