无预定义父子ID时 MySQL5.7+PHP构建层级数组实现方案
问题背景
- 运行环境为MySQL 5.7,版本不支持Common Table Expressions(公共表表达式),无法直接通过SQL递归查询构建「区域-办公室-员工」三级层级关系
- 此前尝试在SQL层通过动态生成ID、ParentID的方式拼接层级,需要编写大量子查询,实现和维护成本很高
- 目标方案:直接获取SQL返回的平面结果集,通过PHP代码处理生成带
Item、Children节点的多层嵌套层级数组
输入输出示例
输入:数据库返回平面数组
数据库查询返回的$dbResponse为二维平面结构,每行包含Region、Office、Employee三个字段,示例如下:
$dbResponse = [ ['Region' => '华东区', 'Office' => '上海办公室', 'Employee' => '张三'], ['Region' => '华东区', 'Office' => '上海办公室', 'Employee' => '李四'], ['Region' => '华东区', 'Office' => '杭州办公室', 'Employee' => '王五'], ['Region' => '华南区', 'Office' => '广州办公室', 'Employee' => '赵六'], ['Region' => '华南区', 'Office' => '深圳办公室', 'Employee' => '孙七'], ];
期望输出:嵌套层级数组
输出结构每层统一包含Item(存储当前节点信息)、Children(存储子节点数组)两个字段,三级嵌套示例如下:
$newOutput = [ [ 'Item' => ['Name' => '华东区', 'Type' => 'Region'], 'Children' => [ [ 'Item' => ['Name' => '上海办公室', 'Type' => 'Office'], 'Children' => [ ['Item' => ['Name' => '张三', 'Type' => 'Employee'], 'Children' => []], ['Item' => ['Name' => '李四', 'Type' => 'Employee'], 'Children' => []] ] ], [ 'Item' => ['Name' => '杭州办公室', 'Type' => 'Office'], 'Children' => [ ['Item' => ['Name' => '王五', 'Type' => 'Employee'], 'Children' => []] ] ] ] ], [ 'Item' => ['Name' => '华南区', 'Type' => 'Region'], 'Children' => [ [ 'Item' => ['Name' => '广州办公室', 'Type' => 'Office'], 'Children' => [ ['Item' => ['Name' => '赵六', 'Type' => 'Employee'], 'Children' => []] ] ], [ 'Item' => ['Name' => '深圳办公室', 'Type' => 'Office'], 'Children' => [ ['Item' => ['Name' => '孙七', 'Type' => 'Employee'], 'Children' => []] ] ] ] ] ];
PHP实现方案
核心思路
不需要写递归,通过两层映射表存储已创建节点的引用,单次遍历平面结果集即可完成层级构建,时间复杂度O(n),性能远高于递归查找方案:
- 遍历每行数据时,按「区域→办公室→员工」的顺序逐层判断节点是否已创建
- 未创建的节点初始化后推入对应父节点的
Children数组,同时在映射表中存储该节点的引用 - 已存在的节点直接通过映射表拿到引用,追加下一级节点即可
可直接运行的实现代码
function buildRegionHierarchy(array $dbResponse): array { $hierarchy = []; // 映射表存储已创建节点的引用,避免重复遍历查找 $regionMap = []; $officeMap = []; foreach ($dbResponse as $row) { $regionName = $row['Region']; $officeName = $row['Office']; $employeeName = $row['Employee']; // 处理区域层级节点 if (!isset($regionMap[$regionName])) { $regionNode = [ 'Item' => ['Name' => $regionName, 'Type' => 'Region'], 'Children' => [] ]; $hierarchy[] = &$regionNode; $regionMap[$regionName] = &$regionNode; $officeMap[$regionName] = []; unset($regionNode); } // 处理办公室层级节点 if (!isset($officeMap[$regionName][$officeName])) { $officeNode = [ 'Item' => ['Name' => $officeName, 'Type' => 'Office'], 'Children' => [] ]; $regionMap[$regionName]['Children'][] = &$officeNode; $officeMap[$regionName][$officeName] = &$officeNode; unset($officeNode); } // 处理员工层级节点 $officeMap[$regionName][$officeName]['Children'][] = [ 'Item' => ['Name' => $employeeName, 'Type' => 'Employee'], 'Children' => [] ]; } // 释放引用,避免后续变量操作污染数组结构 unset($regionMap, $officeMap); return $hierarchy; } // 调用示例 $newOutput = buildRegionHierarchy($dbResponse);
注意:代码中使用引用是为了简化多层数组的操作,遍历完成后必须手动unset引用变量,否则可能出现非预期的数组值污染。如果后续需要扩展层级(比如新增员工下属层级),只需要对应增加一层映射判断逻辑即可,不需要重构整体实现。
内容的提问来源于stack exchange,提问作者Rob
相关产品推荐
相关产品推荐

