从 SQL 查询结果构建父/子数组菜单结构

Posted

技术标签:

【中文标题】从 SQL 查询结果构建父/子数组菜单结构【英文标题】:Building a parent/child array menu structure from SQL query result 【发布时间】:2018-01-16 09:42:11 【问题描述】:

我需要使用 mysql DB 查询动态构建复杂的菜单结构。该查询允许定义用户有权使用和查看的菜单项。 Menu 结构以经典的父/子关系存储在结果集中,其中每个元素都有自己的 id 并依赖于其父 id。 Parent id = 0 表示该元素上方没有父母(它是根):

MNU_ID    MNU_FUNC    MNU_PARENT    MNU_ICON    MNU_TITLE_IT    MNU_TITLE_EN
----------------------------------------------------------------------------
  1       FLTMGR      0             home        STATO FLOTTA    FLEET STATUS
  2       PSTN        0             map-marker  POSIZIONI       POSITIONS
  3       RTS         0             road        PERCORSI        ROUTES
  4       CHRTS       0             line-charts DIAGRAMMI       CHARTS
  ...
  13      MNLS        0             book        MANUALI         MANUALS
  14      RGLTNS      0             bank        NORMATIVE       REGULATIONS
  16      SPD         4             tachometer  VELOCITA'       SPEED
  17      ALT         4             area-chart  ALTITUDINE      ALTITUDE
  18      DST         4             exchange    DISTANZA        DISTANCE
  ...
  32      INSTL       13            book        INSTALLAZIONE   SETUP
  33      BASE        32            wrench      BASE            BASE
  34      FLPR        32            wrench      SONDA CARB.     FUAL PROBE

所以你可以看到元素 33 和 34 在元素 32 之下,而元素 32 在元素 13 之下,最后元素 13 没有父元素,因为它是根元素(它的 MNU_PARENT 为 0)。 话虽如此,我已经开发出我的代码来返回以下内容:

Array(
[FLTMGR] => Array(
    [icon] => fa fa-home
    [title] => STATO FLOTTA
    ),
[PSTN] => Array(
    [icon] => fa fa-map-marker
    [title] => POSIZIONI
    ),
[RTS] => Array(
    [icon] => fa fa-road
    [title] => PERCORSI
    ),
[CHRTS] => Array(
    [icon] => fa fa-line-charts
    [title] => DIAGRAMMI
    [sub] => Array(
            [SPD] => Array(
                [icon] => fa fa-tachometer
                [title] => VELOCITÁ
                ),
            [ALT] => Array(
                [icon] => fa fa-area-chart
                [title] => ALTITUDINE
                ),
            [DST] => Array(
                [icon] => fa fa-exchange
                [title] => DISTANZA
                ),
            [GSLN] => Array(
                [icon] => fa fa-tint blink
                [title] => CARBURANTE
                )
            )
        ),
...
[MNLS] => Array(
    [icon] => fa fa-book
    [title] => MANUALI
    [sub] => Array(
        [INSTL] => Array(
            [MNU_ID] => 32
            [MNU_FUNC] => INSTL
            [MNU_PARENT] => 13
            [icon] => fa fa-book
            [title] => INSTALLAZIONE
            [sub] => Array(
                [0] => Array(
                    [MNU_ID] => 33
                    [MNU_FUNC] => BASE
                    [MNU_PARENT] => 32
                    [icon] => fa fa-wrench
                    [title] => BASE
                    ),
                [1] => Array(
                    [MNU_ID] => 34
                    [MNU_FUNC] => FLPR
                    [MNU_PARENT] => 32
                    [icon] => fa fa-wrench
                    [title] => SONDA CARB.
                    )
                )
            )
        )
    ),
[RGLTNS] => Array( 
    [icon] => fa fa-bank
    [title] => NORMATIVE
    )
)

但是,如您所见,我无法在第一层生成正确的结构。换句话说,如果您查看 MNLS 下的 INSTL 元素,则会出现以下错误:

    项目 MNU_ID,MNU_FUNC,MNU_PARENT 不应存在(参见其他项目) “sub”下的项目与1中的错误相同。 “sub”下的项目应由 BASE、FLPR 而不是 0 和 1 标识

所以预期的结构应该如下:

