Đây không phải là một thiết kế phù hợp cho dữ liệu phân cấp. Xem xét một cách tiếp cận khác như danh sách gần kề .
Giải pháp # 1 - Hỗ trợ MySQL 8 JSON:
Với MySQL 8, bạn có thể sử dụng JSON_ARRAYAGG()
và JSON_OBJECT()
để nhận kết quả JSON chỉ với SQL:
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
Kết quả là:
{
"name": "Bob",
"children": [
{
"name": "Ted",
"children": [
{
"name": "Fred"
}
]
},
{
"name": "Carol",
"children": [
{
"name": "Harry"
}
]
},
{
"name": "Alice",
"children": [
{
"name": "Mary"
}
]
}
]
}
Đã định dạng:
{
"name": "Bob",
"children": [
{
"name": "Ted",
"children": [
{
"name": "Fred"
}
]
},
{
"name": "Carol",
"children": [
{
"name": "Harry"
}
]
},
{
"name": "Alice",
"children": [
{
"name": "Mary"
}
]
}
]
}
Giải pháp # 2 - Tạo JSON với GROUP_CONCAT ():
Nếu tên không chứa bất kỳ bộ đệm trích dẫn nào, bạn có thể tạo chuỗi JSON theo cách thủ công trong các phiên bản cũ hơn bằng cách sử dụng GROUP_CONCAT()
:
$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;
Kết quả sẽ giống nhau (xem bản trình diễn )
Giải pháp # 3 - Xây dựng cấu trúc nestet với các đối tượng PHP:
Bạn cũng có thể viết một truy vấn SQL đơn giản hơn và xây dựng cấu trúc lồng nhau trong 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
");
Kết quả là
name | parent
----------------
Bob | null
Ted | Bob
Carol | Bob
Alice | Bob
Fred | Ted
Harry | Carol
Mary | Alice
Lưu ý:Tên phải là duy nhất trên tất cả các bảng. Nhưng tôi không biết bạn sẽ mong đợi kết quả gì, nếu có thể trùng lặp.
Bây giờ, hãy lưu các hàng dưới dạng các đối tượng trong một mảng được lập chỉ mục bằng tên:
$data = []
while ($row = $result->fetch_object()) {
$data[$row->name] = $row;
}
$data
bây giờ sẽ chứa
[
'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'],
]
Bây giờ chúng ta có thể liên kết các nút trong một vòng lặp duy nhất:
$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);
Kết quả:
{
"name": "Bob",
"children": [
{
"name": "Ted",
"children": [
{
"name": "Fred"
}
]
},
{
"name": "Carol",
"children": [
{
"name": "Harry"
}
]
},
{
"name": "Alice",
"children": [
{
"name": "Mary"
}
]
}
]
}
Nếu có thể có nhiều nút gốc (nhiều hàng trong level_1_name
), sau đó sử dụng
json_encode($roots);