MySQL рекурсивный поиск по дереву - PullRequest
2 голосов
/ 20 апреля 2011

У меня есть база данных с деревом имен, которое может пройти до 9 уровней в глубину, и мне нужно иметь возможность искать сигнальную ветвь дерева из любой точки ветки.

База данных:

+----------------------+
| id |  name  | parent |
+----------------------+
| 1  |  tom   |   0    |
| 2  |  bob   |   0    |
| 3  |  fred  |   1    |
| 4  |  tim   |   2    |
| 5  |  leo   |   4    |
| 6  |  sam   |   4    |
| 7  |  joe   |   6    |
| 8  |  jay   |   3    |
| 9  |  jim   |   5    |
+----------------------+

Дерево:

tom
 fred
  jay
bob
 tim
  sam
   joe
  leo
   jim

Например:

Если я ищу "j" от пользователя "bob", я получу только "joe" и "jim". Если я буду искать "j" из формы "leo", я получу только "jim".

Я не могу придумать какой-либо простой способ сделать это, поэтому любая помощь приветствуется.

Ответы [ 6 ]

8 голосов
/ 20 апреля 2011

Вы действительно должны рассмотреть возможность использования Модифицированного обхода дерева предзаказов , который значительно упрощает такие запросы. Вот ваша таблица, выраженная с помощью MPTT. Я оставил родительское поле, так как оно облегчает некоторые запросы.

+----------------------+-----+------+
| id |  name  | parent | lft | rght |
+----------------------+-----+------+
| 1  |  tom   |   0    |  1  |   6  |
| 2  |  bob   |   0    |  7  |  18  |
| 3  |  fred  |   1    |  2  |   5  |
| 4  |  tim   |   2    |  8  |  17  |
| 5  |  leo   |   4    | 12  |  15  |
| 6  |  sam   |   4    |  9  |  16  |
| 7  |  joe   |   6    | 10  |  11  |
| 8  |  jay   |   3    |  3  |   4  | 
| 9  |  jim   |   5    | 13  |  14  |
+----------------------+-----+------+

Для поиска j от пользователя bob вы должны использовать значения lft и rght для bob:

SELECT * FROM table WHERE name LIKE 'j%' AND lft > 7 AND rght < 18

Реализация логики для обновления lft и rght для добавления, удаления и переупорядочения узлов может быть проблемой (подсказка: используйте существующую библиотеку, если можете), но запрос будет быстрым.

1 голос
/ 31 января 2013

Новая конструкция "recursive with" сделает эту работу, но я не знаю, id MySQL ее поддерживает (пока).

with recursive bobs(id) as (
   select id from t where name = 'bob'
   union all
   select t.id from t, bobs where t.parent_id = bobs.id
)
select t.name from t, bobs where t.id = bobs.id
and name like 'j%'
1 голос
/ 20 апреля 2011

Думали ли вы об использовании рекурсивного цикла? я использую цикл для cms, который я построил поверх codeigniter, который позволяет мне начинать с любого места в дереве сайта, а затем впоследствии фильтрует всех детей> внуков> пра-внуков и т. д. Кроме того, он сокращает sql до короткого быстрого запросы в отличие от множества сложных объединений. В вашем случае, возможно, потребуется внести некоторые изменения, но я думаю, что это может сработать.

/**
 * build_site_tree
 *
 * @return void
 * @author Mike Waites
**/
public function build_site_tree($parent_id)
{
    return $this->find_children($parent_id);
}

/** end build_site_tree **/

// -----------------------------------------------------------------------

/**
 * find_children
 * Recursive loop to find parent=>child relationships
 *
 * @return array $children
 * @author Mike Waites
**/
public function find_children($parent_id)
{
    $this->benchmark->mark('find_children_start');

    if(!class_exists('Account_model'))
            $this->load->model('Account_model');

    $children = $this->Account_model->get_children($parent_id);

    /** Recursively Loop over the results to build the site tree **/
    foreach($children as $key => $child)
    {
        $childs = $this->find_children($child['id']);

        if (count($childs) > 0)
                $children[$key]['children'] = $childs;
    }

    return $children;

    $this->benchmark->mark('find_children_end');
}

/** end find_children **/

