从 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?的主要内容,如果未能解决你的问题,请参考以下文章