Array(
[FLTMGR] => Array(
    [icon] => fa fa-home
    [title] => STATO FLOTTA
    ),
[PSTN] => Array(
    [icon] => fa fa-map-marker
    [title] => POSIZIONI
[RTS] => Array(
    [icon] => fa fa-road
    [title] => PERCORSI
    ),
[CHRTS] => Array(
    [icon] => fa fa-line-charts
    [title] => DIAGRAMMI
    [sub] => Array(
            [SPD] => Array(
                [icon] => fa fa-tachometer
                [title] => VELOCITÁ
                ),
            [ALT] => Array(
                [icon] => fa fa-area-chart
                [title] => ALTITUDINE
                ),
            [DST] => Array(
                [icon] => fa fa-exchange
                [title] => DISTANZA
                ),
            [GSLN] => Array(
                [icon] => fa fa-tint blink
                [title] => CARBURANTE
                )
            )
        ),
...
[MNLS] => Array(
    [icon] => fa fa-book
    [title] => MANUALI
    [sub] => Array(
        [INSTL] => Array(
            [icon] => fa fa-book
            [title] => INSTALLAZIONE
            [sub] => Array(
                [BASE] => Array(
                    [icon] => fa fa-wrench
                    [title] => BASE
                    ),
                [FLPR] => Array( 
                    [icon] => fa fa-wrench
                    [title] => SONDA CARB.
                    )
                )
            )
        )
    ),
[RGLTNS] => Array(
    [icon] => fa fa-bank
    [title] => NORMATIVE
    )
)

现在是代码:

// $MenuDB contains the Menu structure returned by the DB

// Build the basic structure
$new = array();
foreach ($MenuDB as $a)
    $new[$a['MNU_PARENT']][] = $a;
    

// Calls the recursive function CreateTree 
$tree = createTree($new, $new[0]);  

// Make final correction (remove unwanted items and replace index with keys)
$b=replaceKeys($tree);

print_r($b);
exit();

function replaceKeys(array $input)     
    foreach($input as $key => &$val)                   // Scan the input array, each element will go in $val, the key will be $key
        $input[$val['MNU_FUNC']]=$input[$key];          // Replace index with key, the key is the value of the field MNU_FUNC
        if(is_numeric($key)) unset($input[$key]);       // Remove the item with numeric key (index) and leave the item with non-numeric index (key)
        unset($val['MNU_ID']);                          // Remove ID
        unset($val['MNU_PARENT']);                      // Remove Parent
        unset($val['MNU_FUNC']);                        // Remove Function
        if(isset($val['sub']))                         // avoid to work with undefined items
            if (is_array($val['sub']))                 // check if there are childs inside the 'sub' item
                $val['sub'] = replaceKeys($val['sub']); // if we have childs, do it again recursively
                unset($val['url']);                     // remove url element if we have childs
                unset($val['url_target']);              // remove url_target element if we have childs
                
            
        
    return $input;
    

function createTree(&$list, $parent)
    $tree = array();
    foreach ($parent as $k=>$l)
        if(isset($list[$l['MNU_ID']]))
            $l['sub'] = createTree($list, $list[$l['MNU_ID']]);
            
        $tree[] = $l;
         
    return $tree;
    

尽管我努力了,但我无法弄清楚错误在哪里。 我的工作流程有其他选择吗?

【问题讨论】:

你能用ajax吗...?而不是这个巨大的代码...... 不幸的是,我不能使用 Ajax 调用,因为这段代码在服务器端脚本上用于构建框架菜单。我唯一能做的就是 MySQL 查询和 php 脚本。 你能显示你从databese获得的var_dump数据吗? @J.Litvak $MenuDB 包含数据库中的数据,与您在我的帖子中看到的完全一样。 【参考方案1】:

你只能使用一个递归函数:

function makeTree($array, $parent) 
    $return = [];
    foreach ($array as $key => $value) 
        if ($value['MNU_PARENT'] == $parent) 
            $return[$value['MNU_FUNC']] = [
                'icon' => 'fa fa-' . $value['MNU_ICON'],
                'title' => $value['MNU_TITLE_IT'],
            ];
            $subs = false;
            foreach ($array as $search) 
                if ($search['MNU_PARENT'] == $value['MNU_ID']) 
                    $subs = true;
                
            
            if ($subs === true) 
                $return[$value['MNU_FUNC']]['subs'] = makeTree($array, $value['MNU_ID']);
            
        
    
    return $return;


$new = makeTree($arr, 0);

【讨论】:

你是对的! +1 您的代码完美运行,您的想法是正确的!我发现了问题:元素 INSTL 没有子元素,但它的元素有子元素!!!这就是递归停止在那里的原因。我也将发布我修改后的代码......【参考方案2】:

这段代码解决了这个问题:

$new = array();
foreach ($MenuDB as $a)
    $new[$a['MNU_PARENT']][] = $a;
    

$tree = createTree($new, $new[0]);  

print_r($tree).PHP_EOL;

exit();


function createTree(&$list, $parent)
    $tree = array();
    foreach ($parent as $k=>$l)
        if(isset($list[$l['MNU_ID']]))                       // check if current element has childs 
            $l['sub'] = createTree($list, $list[$l['MNU_ID']]); // build child structure inside 'sub'
            unset($l['url']);                                    // remove the 'url' item for elements having childs
            unset($l['url_target']);                             // remove the 'url_target' item for elements having childs
            
        unset($l['MNU_ID']);                                    // remove the 'MNU_ID' item not needed anymore
        unset($l['MNU_PARENT']);                                // remove the 'MNU_PARENT' item not needed anymore
        //$tree[] = $l;
        $tree[$l['MNU_FUNC']]=$l;                               // while $tree[] = $l; will transfer the elements array to $tree using index, this one will will transfer the elements array to $tree using the key $l['MNU_FUNC'] 
        unset($tree[$l['MNU_FUNC']]['MNU_FUNC']);               // remove the 'MNU_FUNC' item not needed anymore 
         
    return $tree;
    

这更短,甚至更胖!,我们绝对不需要两个段落,所以不再需要replaceKeys,我们可以在createTree 中做所有的事情。

【讨论】:

以上是关于从 SQL 查询结果构建父/子数组菜单结构的主要内容,如果未能解决你的问题,请参考以下文章

查询父项时如何获取猫鼬子文档数组中的值的聚合总和?

Laravel:嵌套查询连接导致子数组

Mysql 查询父菜单下所有的子菜单,Mysql迭代查询

使用 LINQ 从具有嵌套数组的类中获取子属性值和父属性值

如何在 SQL 子查询中获取字符串数组

选择数组、子查询和多行结果