Как вы можете видеть, это довольно упрощенная версия, и имейте в виду, что она была встроена в codeigniter, поэтому вам нужно будет модифицировать ее для набора, но в основном у нас есть цикл, который вызывает себя, добавляя в массив каждый раз, когда он идет. , Это позволит вам получить все дерево или даже начать с какой-либо точки в дереве, если у вас есть parent_id avaialble первым!

Надеюсь, это поможет

1 голос
/ 20 апреля 2011

Нет хорошего / простого способа сделать это;базы данных плохо поддерживают структуры данных в древовидном стиле.

Вам нужно будет работать по уровням, чтобы обрезать результаты от потомка к родителю, или создать представление, которое дает все 9 поколений.из данного узла и сопоставьте, используя OR на потомках.

0 голосов
/ 20 апреля 2011

Вы можете сделать это с помощью хранимой процедуры следующим образом:

Пример звонков

mysql> call names_hier(1, 'a');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  2 | ali      |      1 | f00         |     1 |
|  8 | anna     |      6 | keira       |     4 |
+----+----------+--------+-------------+-------+
2 rows in set (0.00 sec)

mysql> call names_hier(3, 'k');
+----+----------+--------+-------------+-------+
| id | emp_name | parent | parent_name | depth |
+----+----------+--------+-------------+-------+
|  6 | keira    |      5 | eva         |     2 |
+----+----------+--------+-------------+-------+
1 row in set (0.00 sec)

$sqlCmd = sprintf("call names_hier(%d,'%s')", $id, $name);  // dont forget to escape $name
$result = $db->query($sqlCmd);

Полный скрипт

drop table if exists names;
create table names
(
id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent smallint unsigned null,
key (parent)
)
engine = innodb;

insert into names (name, parent) values
('f00',null), 
  ('ali',1), 
  ('megan',1), 
      ('jessica',3), 
      ('eva',3), 
         ('keira',5), 
            ('mandy',6), 
            ('anna',6);

drop procedure if exists names_hier;

delimiter #

create procedure names_hier
(
in p_id smallint unsigned,
in p_name varchar(255)
)
begin

declare v_done tinyint unsigned default(0);
declare v_dpth smallint unsigned default(0);

set p_name = trim(replace(p_name,'%',''));

create temporary table hier(
 parent smallint unsigned, 
 id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select parent, id, v_dpth from names where id = p_id;

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

create temporary table tmp engine=memory select * from hier;

while not v_done do

    if exists( select 1 from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth) then

        insert into hier select n.parent, n.id, v_dpth + 1 
            from names n inner join tmp on n.parent = tmp.id and tmp.depth = v_dpth;

        set v_dpth = v_dpth + 1;            

        truncate table tmp;
        insert into tmp select * from hier where depth = v_dpth;

    else
        set v_done = 1;
    end if;

end while;


select 
 n.id,
 n.name as emp_name,
 p.id as parent,
 p.name as parent_name,
 hier.depth
from 
 hier
inner join names n on hier.id = n.id
left outer join names p on hier.parent = p.id
where
 n.name like concat(p_name, '%');

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

-- call this sproc from your php

call names_hier(1, 'a');
call names_hier(3, 'k');
0 голосов
/ 20 апреля 2011

Не существует одного SQL-запроса, который вернул бы данные в древовидном формате - вам нужно обработать их в нужном порядке.

Один из способов - запросить MySQL для возврата MPTT:

SELECT * FROM таблицы ORDER BY parent asc;

корень дерева будет первым элементом таблицы, его дочерние элементы будут следующими, и т. Д., Дерево будет перечислено «в ширину первым» (в слояхувеличение глубины)

Затем используйте PHP для обработки данных, превратив их в объект, который содержит структуру данных.

В качестве альтернативы, вы можете реализовать функции поиска MySQL, которые предоставляют узел, рекурсивно искать ивернуть таблицу всех ее потомков или таблицу всех своих предков.Поскольку эти процедуры имеют тенденцию быть медленными (будучи рекурсивными, возвращая слишком много данных, которые затем фильтруются по другим критериям), вы хотите делать это только в том случае, если вы знаете, что не запрашиваете такие данные снова и снова, или если вызнать, что набор данных остается небольшим (9 уровней и насколько?)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...