これは、階層データの適切な設計のようには見えません。 隣接リストのような別のアプローチを検討してください 。
ソリューション#1-MySQL 8 JSONサポート:
MySQL 8では、<を使用できます。 code> 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"}]}]}
フォーマット済み:
{"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;
結果は同じになります(デモ を参照) )
ソリューション#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
注:名前は、すべてのテーブルで一意である必要があります。しかし、重複が可能だった場合、どのような結果が期待できるかわかりません。
次に、次の名前でインデックス付けされた配列のオブジェクトとして行を保存します:
$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'],
]
これで、ノードを1つのループでリンクできます。
$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"}]}]}
複数のルートノードが可能な場合( level_1_name
の複数の行 )、次に
json_encode($roots);