Получить древовидный массив из плоского за один запрос к базе?

Или можно перефразировать задачу — Как получить многомерный массив из одномерного (плоского), или Получить вложенный массив из одномерного.

Допустим у нас есть база в которой есть список документов, или чего-то там, и есть уникальное поле id, а также неуникальное поле parent_id. Если выбрать все элементы, то получится двумерны массив вида [id_xxx] => array("id", ..., "parent_id"). И с таким массивом может быть неудобно работать если вы захотите вывести вложенны список к приеру или как-то еще реализовать вложенность элементов. Для того чтобы решить эту проблему я предлагаю использовать нижеследующий скрипт:

Итак, вот такая у нас база:

И вот что мы хотим получить:

Для этого пишем код:

// Подключаемся к базе MYSQL
// Получаем из базы плоский массив (не буду это расписывать, т.к. у всех свои способы)
// Может быть у вас массив вообще не из MYSQL базы, а из CSV файла какого-нибудь..
class MyClassName {
    public $cash = array();
    public function getMaterialListTree(){
        $query = $this->db->query("SELECT * FROM YourTable ORDER BY sort_order"); // Получаем как-то наш плоский массив
        $this->cash['getMaterialList'] = $query; // И записываем его в кэш
        $output = array();
        foreach($this->cash['getMaterialList'] as $item){
            if($item['parent_id'] == 0){ //Запускаем рекурсивную функцию только для паренотов самого высого уровня
                $output[$item['id']] = $this->getMaterialListTree_get($item['id']);
            }
        }
        return $output;
    }
    // Рекурсивная функция
    public function getMaterialListTree_get($id = 0){
        $output = array();
        foreach($this->cash['getMaterialList'] as $k => $item){
            if($item['id'] == $id){
                // Записываем значение в возвращаемый массив
                $output = $this->cash['getMaterialList'][$k]; 
                // снова проходимся по нашему плоскому массиву в поисках документов у которых текущий числится родителем
                foreach($this->cash['getMaterialList'] as $item_1){
                    if($item_1['parent_id'] == $id){
                        // если пока что массив для деток не создавался, создаем его
                        if(empty($output['children'])){ 
                            $output['children'] = array();
                        }
                        // если находим деток, то запускаем рекурсию дальше
                        $output['children'][$item_1['id']] = $this->getMaterialListTree_get($item_1['id']);
                    }
                }
            }
        }
        return $output;
    }
}

// Печатаем как нам удобно:
$tree_elem = new MyClassName();
print_r($tree_elem->getMaterialListTree());

Еще один вариант для структурирования одномерного массива

Исходные данные те же самые, что и в первом примере. Но результат нужен такой:

Для этого пишем код:

class MyClassName {
    public function getMaterialList(){
        $input_array = $this->db->query("SELECT * FROM YourTable ORDER BY sort_order");
        $material_list = array();
        
        // Надо определить кто является парентом чтобы их не выводить
        $material_parent_ids = array();
        foreach($input_array as $elem){
            $material_list[$elem['id']] = $elem;
        }
        foreach($material_list as $elem){
            if(!in_array($elem['parent_id'] ,$material_parent_ids)){
                $material_parent_ids[$elem['parent_id']] = $material_list[$elem['parent_id']]['id'];
            }
        }
        
        $material_list_string = array();
        foreach($material_list as $elem){
            $temp_array = array();
            // Тут нет рекурсии, но уж простите, в задаче не требовалось. Рекурсия выполнена в предыдущем примере.
            if(!in_array($elem['id'],$material_parent_ids)){
                $temp_array[] = $elem['material_name'];
                if($elem['parent_id'] != 0){
                    $temp_array[] = $material_list[$elem['parent_id']]['material_name'];
                }
                if($material_list[$elem['parent_id']]['parent_id'] != 0){
                    $temp_array[] = $material_list[$material_list[$elem['parent_id']]['parent_id']]['material_name'];
                }
                $material_list_string[$elem['id']] = implode(" / ", array_reverse($temp_array));
            }
        }
        asort($material_list_string);
        return $material_list_string;
    }
}
// Печатаем как нам удобно:
$tree_elem = new MyClassName();
print_r($tree_elem->getMaterialListTree());

Комментарии (0)

  1. Напишите первый комментарий
*Комментарий будет опубликован после проверки модератором

Комментарии easyComm

Станислав 17 марта 2018, 22:28

Увы, "...за один запрос" лишнее)

Администратор по умолчанию

Почему? Запрос к базе 1, после него мы только с кэшем работаем:)

Похожие статьи

Пример перевода с помощью Yandex Translate API

Универсальная форма обратной связи — feedBackForm

Как контролировать кэш CSS и скриптов

Как поменять версию PHP, используемую в командной строке на Windows

Как поменять язык в Faker

Транслитерация URL в Laravel. Примеры str_slug()

Простое логирование

Курсы валют с cbr.ru на PHP

Разные фишки, заготовки

Получить вложенный массив из плоского

Laravel Excel - Базовый экспорт

Namespace на примерах - Как понять пространства имен в PHP

Заготовки для автоматического заполнения товарами магазина 1.5.5.1.2

Как сделать middleware в Laravel 6 - простой пример

Примеры работы с API

Как обработать POST данные в PHP

Загрузить файл для постобработки

Получить время выполнения PHP скрипта. Решение в 3 строки кода

Получить курсы валют с cbr.ru на PHP с кэшированием результатов

Экспорт маршрутов из Laravel в JSON файл

Фиксированная сортировка массива на основе хэша

Вывести список всех файлов на сервере (и размер файла)

Наш сайт использует куки, нажмите «ОК» если вы не против
OK