Форум: Форум PHPФорум ApacheФорум Регулярные ВыраженияФорум MySQLHTML+CSS+JavaScriptФорум FlashРазное
Новые темы: 0000000
PHP. Практика создания Web-сайтов (второе издание). Авторы: Кузнецов М.В., Симдянов И.В. PHP Puzzles. Авторы: Кузнецов М.В., Симдянов И.В. MySQL на примерах. Авторы: Кузнецов М.В., Симдянов И.В. PHP на примерах (2 издание). Авторы: Кузнецов М.В., Симдянов И.В. Программирование. Ступени успешной карьеры. Авторы: Кузнецов М.В., Симдянов И.В.
ВСЕ НАШИ КНИГИ
Консультационный центр SoftTime

Форум MySQL

Выбрать другой форум

 

Здравствуйте, Посетитель!

вид форума:
Линейный форум Структурный форум

тема: Дерево
 
 автор: confirm   (10.04.2014 в 21:33)   письмо автору
 
 

Известный вопрос, и известны решения (ссылки на которые есть и здесь) - рекурсия, без рекурсии обходом вправо, NestedSet ... Не было времени, да и лень мешала писать много, теперь пока лень спит, можно и возразить по этому поводу.

Действительно, создаем сложные запросы объединяя их в один, и вроде бы в порядке вещей, но почему-то, если надо получить дерево каталогов, то забываем об этой возможности. А можно ведь получить дерево одним запросом без рекурсий, множества запросов, левых и правых ключей (NestedSet), да и вообще каких либо прочих ключей. Потребуется минимальное - идентификатор записи и ее владелец (родитель), и это все. Для того чтобы это реализовать, нужно сформировать должных образом тело запроса, с соблюдением единственного условия, о котором ниже.

Сразу оговорюсь - будет "тезисное" изложение вопроса на примере фиксированного уровня вложения. Кому надо, домозгуют, если интересно "добить" это, дополнения и решения можно тут добавить.

Чтобы не возвращаться к частям повествования, лучше начать его с описания аргументов функции получения дерева, которую условно назовем tree():

