sql >> Base de Datos >  >> RDS >> Mysql

¿Crear JSON jerárquico a partir de los resultados de MySQL y PHP para el árbol D3.js?

Esto no parece un diseño decente para datos jerárquicos. Considere otro enfoque como lista de adyacencia .

Solución n.º 1:compatibilidad con MySQL 8 JSON:

Con MySQL 8 puede usar JSON_ARRAYAGG() y JSON_OBJECT() para obtener el resultado JSON solo con 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

El resultado es:

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

demostración de db-fiddle

Formateado:

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

Solución #2 - Construcción de JSON con GROUP_CONCAT():

Si los nombres no contienen caracteres de comillas, puede construir manualmente la cadena JSON en versiones anteriores usando 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;

El resultado sería el mismo (ver demo )

Solución n.º 3:construir una estructura nestet con objetos PHP:

También puede escribir una consulta SQL más simple y construir la estructura anidada en 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
");

El resultado es

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

demostración

Nota:El nombre debe ser único en todas las tablas. Pero no sé qué resultado esperaría, si los duplicados fueran posibles.

Ahora guarde las filas como objetos en una matriz indexada por el nombre:

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

$data ahora contendrá

[
    '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'],
]

Ahora podemos vincular los nodos en un solo bucle:

$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);

El resultado:

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

demostración

Si son posibles varios nodos raíz (varias filas en level_1_name ), luego use

json_encode($roots);