从 MySQL 结果和 PHP 为 D3.js 树创建分层 JSON?

Posted

技术标签:

【中文标题】从 MySQL 结果和 PHP 为 D3.js 树创建分层 JSON?【英文标题】:Creating hierarchical JSON from MySQL results and PHP for D3.js tree? 【发布时间】:2019-09-28 03:24:39 【问题描述】:

我正在尝试使用 php 从数据库结果创建以下 JSON(非常简化...):


    "name": "Bob",
    "children": [
            "name": "Ted",
            "children": [
                "name": "Fred"
            ]
        ,
        
            "name": "Carol",
            "children": [
                "name": "Harry"
            ]
        ,
        
            "name": "Alice",
            "children": [
                "name": "Mary"
            ]
        
    ]

数据库表:

Table 'level_1':

level_1_pk| level_1_name
-------------------------
 1 | Bob  


Table 'level_2':

level_2_pk| level_2_name | level_1_fk
-------------------------
 1 | Ted                 | 1
 2 | Carol               | 1
 3 | Alice               | 1


Table 'level_3':

level_3_pk| level_3_name | level_2_fk
-------------------------
 1 | Fred                | 1
 2 | Harry               | 2
 3 | Mary                | 3

代码:

$query = "SELECT * 
FROM level_1
LEFT JOIN level_2
ON level_1.level_1_pk = level_2.level_1_fk";
$result = $connection->query($query);
 while ($row = mysqli_fetch_assoc($result))
        $data[$row['level_1_name']] [] = array(
            "name" => $row['level_2_name']
            );
    

echo json_encode($data);

生产:

"Bob":["name":"Ted","name":"Carol","name":"Alice"]

问题:

如何获得下一个级别 level_3,并按照上面定义的 JSON 的要求在 JSON 中包含文本“children”和 level_3 children?

我想我需要 PHP 是递归的,因为 JSON 中有更多的孩子。

SQL

【问题讨论】:

究竟尝试过什么?为什么不采用递归方法? 【参考方案1】:

这看起来不像是分层数据的体面设计。考虑另一种方法,例如 邻接列表

解决方案 #1 - MySQL 8 JSON 支持:

在 MySQL 8 中,您可以使用 JSON_ARRAYAGG()JSON_OBJECT() 仅通过 SQL 获取 JSON 结果:

select json_object(
  'name', l1.level_1_name,
  'children', json_arrayagg(json_object('name', l2.level_2_name, 'children', l2.children))
) as json
from level_1 l1
left join (
  select l2.level_2_name
       , l2.level_1_fk
       , json_arrayagg(json_object('name', l3.level_3_name)) as children
  from level_2 l2
  left join level_3 l3 on l3.level_2_fk = l2.level_2_pk
  group by l2.level_2_pk
) l2 on l2.level_1_fk = l1.level_1_pk
group by level_1_pk

结果是:

"name": "Bob", "children": ["name": "Ted", "children": ["name": "Fred"], "name": "Carol", "children": ["name": "Harry"], "name": "Alice", "children": ["name": "Mary"]]

db-fiddle demo

格式化:


  "name": "Bob",
  "children": [
    
      "name": "Ted",
      "children": [
        
          "name": "Fred"
        
      ]
    ,
    
      "name": "Carol",
      "children": [
        
          "name": "Harry"
        
      ]
    ,
    
      "name": "Alice",
      "children": [
        
          "name": "Mary"
        
      ]
    
  ]

解决方案 #2 - 使用 GROUP_CONCAT() 构造 JSON:

如果名称不包含任何引号字符,您可以在旧版本中使用 GROUP_CONCAT() 手动构造 JSON 字符串:

$query = <<<MySQL
    select concat('',
      '"name": ', '"', l1.level_1_name, '", ',
      '"children": ', '[', group_concat(
        '',
        '"name": ', '"', l2.level_2_name, '", ',
        '"children": ', '[', l2.children, ']',
        ''
      separator ', '), ']'        
    '') as json
    from level_1 l1
    left join (
      select l2.level_2_name
           , l2.level_1_fk
           , group_concat('', '"name": ', '"',  l3.level_3_name, '"', '') as children
      from level_2 l2
      left join level_3 l3 on l3.level_2_fk = l2.level_2_pk
      group by l2.level_2_pk
    ) l2 on l2.level_1_fk = l1.level_1_pk
    group by level_1_pk
MySQL;

结果是一样的(见demo)

解决方案 #3 - 使用 PHP 对象构建嵌套结构:

