将分层数据从数据库加载到数组中

时间:2014-03-24 17:44:40

标签: php arrays database hierarchical-data

我在数据库中有一个表,其中包含以下结构/数据:

n_id      n_parent_id      ... some other fields ...
====      ===========      =========================
 1         null            ...
 2         null            ...
...
11            1            ...
12            1            ...
...
25            2            ...
...
65           11            ...
66           11            ...
...

此表存储分层数据,如上例所示。我需要将它加载到一个树状的fasion中的PHP数组中,这样数组就会包含这样的内容:

Array
(
    [1] => Array
        (
            [n_id] => 1
            [n_parent_id] => 
            [other_data] => ...
            [children] => Array
                (
                    [11] => Array
                        (
                            [n_id] => 11
                            [n_parent_id] => 1
                            [other_data] => ...
                            [children] => Array
                                 (
                                    [65] => Array
                                        (
                                            [n_id] => 65
                                            [n_parent_id] => 11
                                            [other_data] => ...
                                        )
                                 )
   ... and so on ...
)

我可以轻松处理一个级别:

//ordering will ensure that parent row is always read before children rows
//my data is set up in this way.
$query = "select n_id, n_parent_id, other_data from hierarchy_table order by n_parent_id, n_id";
if(($dbs = $dbh->query($query)) === FALSE) {
    $e = $dbh->errorInfo();
    // ... deal with error
}
$result = array();
while($row = $dbs->fetch(PDO::FETCH_ASSOC)) {
    if(is_null($row['n_parent_id'])) {
        $result[$row['n_id']] = array(
            'n_id' => $row['n_id'],
            'n_parent_id' => null,
            'other_data' => ...,
            'children' => array()
        );
    }
    elseif(isset($result[$row['n_parent_id']])) {
        $result[$row['n_parent_id']]['children'][$row['n_id']] = array(
            'n_id' => $row['n_id'],
            'n_parent_id' => $row['n_parent_id'],
            'other_data' => ...
            children => array()
        );
    }
}

但是,我似乎无法将此扩展到多个级别,而无需每次需要添加行时都在递归循环遍历整个数组。当然,如果它是Java或C,我只会存储指向数据结构的指针,这将解决问题,但在PHP中,这并不是那么容易。在这一切结束时,我需要将json_encode发送给客户端。

This question涵盖了类似的问题,但我在数据库中没有实际的分层信息 - 只有父ID。

对此有任何帮助表示赞赏。

编辑:我的数据库表包含数十万行,因此性能非常重要。

2 个答案:

答案 0 :(得分:1)

经过一番挣扎之后,我设法通过记录集上的一次传递(只读取每个记录一次)得到我需要的东西 - 使用引用。由于内存引用支持在PHP中相当有限,因此需要保留一些有趣的东西(例如,我从数据库中读取的每一行的新变量名称)。无论如何,这是我最终得到的代码(此代码仅处理idparent_id - 但读取/存储更多数据非常简单):

$dbh = new PDO(CONNECT_STRING, USERNAME, PASSWORD);
$dbs = $dbh->query("SELECT n_id, n_parent_id from test_table order by n_parent_id, n_id");
$elems = array();

while(($row = $dbs->fetch(PDO::FETCH_ASSOC)) !== FALSE) {
    $row['children'] = array();
    $vn = "row" . $row['n_id'];
    ${$vn} = $row;
    if(!is_null($row['n_parent_id'])) {
        $vp = "parent" . $row['n_parent_id'];
        if(isset($data[$row['n_parent_id']])) {
            ${$vp} = $data[$row['n_parent_id']];
        }
        else {
            ${$vp} = array('n_id' => $row['n_parent_id'], 'n_parent_id' => null, 'children' => array());
            $data[$row['n_parent_id']] = &${$vp};
        }
        ${$vp}['children'][] = &${$vn};
        $data[$row['n_parent_id']] = ${$vp};
    }
    $data[$row['n_id']] = &${$vn};
}
$dbs->closeCursor();

$result = array_filter($data, function($elem) { return is_null($elem['n_parent_id']); });
print_r($result);

对此数据执行时:

mysql> select * from test_table;
+------+-------------+
| n_id | n_parent_id |
+------+-------------+
|    1 |        NULL |
|    2 |        NULL |
|    3 |           1 |
|    4 |           1 |
|    5 |           2 |
|    6 |           2 |
|    7 |           5 |
|    8 |           5 |
+------+-------------+

最后print_r产生此输出:

Array
(
    [1] => Array
        (
            [n_id] => 1
            [n_parent_id] => 
            [children] => Array
                (
                    [3] => Array
                        (
                            [n_id] => 3
                            [n_parent_id] => 1
                            [children] => Array
                                (
                                )

                        )

                    [4] => Array
                        (
                            [n_id] => 4
                            [n_parent_id] => 1
                            [children] => Array
                                (
                                )

                        )

                )

        )

    [2] => Array
        (
            [n_id] => 2
            [n_parent_id] => 
            [children] => Array
                (
                    [5] => Array
                        (
                            [n_id] => 5
                            [n_parent_id] => 2
                            [children] => Array
                                (
                                    [7] => Array
                                        (
                                            [n_id] => 7
                                            [n_parent_id] => 5
                                            [children] => Array
                                                (
                                                )

                                        )

                                    [8] => Array
                                        (
                                            [n_id] => 8
                                            [n_parent_id] => 5
                                            [children] => Array
                                                (
                                                )

                                        )

                                )

                        )

                    [6] => Array
                        (
                            [n_id] => 6
                            [n_parent_id] => 2
                            [children] => Array
                                (
                                )

                        )

                )

        )

)

这正是我所寻找的。

答案 1 :(得分:1)

由于我也遇到了一个几乎相同的问题,Aleks G的创意(!)解决方案并没有完全满足我的需求,因为我使用嵌套集模型保存我的分层数据,这是我使用嵌套时的解决方案集合(需要一段时间才能实现)。必须根据前序遍历对$data数组进行排序。

用法示例:

$data =
[
    0 => ['ID' => 0, 'depth' => 0],
    1 => ['ID' => 1, 'depth' => 1],
    2 => ['ID' => 2, 'depth' => 2],
    3 => ['ID' => 6, 'depth' => 2],
    4 => ['ID' => 10, 'depth' => 1]
];

$IDs = hierachicDataToArray($data);
print_r($IDs);

$IDs = hierachicDataToArray($data, true);
print_r($IDs);

输出:

Array
(
    [0] => Array
        (
            [1] => Array
                (
                    [2] => 2
                    [6] => 6
                )

            [10] => 10
        )

)

Array
(
    [0] => Array
        (
            [ID] => 0
            [depth] => 0
            [children] => Array
                (
                    [1] => Array
                        (
                            [ID] => 1
                            [depth] => 1
                            [children] => Array
                                (
                                    [2] => Array
                                        (
                                            [ID] => 2
                                            [depth] => 2
                                            [children] => Array
                                                (
                                                )

                                        )

                                    [6] => Array
                                        (
                                            [ID] => 6
                                            [depth] => 2
                                            [children] => Array
                                                (
                                                )

                                        )

                                )

                        )

                    [10] => Array
                        (
                            [ID] => 10
                            [depth] => 1
                            [children] => Array
                                (
                                )

                        )

                )

        )

)

方法:

/**
 * Convert hierarchic data records to a multidimensional array.
 * Expects an array in the form: [<i> => ['ID' => <int ID>, 'depth' => <int depth>, '<string>' => <mixed>, ...]]
 * At least the 'ID' and 'depth' key/value pairs must exist.
 * @author: lsblsb[at]gmx.de
 * @copyright: GPL-3.0
 * 
 * @param array $data The data array.
 * @param bool $incData = false Whether to include additional data or not.
 * @param bool $IDKeys = true Whether to use IDs as key or not (false only possible when $incData = true)
 * 
 * @return array[]
 */
function hierarchicDataToArray(array $data, $incData = false, $IDKeys = true)
{
    $nodes = [];

    foreach($data as $i => $record)
    {
        $ID = $record['ID'];
        $depth = $record['depth'];
        $prevRecord = isset($data[$i-1]) ? $data[$i-1] : false;
        $prevDepth = $prevRecord ? $prevRecord['depth'] : false;
        $prevID = $prevRecord ? $prevRecord['ID'] : false;
        $nextRecord = isset($data[$i+1]) ? $data[$i+1] : false;
        $nextDepth = $nextRecord ? $nextRecord['depth'] : false;
        $nextID = $nextRecord ? $nextRecord['ID'] : false;

        if($prevRecord && $prevDepth >= $depth)
        {
            $pID = $depthIDs[$depth-1];
            if($depth == 1)
            {
                if($incData)
                    $nodes[$pID]['children'][$ID] = &$refs[$ID];
                else
                    $nodes[$pID][$ID] = &$refs[$ID];
            }
            else
            {
                if($incData)
                    $refs[$pID]['children'][$ID] = &$refs[$ID];
                else
                    $refs[$pID][$ID] = &$refs[$ID];
            }
        }

        if($nextRecord && $nextDepth > $depth)
        {
            if($depth == 0)
            {
                if($incData)
                {
                    if(!isset($nodes[$ID])) $nodes[$ID] = $record;
                    $nodes[$ID]['children'][$nextID] = &$refs[$nextID];
                }
                else
                    $nodes[$ID][$nextID] = &$refs[$nextID];
            }
            else
            {
                if($incData)
                {
                    if(!isset($refs[$ID])) $refs[$ID] = $record;
                    $refs[$ID]['children'][$nextID] = &$refs[$nextID];
                }
                else
                    $refs[$ID][$nextID] = &$refs[$nextID];
            }
        }
        else
        {
            $node = $incData ? $record + ['children' => []] : $ID;
            $refs[$ID] = $node;
        }

        if(!$IDKeys && $incData)
        {
            if(!$nextRecord)
            {
                $nodes = array_values($nodes);
                $nodes[0]['children'] = array_values($nodes[0]['children']);
            }
            elseif($nextDepth < $depth)
            {
                $pID = $depthIDs[$depth-1];
                $refs[$pID]['children'] = array_values($refs[$pID]['children']);
            }
        }

        $depthIDs[$depth] = $ID;
    }

    return $nodes;
}