1

can you please help me write SQL select?

I got table:

REGIONS

  • id (int)
  • name (varchar(50))
  • parent_id (int)

where "parent_id" referees to "id". Rows with parent_id set to 0 has no parent and are on the top. I need to select data as tree.

Right know Im using recursive function :)

function SelectData($parent=0, $padding='')
{
 $q = mysql_query("SELECT * FROM regions WHERE parent=$parent");
 while($row = mysql_fetcharray($q))
 {
  echo($row[name]);
  priklad($row[id], $padding.'  ');
 }
}

I found out it is possible to do with CTE ( Can I select full hierarchy of parents when id and parent id are in the same table? ), but also found out that MySQL doesn't support CTE.

So, is there any way to use one select instead of recursive function in MySQL ? Thanks :)

Community
  • 1
  • 1
Buksy
  • 11,571
  • 9
  • 62
  • 69

4 Answers4

1

You can use a non recursive mysql stored procedure which you can call from your php as follows:

Example calls

$result = $conn->query(sprintf("call category_hier(%d)", $catID));

mysql> call category_hier(1);
+--------+---------------+---------------+----------------------+-------+
| cat_id | category_name | parent_cat_id | parent_category_name | depth |
+--------+---------------+---------------+----------------------+-------+
|      1 | Location      |          NULL | NULL                 |     0 |
|      3 | USA           |             1 | Location             |     1 |
|      5 | Chicago       |             3 | USA                  |     2 |
|      4 | Illinois      |             3 | USA                  |     2 |
+--------+---------------+---------------+----------------------+-------+
4 rows in set (0.00 sec)

mysql> call category_hier(2);
+--------+---------------+---------------+----------------------+-------+
| cat_id | category_name | parent_cat_id | parent_category_name | depth |
+--------+---------------+---------------+----------------------+-------+
|      2 | Color         |          NULL | NULL                 |     0 |
|      6 | Black         |             2 | Color                |     1 |
|      7 | Red           |             2 | Color                |     1 |
+--------+---------------+---------------+----------------------+-------+
3 rows in set (0.00 sec)

Full script and test data below

drop table if exists categories;
create table categories
(
cat_id smallint unsigned not null auto_increment primary key,
name varchar(255) not null,
parent_cat_id smallint unsigned null,
key (parent_cat_id)
)
engine = innodb;

insert into categories (name, parent_cat_id) values
('Location',null), 
('Color',null), 
   ('USA',1), 
      ('Illinois',3), 
      ('Chicago',3), 
   ('Black',2), 
   ('Red',2);


drop procedure if exists category_hier;
delimiter #

create procedure category_hier
(
in p_cat_id smallint unsigned
)
begin

declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;

create temporary table hier(
 parent_cat_id smallint unsigned, 
 cat_id smallint unsigned, 
 depth smallint unsigned default 0
)engine = memory;

insert into hier select parent_cat_id, cat_id, v_depth from categories where cat_id = p_cat_id;
create temporary table tmp engine=memory select * from hier;

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

while not v_done do

    if exists( select 1 from categories c
        inner join tmp on c.parent_cat_id = tmp.cat_id and tmp.depth = v_depth) then

        insert into hier select c.parent_cat_id, c.cat_id, v_depth + 1 from categories c
            inner join tmp on c.parent_cat_id = tmp.cat_id and tmp.depth = v_depth;

        set v_depth = v_depth + 1;          

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

    else
        set v_done = 1;
    end if;

end while;

select 
 c.cat_id,
 c.name as category_name,
 p.cat_id as parent_cat_id,
 p.name as parent_category_name,
 hier.depth
from 
 hier
inner join categories c on hier.cat_id = c.cat_id
left outer join categories p on hier.parent_cat_id = p.cat_id
order by
 hier.depth;

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

end #

delimiter ;

call category_hier(1);

call category_hier(2);

Hope this helps :)

Jon Black
  • 16,223
  • 5
  • 43
  • 42
0

step1->

select distinct ID from tbl where PARENT_ID=0

store the value in an array, and then for each of the value

step 2->

select ID from TBL where PARENT_ID = $values_from_array

or perhaps this will work

select ID from TBL where PARENT_ID in (select distinct ID from tbl where PARENT_ID=0) order by PARENT_ID
Sourav
  • 17,065
  • 35
  • 101
  • 159
  • thats what my function is doing now, but more in deep. I mean,one row can have parent, which can have parent, which can have parent,... I'm just curious if there's way to do id in one Query select – Buksy Apr 06 '11 at 13:25
0

A part of code from my MyBB plugin. its creating a combobox from hierarcical forums->sub->sub

function generate_video_category_select($name, $selectedId, $parent_id = 0, $depth = 1)
        {   
            global $db;
            $box = '';
            if($depth == 1)
            {
                $box .= '<select name="' . $name . '">';
                $box .='<option value="0" selected="selected">Boş</option>';
            }


            $sql = "SELECT * FROM ".TABLE_PREFIX."aofvideo_categories WHERE parent_id = ".$parent_id." ORDER BY name ASC";  

            $result = $db->query($sql);
            while($row = $db->fetch_array($result))
            {   
                $box .='<option value="'.$row['id'].'"'.($selectedId == $row['id'] ? ' selected="selected"' : '').'>'.str_repeat('&nbsp;',$depth).$row['name'].'</option>';
                $box .= generate_video_category_select($name,$selectedId,$row['id'],$depth + 5);
            }

            if($depth == 1)
                $box .="</select>";


            return $box;
        }
0

Whenever you need to extract information from a table that is dependent on other information in the same table it is a good idea to use two 'virtual' tables that refer to the same one. In the case where you know that the relationship is only two deep you could use the following:

select a.name, b.name, from religion a, religion b where a.id=b.parent_id

an example for 3 would be the following:

select a.name, b.name, c.name from religion a, religion b, religion c where a.id=b.parent_id

Hope that helps.

Lloyd Moore
  • 3,117
  • 1
  • 32
  • 32