<?
function tree($db//условимся, что PDO, и это созданное подключение  
              
$tbl//имя таблицы
              
$fld//массив содержащий поля таблицы, значения которых нужно включить в вывод 
              
$lvl//уровень вложения как величина максимального вложения минус 1 (пояснение показано на максимуме 3)
              
$srt=false //поля таблицы по которым нужно производить сортировку результата
              
) {
    
    
//Теперь об упомянутом условии - в аргументе $fld, должны быть перечислены "поименно" необходимые поля таблицы,
    //при этом первый элемент этого массива должен обязательно быть именем поля идентификатора записи,
    //а второй элемент должен обязательно быть именем поля ее владельца (родителя),
    //указывать в этом аргументе поля как (*) НЕЛЬЗЯ                   

    //Формируем тело запроса
    
$second $fld[0]; //имя поля идентификатора категории
    
$parent $fld[1]; //имя поля категории владельца
    
    //Формируем поля запроса и их псевдонимы
    //для этого просто будем к именам таблиц и именам полей добавлять число от 0 до $lvl
    //реально псевдонимы использоваться будут только в запросе, в дальнейшем они учитываться не будут 
    
$sql 'SELECT ' implode(','array_map(function($n) use($fld) { 
        foreach(
$fld as $v$m[] = 't'.$n.'.`'.$v.'` '.$v.$n;
        return 
implode(','$m); 
    }, 
range(0$lvl))).',';
    
    
//Подключаем к телу запрос к первичной таблице содержащей записи не имеющих родителя -  $parent = 0,
    //то есть это будут корневые каталоги дерева
    
$sql rtrim($sql,',').' FROM '.$tbl.' t0 ';

    
//Формируем вложенные запросы, добавляя к псевдонимам имен таблиц и именам полей условия выборки число от 0 до $lvl
    //таким образом каждое последующее объединение будет выбирать каталог, 
    //идентификатор родителя которого равен идентификатору предыдущего запроса/объединения
    
for($i=0$i<$lvl$i++) $sql .= 'LEFT JOIN '.$tbl.' t'.($i+1).' ON t'.($i+1).'.`'.$parent.'`=t'.$i.'.`'.$second.'` ';

    
//Подключаем условия выборки и сортировки, если она определена
    //условие выборки одно - получить для первичного запроса все идентификаторы не имеющие родителей, то есть корневых записей
    //Какие либо иные условия можно добавить или формировать по условиям передаваемых как аргумент функции,
    //но эти индивидуальности можно учесть и потом, в результирующем наборе, по соответствующим полям
    //Сортировку можно тоже формировать здесь, но лучше передавать уже готовый набор в функцию,
    //ели будет необходимость получать в итоге различные наборы 
    
$sql .= 'WHERE t0.`'.$second.'` IN(SELECT `'.$second.'` FROM '.$tbl.' WHERE `'.$parent.'`=0) '.($srt 'ORDER BY '.$srt null);

    
//Делаем запрос и обрабатываем результат
    
if($q $db->query($sql)) {
        if(
$q->rowCount()) {
            
//Возвращаемое сформированное дерево
            
$tree = [];  
            
//Корневой идентификатор
            
$id 0
            
//Если необходимо хранить идентификаторы вложений (от корня до последнего вложения), для каждого идентификатора,
            //например для того, чтобы по ним из результирующего дерева получить путь для навигатора
            //или иметь возможность сформировать список каталогов для администрирования, учитывающий уровни их вложения,
            //наличие и глубину субкаталогов, для предотвращения переноса каталога, например самого на себя, и т.п.,
            //то сохраним эту вложения в этот массив   
            
$dir = [];
            
//Число полей в запросе
            
$f count($fld); 
            
//Удаляем из массива имен полей имена идентификатора записи и владельца,
            //если они могут потребоваться в дальнейшем, например для рекурсивного обхода с помощью array_walk_recursive(),
            //то оставляем для последующего объединения весь массив имен $fld, а не его срез
            
$fld array_slice($fld2);
            
            while(
$r $q->fetch(PDO::FETCH_NUM)) {
                
//Разбиваем поля выборки на группы по количеству числа полей запроса
                
$r array_chunk($r$f);
                
//Удаляем пустые значения выборки у записей с вложениями менее $lvl 
                
$r array_intersect_key($rarray_diff(array_column($r0), [null])); 
                
//Запоминаем идентификаторы вложений
                
$dir[] = array_column($r0);
                
//Получаем корневой идентификатор
                
if($id!=$r[0][0]) $id $r[0][0]; 
                for(
$i=0$k=count($r); $i<$k$i++) {
                    if(!
$r[$i][1]) { //если корневой каталог
                        //если корневой каталог еще не добавлен в результат, добавить
                        //результатом ветки дерева будут имена полей запроса ($fld, за вычетом id и родителя), как ключи
                        //и соответствующие им индексы выборки как значения
                        
if(!array_key_exists($id$tree)) $tree[$id] = array_combine($fldarray_slice($r[$i], 2))+['level'=>$i];//если нужны будут уровни вложения, то добавляем их   
                    
} else if($i+1<$k) { //если не достигли максимального вложения
                        //если еще не добавлялась категория первого вложения в результат
                        
if(@!array_key_exists($r[$i][0], $tree[$id]['sub'])) $tree[$id]['sub'][$r[$i][0]] = array_combine($fldarray_slice($r[$i], 2))+['level'=>$i];
                    } else { 
//достигнут максимальный уровень вложения
                        //если уровень 1 
                        
if($i==1$tree[$id]['sub'][$r[$i][0]] = array_combine($fldarray_slice($r[$i], 2))+['level'=>$i];
                        
//если последний уровень
                        
else $tree[$id]['sub'][$r[$i][1]]['sub'][$r[$i][0]] = array_combine($fldarray_slice($r[$i], 2))+['level'=>$i];
                    }
                }
            }
            return [
$tree$dir];
        } else return 
0//нет записей
    
} else return null//ошибка запроса
}

//Например, получить дерево из таблицы tbl, с именами идентификатора id и владельца prt, включив в результат название (name), число записей (cnt), и отсортировать результат по полю name:

if(!is_bool($tree tree($db'tbl', ['id''prt''name''cnt'], 2'name0, name1, name2'))) {
    if(
$tree) {
        
//формирование нужного
        //дерево $tree[0] имеет структуру:
        /*
           [1] => array(
                [name] => value
                [cnt] => value
                [level] => value
                [sub] = array( субкаталог, если есть
                    ....   
        */
    
} else echo 'Нет записей'
} else echo 
'Ошибка запроса';  

  Ответить  
Rambler's Top100
вверх

Rambler's Top100 Яндекс.Метрика Яндекс цитирования