Select ancestors using a query as a recursive function

I have a database table menu that has id, name and parentid.

I have the following values โ€‹โ€‹in the database, I want to collect all the fields, including the parent menus, using the query.

   id  name      parentid
   1   File         0
   2   New          1
   3   Document     2
   4   Image        2
   5   Edit         0
   6   Copy         5
   7   Paste        5

Example: I have 2 as my current menu, I want to select all the fields with the parent Id 2and parents and parents of the parents, until it reaches the top parent (i.e. using parentid=0).

Is it possible to assemble it with a single query? If so, how can this be achieved?

+3
source share
2 answers

, , , , , , .

, , . , .

Joe Celko , .

,

+2

, . , - !

, , php!

call menus_hier_downward(1);
call menus_hier_upward(3);

- , :)

call menus_hier_downward(1);
+---------+-----------+-----------+------------------+-------+
| menu_id | menu_name | parent_id | parent_menu_name | depth |
+---------+-----------+-----------+------------------+-------+
|       1 | File      |      NULL | NULL             |     0 |
|       2 | New       |         1 | File             |     1 |
|       3 | Document  |         2 | New              |     2 |
|       4 | Image     |         2 | New              |     2 |
+---------+-----------+-----------+------------------+-------+
4 rows in set (0.00 sec)

call menus_hier_upward(3);
+---------+-----------+-----------+------------------+-------+
| menu_id | menu_name | parent_id | parent_menu_name | depth |
+---------+-----------+-----------+------------------+-------+
|       3 | Document  |         2 | New              |     1 |
|       2 | New       |         1 | File             |     2 |
|       1 | File      |      NULL | NULL             |     3 |
+---------+-----------+-----------+------------------+-------+
3 rows in set (0.00 sec)

. . script :

drop table if exists menus;
create table menus
(
menu_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_id smallint unsigned null,
key (parent_id)
)
engine = innodb;

insert into menus (name, parent_id) values
('File',null), 
 ('New',1), 
   ('Document',2), 
   ('Image',2), 
('Edit',null), 
 ('Copy',5), 
 ('Paste',5);

drop procedure if exists menus_hier_downward;

delimiter #

create procedure menus_hier_downward
(
in p_menu_id smallint unsigned
)
begin

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

create temporary table hier(
 parent_id smallint unsigned, 
 menu_id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select parent_id, menu_id, v_dpth from menus where menu_id = p_menu_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 menus m inner join hier on m.parent_id = hier.menu_id and hier.depth = v_dpth) then

        insert into hier select m.parent_id, m.menu_id, v_dpth + 1 
            from menus m inner join tmp on m.parent_id = tmp.menu_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 
 m.menu_id,
 m.name as menu_name,
 p.menu_id as parent_id,
 p.name as parent_menu_name,
 hier.depth
from 
 hier
inner join menus m on hier.menu_id = m.menu_id
left outer join menus p on hier.parent_id = p.menu_id;

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

end #

delimiter ;

drop procedure if exists menus_hier_upward;

delimiter #

create procedure menus_hier_upward
(
in p_menu_id smallint unsigned
)
begin

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

create temporary table hier(
 parent_id smallint unsigned, 
 menu_id smallint unsigned, 
 depth smallint unsigned
)engine = memory;

insert into hier select menu_id, null, v_dpth from menus where menu_id = p_menu_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 menus m inner join hier on m.menu_id = hier.parent_id and hier.depth = v_dpth) then

        insert into hier select m.parent_id, m.menu_id, v_dpth + 1 
            from menus m inner join tmp on m.menu_id = tmp.parent_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 
 m.menu_id,
 m.name as menu_name,
 p.menu_id as parent_id,
 p.name as parent_menu_name,
 hier.depth
from 
 hier
inner join menus m on hier.menu_id = m.menu_id
left outer join menus p on hier.parent_id = p.menu_id;

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

end #

delimiter ;
+4

Source: https://habr.com/ru/post/1796731/


All Articles