递归函数对于创建分层选择列表来说太慢了

时间:2017-02-28 13:45:54

标签: php mysql recursion

我有一个查询从数据库中获取Geo-Structure。

分类表(内部30.000行):

id  title          parent  type 
-------------------------------
1   germany             0     1
2   bavaria             1     2
3   upper bavaria       2     3
4   munich              3     4
6   italy               0     1
7   toscana             6     2
8   city florence       7     3
9   florence            8     4

分类语言表

cid language title
--------------------------
1   en-UK    germany
2   de-DE    deutschland

对象表:

id  title   landid  regionid    uregionid   cityid
--------------------------------------------------
1   o1           1         2            3        4
2   o2           1         2            3        4 
3   o3           6         7            8        9

MySQL查询:

SELECT c.id, c.title, l.title AS translated, c.type, c.parent, count(c.id) as cnt
FROM category c
LEFT JOIN objects o ON (o.landid = c.id  OR o.regionid = c.id OR o.uregionid = c.id OR o.cityid = c.id) 
LEFT JOIN category_lang l ON l.cid = c.id AND l.language = "en-UK"
WHERE c.published = 1 AND o.published = 1
GROUP BY c.id
ORDER BY c.parent

我得到一个关联数组($ tree),其值如下:

Array
(
    [0] => Array
        (
            [id] => 1
            [title] => Germany
            [type] => 1
            [parent] => 0
            [cnt] => 1
        )

    [1] => Array
        (
            [id] => 6
            [title] => Italy
            [type] => 1
            [parent] => 0
            [cnt] => 1
        )

    [2] => Array
        (
            [id] => 2
            [title] => Bavaria
            [type] => 2
            [parent] => 1
            [cnt] => 1
        )

    [3] => Array
        (
            [id] => 7
            [title] => Toscana
            [type] => 2
            [parent] => 6
            [cnt] => 1
        )

    [4] => Array
        (
            [id] => 3
            [title] => Upper Bavaria
            [type] => 3
            [parent] => 2
            [cnt] => 1
        )

    [5] => Array
        (
            [id] => 8
            [title] => City Florence
            [type] => 3
            [parent] => 7
            [cnt] => 1
        )

    [6] => Array
        (
            [id] => 4
            [title] => Munich
            [type] => 4
            [parent] => 3
            [cnt] => 1
        )

    [7] => Array
        (
            [id] => 9
            [title] => Florence
            [type] => 4
            [parent] => 8
            [cnt] => 1
        )

)

然后我创建一个结构,准备显示选择列表:

public static function buildTree($tree, $root = 0) { 
        $return = array(); 
        foreach($tree as $child) { 
            if($child['parent'] == $root) { 
                $return[] = array( 
                    'name' => $child, 
                    'next' => self::buildTree($tree, $child['id']) 
                ); 
            } 
        } 
        return empty($return) ? null : $return;     
    } 

然后我将$ return内的结构发送到一个函数来创建最终的选择列表:

public static function buildSelect($tree, $s) {
        $option = '';
        if(!is_null($tree) && count($tree) > 0) {
            foreach($tree as $node) {
                $selected = '';
                $class_type = $node['name']['type'];                
                $option .= '<option value="'.$node['name']['id'].'" class="h'.$class_type.'" '.$selected.' data-type="'.$node['name']['type'].'">'
                            .$node['name']['title']. ' (' . $node['name']['cnt'] . ')</option>'
                            . self::buildSelect($node['next'], $s); 
            }
            return $option; 
        }    
    } 

这一切都有效,但如果Geo-Structure变得非常大,那么db查询会变得非常慢。 非常感谢有关如何提高速度的任何想法,谢谢!

2 个答案:

答案 0 :(得分:1)

基础知识似乎存在,如果没有设置大量测试数据,很难进行任何测试。

以下是一些小调整(您需要更改以应对您希望保持比较例程的位置)。可能还不足以解决问题。不过,我有兴趣知道它有什么影响,以及哪种方法花时间的一些时间,以及它如何提升: -

