0

this is my database structure for my terms table (categories):

   id   |   name   |   parent_id   |   level   |   position   
--------------------------------------------------------------
   1    |   term 1 |   NULL        |   0       |   1
   2    |   term 2 |   1           |   1       |   1
   3    |   term 3 |   1           |   1       |   2
   4    |   term 4 |   NULL        |   0       |   2
   5    |   term 5 |   4           |   1       |   1

so terms 2 and 3 are 1st level children of 1 and 5 is a first lst level child of 4

this is my query: (and it's not correct, this should be fixed)

SELECT
    `id`,
    `name`
FROM
    `terms`
ORDER BY
    `position` ASC,
    `level` ASC

this is my php:

$terms = array();

// query part

if(!$this->_db->resultRows > 0)
  return $terms;

while($d = $this->_db->fetch($r))
{
  $terms[$d->id] = new Term($d->id);
}     

return $terms;

current result:

term 1
  term 2
  term 5
term 4
 term 3

but the result should be:

term 1
  term 2
  term 3
term 4
  term 5

I don't know how to alter the query to get the correct result
the goal is to output this in a (multiple) select box
I know how to do it with nested lists, but you can't nest a select

Ruben
  • 8,956
  • 14
  • 63
  • 102
  • MySQL doesn't support recursive functions, so it is not well suited to this adjacency list model of storing hierarchical data. You ought to consider restructuring your data to use either nested sets or closure tables. See [this answer](http://stackoverflow.com/a/192462/623041) for more information. – eggyal Jun 29 '12 at 18:18
  • Do you know the limit of levels? Currently there are only 2 levels. Does the query need to deal with more than 2 levels? – ghbarratt Jun 29 '12 at 18:56
  • yes the levels should be unlimited – Ruben Mar 24 '14 at 09:34

1 Answers1

0

Okay, so there are many ways to deal with this type of problem. All the ways that I will present do not require there to be a level column in the table. In my opinion that is redundant data since it can be deduced from the information found in the other columns.

If you know the maximum "level" will only be 1 (maximum depth is 2) you can use a query like this:

SELECT
        t.`id`,
        t.`name`,
        IF(p.`position` IS NULL, t.`position`*{$row_count}, p.`position`*{$row_count}+t.`position`) AS display_order
FROM
        `terms` t
        LEFT JOIN `terms` p ON p.`id` = t.`parent_id`
ORDER BY
        display_order

where $row_count is calculated:

SELECT COUNT(*) FROM `terms`

There are ways to modify this SQL to make it work with more levels (depth), but the query needs to get bigger with each maximum level/depth it will support.


If you are uncertain about the number of levels you will have, then you should probably just do something like this:

$dbh->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
$sth = $dbh->prepare('SELECT id,name FROM terms WHERE parent_id IS NULL ORDER BY position');
$sth->execute(array(NULL));
$terms = $sth->fetchAll();

$sql = 'SELECT id,name FROM terms WHERE parent_id = ? ORDER BY position';
$terms_to_check = $terms;
$terms = array();
while(count($terms_to_check))
{
        $k = array_shift($terms_to_check);
        $terms[] = $k;
        $sth = $dbh->prepare($sql);
        $sth->execute(array($k['id']));
        $results = $sth->fetchAll();
        $terms_to_check = array_merge($results, $terms_to_check);
}

(By the way I recommend using PDO.)

ghbarratt
  • 11,496
  • 4
  • 41
  • 41