你还可以编写一个更简单的 SQL 查询并在 PHP 中构建嵌套结构:

$result = $connection->query("
    select level_1_name as name, null as parent
    from level_1
    union all
    select l2.level_2_name as name, l1.level_1_name as parent
    from level_2 l2
    join level_1 l1 on l1.level_1_pk = l2.level_1_fk
    union all
    select l3.level_3_name as name, l2.level_2_name as parent
    from level_3 l3
    join level_2 l2 on l2.level_2_pk = l3.level_2_fk
");

结果是

name    | parent
----------------
Bob     | null
Ted     | Bob
Carol   | Bob
Alice   | Bob
Fred    | Ted
Harry   | Carol
Mary    | Alice

demo

注意:名称在所有表中都应该是唯一的。但我不知道你会期待什么结果,如果可以重复的话。

现在将行作为对象保存在按名称索引的数组中:

$data = []
while ($row = $result->fetch_object()) 
    $data[$row->name] = $row;

$data 现在将包含

[
    'Bob'   => (object)['name' => 'Bob',   'parent' => NULL],
    'Ted'   => (object)['name' => 'Ted',   'parent' => 'Bob'],
    'Carol' => (object)['name' => 'Carol', 'parent' => 'Bob'],
    'Alice' => (object)['name' => 'Alice', 'parent' => 'Bob'],
    'Fred'  => (object)['name' => 'Fred',  'parent' => 'Ted'],
    'Harry' => (object)['name' => 'Harry', 'parent' => 'Carol'],
    'Mary'  => (object)['name' => 'Mary',  'parent' => 'Alice'],
]

我们现在可以在一个循环中链接节点:

$roots = [];
foreach ($data as $row) 
    if ($row->parent === null) 
        $roots[] = $row;
     else 
        $data[$row->parent]->children[] = $row;
    
    unset($row->parent);


echo json_encode($roots[0], JSON_PRETTY_PRINT);

结果:


    "name": "Bob",
    "children": [
        
            "name": "Ted",
            "children": [
                
                    "name": "Fred"
                
            ]
        ,
        
            "name": "Carol",
            "children": [
                
                    "name": "Harry"
                
            ]
        ,
        
            "name": "Alice",
            "children": [
                
                    "name": "Mary"
                
            ]
        
    ]

demo

如果可能有多个根节点(level_1_name 中有多个行),则使用

json_encode($roots);

【讨论】:

感谢您提供非常全面的答案和一系列解决方案。我没有安装 MySQL 8,但是使用 GROUP_CONCAT() 的替代解决方案效果很好,并且是迄今为止最简单的。 我还有一个关于在 ***.com/questions/56145467/…@span> 向链中添加一组孩子的问题 【参考方案2】:

我会推荐以下递归:

function getPeople($levelNum = 1, $parent = 0) 
    if ($levelNum > 3) return array(); // break recursion condition
    global $connection;
    $level = 'level_' . $levelNum; // also can check here if the table exist by this name
    $query = "SELECT * FROM ". $level; 
    if ($parent) // if there is parent add him to query
        $query .= "WHERE " . $level . "_fk = " . $parent;
    $result = $connection->query($query);
    while ($row = mysqli_fetch_assoc($result))  // for each row:
        $idCol = $level . "_pk"; // get the primary ID key
        $id = $row[$idCol]; // get the ID
        $localResult[$id] = array("Name" => $row[$level . "_name"]); // set local array with key as ID and name
    

    foreach ($localResult as $id => $elem)  // elem is array with only name
        $children = getPeople($levelNum + 1, $id); // recursively get all children
        if ($children)
            $elem["children"] = $children;
        $data[] = $elem;  // append the new elem to origin array
    
    return $data;

初始调用应类似于getPeople()json_encode(getPeople())

注意 - 我使用最大深度作为递归中断,假设你知道最大深度 - 你也可以(我建议你这样做)跳过中断条件并检查表名是否存在! (如$level字符串)

我把它写成伪代码,因为我实际上并没有构建表——它可能有语法错误,但逻辑应该是可靠的......

【讨论】:

评论不用于扩展讨论;这个对话是moved to chat。

以上是关于从 MySQL 结果和 PHP 为 D3.js 树创建分层 JSON?的主要内容,如果未能解决你的问题,请参考以下文章

从 CSV 数据创建 D3.js 可折叠树

D3.js 缩放和平移可折叠树图

将 ctree 输出转换为 JSON 格式(用于 D3 树布局)

d3.js之树形折叠树

如何在d3js中垂直树

如何从 mySQL 和 PHP 检索结果为多维数组?