<?php

function cmp($a, $b)
{
    return strcmp($a["parent"], $b["parent"]);
}

usort($tree, "cmp");

$recursive_tree = self::buildTree($tree);

echo self::buildSelect($recursive_tree, '');

public static function buildTree(&$tree, $root = 0) 
{ 
    $return = array(); 
    foreach($tree as $child) 
    { 
        if($child['parent'] == $root) 
        { 
            $return[] = array( 
                'name' => $child, 
                'next' => self::buildTree($tree, $child['id']) 
            ); 
        }
        else
        {
            if ($child['parent'] > $root)
            {
                break;
            }
        }
    } 
    return empty($return) ? null : $return;     
} 


public static function buildSelect(&$tree, $s) 
{
    $option = '';
    if(!is_null($tree) && count($tree) > 0) 
    {
        foreach($tree as $node) 
        {
            $selected = '';
            $class_type = $node['name']['type'];                
            $option .= '<option value="'.$node['name']['id'].'" class="h'.$class_type.'" '.$selected.' data-type="'.$node['name']['type'].'">'
                        .$node['name']['title']. ' (' . $node['name']['cnt'] . ')</option>'
                        . self::buildSelect($node['next'], $s); 
        }
        return $option; 
    }    
}

是否必须构建输出,或者可以直接输出(甚至是临时文件)?

修改

如果你的对象表在landid,regionid,uregionid和cityid上有索引(即每个索引都有单独的索引),那么试试这个: -

SELECT c.id, c.title, c.type, c.parent, count(c.id) as cnt
FROM category c
LEFT JOIN objects o1 ON o1.landid = c.id AND o1.published = 1
LEFT JOIN objects o2 ON o2.regionid = c.id AND o2.published = 1
LEFT JOIN objects o3 ON o3.uregionid = c.id AND o3.published = 1
LEFT JOIN objects o4 ON o4.cityid = c.id AND o4.published = 1
WHERE c.published = 1 
AND (01.id IS NOT NULL
OR 02.id IS NOT NULL
OR 03.id IS NOT NULL
OR 04.id IS NOT NULL)
GROUP BY c.id, 
        c.title, 
        c.type, 
        c.parent
ORDER BY c.parent

编辑

在您的语言表中添加: -

SELECT c.id,
        c.title, 
        l.title AS translated, 
        c.type, 
        c.parent, 
        count(c.id) as cnt
FROM category c
LEFT OUTER JOIN category_lang l ON l.cid = c.id AND l.language = "en-UK"
LEFT OUTER JOIN objects o1 ON o1.landid = c.id AND o1.published = 1
LEFT OUTER JOIN objects o2 ON o2.regionid = c.id AND o2.published = 1
LEFT OUTER JOIN objects o3 ON o3.uregionid = c.id AND o3.published = 1
LEFT OUTER JOIN objects o4 ON o4.cityid = c.id AND o4.published = 1
WHERE c.published = 1 
AND (01.id IS NOT NULL
OR 02.id IS NOT NULL
OR 03.id IS NOT NULL
OR 04.id IS NOT NULL)
GROUP BY c.id, 
        c.title, 
        translated,
        c.type, 
        c.parent
ORDER BY c.parent

关于这个或UNION解决方案是否更好,这将主要归结为您的数据。例如,如果对象表中的4个id列可能有重复项,则此解决方案将会很困难(不太可能,因为我怀疑城市也可能是一个区域)。

答案 1 :(得分:1)

看看Nested Sets。将计算的tree_lefttree_righttree_depth添加到当前基于parent的模型中相当容易 - parent保留为主要数据,其他三个将在改变时重新计算。

然后,您可以使用更简单的选择来获取整个类别子树的项目,并且感谢tree_depth列,您可以轻松地在<select>元素或列表中计算正确的缩进任何递归。