获取所有类别及其子项

时间:2015-11-03 22:47:30

标签: php mysql recursion

我即将给出100个赏金点来回答这个问题

所以我对递归有一个非常困难的问题 - 如何获得所有项目的类别和包含该父项的所有子项,并且直到最后更深入?

我有桌子:

+----+---------------+-----------------+
| id | category name | category_parent |
+----+---------------+-----------------+
| 1  | cars          |        0        |
+----+---------------+-----------------+
| 2  | real estate   |        0        |
+----+---------------+-----------------+
| 3  | clothes       |        0        |
+----+---------------+-----------------+
| 4  | bmw           |        1        |
+----+---------------+-----------------+
| 5  | audi          |        1        |
+----+---------------+-----------------+
| 6  | 100           |        5        |
+----+---------------+-----------------+
| 7  | 80            |        5        |
+----+---------------+-----------------+
| 8  | A4            |        5        |
+----+---------------+-----------------+
| 9  | QUATRO        |        8        |
+----+---------------+-----------------+
| 10 | TDI           |        8        |
+----+---------------+-----------------+
| 11 | Black         |        9        |
+----+---------------+-----------------+
| 12 | White         |        9        |
+----+---------------+-----------------+
| 13 | 2 doors       |        11       |
+----+---------------+-----------------+
| 14 | 5 doors       |        11       |
+----+---------------+-----------------+

我的产品表看起来像这样:

+----+---------------+-----------------+
| id | category_id   | name            |
+----+---------------+-----------------+

例如,我想计算cars类别中的所有项目。所以基本上我应该传递这个类别id (1)并以某种方式进行递归来计算所有项目。但是我不知道如何处理它,因为那个类别的孩子可以是无限的。

所以当我想知道那个父母计数的所有项目时,我应该做这样的事情:

1++:
  4++
  5++:
    6++
    7++
    8++:
      9++:
        11++:
           13++
           14++
        12++
      10++

我希望你能理解我需要什么,并给我任何可以帮助我的建议。

另外,这是我到目前为止所做的开始 - 我可以实现它但是将来我会陷入递归......所以它没有任何价值。

public function get_category_tree_id_list($cat_id, $list_array = FALSE)
{
    if ( !$list_array ){
        $items = $this->system->_getCustomTableData('categories', array(array('category_parent' => $cat_id)), 'id DESC');
        $this->__tmp['id_list'] = [];
        foreach ( $items as $key => $value ) {
            $this->__tmp['id_list'][] = $value['id'];
        }
    }        
}

7 个答案:

答案 0 :(得分:14)

您很可能想要嵌套集。它们设置起来有点棘手,但使查询更简单。因此,您将有两列 - lftrgt,而不是类别父级。左侧和右侧基本上是类别的边界,如果项目的类别ID在这些值之间,您知道它是该类别的子级。

+----+---------------+-----+------+
| id | category name | lft | rgt  |
+----+---------------+-----+------+
| 1  | cars          |  1  |  24  |
+----+---------------+-----+------+
| 2  | bmw           |  2  |  3   |
+----+---------------+-----+------+
| 5  | audi          |  4  | 23   |
+----+---------------+-----+------+
| 6  | 100           |  5  |  6   |
+----+---------------+-----+------+
| 7  | 80            |  7  |  8   |
+----+---------------+-----+------+
| 8  | A4            |  9  | 22   |
+----+---------------+-----+------+
| 9  | TDI           |  10 | 11   |
+----+---------------+-----+------+
| 10 | Quatro        |  12 | 21   |
+----+---------------+-----+------+
| 11 | Black         |  13 | 18   |
+----+---------------+-----+------+
| 12 | White         |  19 |  20  |
+----+---------------+-----+------+
| 13 | 2 doors       |  14 |  15  |
+----+---------------+-----+------+
| 14 | 5 doors       |  16 | 17   |
+----+---------------+-----+------+

然后,为了获得汽车类别中的物品数量,你可以像这样简单地完成:

SELECT categories.name, items.id, items.category_id, items.name 
FROM categories 
LEFT JOIN items 
    ON (items.category_id BETWEEN categories.lft AND categories.rgt)
WHERE categories.category_name = 'cars'

显然,您只需更改category_name的值并获取任何类别的项目。

很抱歉,出于某种原因,我在此处上传图片时图像已旋转,但如果您将类别绘制为圆圈,然后对线条进行编号,则可以看到左右值应该是什么。

我只做了汽车,因为我认为你可以推断其他类别。

enter image description here

所以如果你写出这样的类别:

Cars(BMW(), Audi(100(),80(),A4(TDI(),Quatro(Black(2dr(),5dr()), White())))

然后你可以用数字标记括号:

Cars[1]->(BMW[2]->()<-[3], Audi[4]->(100[5]->()<-[6],80[7]->()<-[8],A4[9]->(TDI[10]->()<-[11],Quatro[12]->(Black[13]->(2dr[14]->()<-[15], 5dr[16]->()<-[17])<-[18], White[19]->()<-[20])<-[21])<-[22])<-[23])<-[24]

或者,如果您将其标记为树,则可以将其标记为这样,在此处用最大的数字标记最左边的节点,并在标记所有节点的子节点时仅标记右节点:

enter image description here

答案 1 :(得分:4)

我有一个新想法,我觉得它会很好。 这个想法是这样的: 在category_parent列中,我们将插入对该节点的所有父节点的引用。

+----+---------------+-----------------+
| id | category name |    hierarchy    |
+----+---------------+-----------------+
| 1  | cars          |        1        |
+----+---------------+-----------------+
| 2  | real estate   |        2        |
+----+---------------+-----------------+
| 3  | clothes       |        3        |
+----+---------------+-----------------+
| 4  | bmw           |       1-4       |
+----+---------------+-----------------+
| 5  | audi          |       1-5       |
+----+---------------+-----------------+
| 6  | 100           |      1-4-6      |
+----+---------------+-----------------+
| 7  | 80            |      1-4-7      |
+----+---------------+-----------------+
| 8  | A4            |      1-4-8      |
+----+---------------+-----------------+
| 9  | QUATRO        |     1-4-8-9     |
+----+---------------+-----------------+
| 10 | TDI           |     1-4-8-10    |
+----+---------------+-----------------+
| 11 | Black         |    1-4-8-9-11   |
+----+---------------+-----------------+
| 12 | White         |   1-4-8-9-12    |
+----+---------------+-----------------+
| 13 | 2 doors       |  1-4-8-9-11-13  |
+----+---------------+-----------------+
| 14 | 5 doors       |  1-4-8-9-11-14  |
+----+---------------+-----------------+

如果你查看我的更新表,你会发现每条记录都有一个链接到它的父母,不仅是直接的,还有所有的父母。 为了这项工作,我做了一些修改,插入:

Insert into table_name (category_name, hierarchy) values ('new_name', (concat(parent_hierarch, '-', (SELECT Auto_increment FROM information_schema.tables WHERE table_name='table_name'))))

现在让我们进行所需的查询:

1-所有子类别的汽车:

select * from table_name where hierarchy like '1-%'

2-如果你需要BLACK的所有父母,你只需输入:

select * from table_name where hierarchy = '1-4-8-9' or hierarchy = '1-4-8' or hierarchy = '1-4' or hierarchy = '1'

(您可以从php构建该查询,在' - 'char处拆分层次结构字段)

3-查看具有级别和直接父级的所有类别:

select *, SUBSTR(hierarchy, 1, (LENGTH(hierarchy) - LENGTH(id) - 1)) as parent, LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '-', '')) as level From table_name
+----+---------------+-----------------+-----------+--------+
| id | category name |    hierarchy    |   parent  |  level |
+----+---------------+-----------------+-----------+--------+
| 1  | cars          |        1        |           |    0   |
+----+---------------+-----------------+-----------+--------+
| 2  | real estate   |        2        |           |    0   |
+----+---------------+-----------------+-----------+--------+
| 3  | clothes       |        3        |           |    0   |
+----+---------------+-----------------+-----------+--------+
| 4  | bmw           |       1-4       |     1     |    1   |
+----+---------------+-----------------+-----------+--------+
| 5  | audi          |       1-5       |     1     |    1   |
+----+---------------+-----------------+-----------+--------+
| 6  | 100           |      1-4-6      |    1-4    |    2   |
+----+---------------+-----------------+-----------+--------+
| 7  | 80            |      1-4-7      |    1-4    |    2   |
+----+---------------+-----------------+-----------+--------+
| 8  | A4            |      1-4-8      |    1-4    |    2   |
+----+---------------+-----------------+-----------+--------+
| 9  | QUATRO        |     1-4-8-9     |   1-4-8   |    3   |
+----+---------------+-----------------+-----------+--------+
| 10 | TDI           |     1-4-8-10    |   1-4-8   |    3   |
+----+---------------+-----------------+-----------+--------+
| 11 | Black         |    1-4-8-9-11   |  1-4-8-9  |    4   |
+----+---------------+-----------------+-----------+--------+
| 12 | White         |   1-4-8-9-12    |  1-4-8-9  |    4   |
+----+---------------+-----------------+-----------+--------+
| 13 | 2 doors       |  1-4-8-9-11-13  |1-4-8-9-11 |    5   |
+----+---------------+-----------------+-----------+--------+
| 14 | 5 doors       |  1-4-8-9-11-14  |1-4-8-9-11 |    5   |
+----+---------------+-----------------+-----------+--------+

这是一个新想法,需要一些改进。 我希望你能从中受益。

答案 2 :(得分:3)

您可能希望查看有关在MySQL中处理树结构数据的建议的文章。

http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql

您选择采用的方法可能取决于您的应用程序读写用例包含的内容。

目前,您正在使用邻接列表模型,这在使用任意树深度类型的查询时尤其成问题。如果这是您的主要用例,您可能需要考虑嵌套集方法,虽然可能不那么直观,但更适合任意深度树查询。

嵌套集方法的挑战是对表的更新通常更难以管理。

答案 3 :(得分:1)

要调用它,只需将对象targetId构造为参数:

喜欢:

$counter = new RecursiveCounter(8);
$count = $counter->getCount();

班级:

class RecursiveCounter {
private $row;
private $targetId;

public function __construct($targetId) {
    //Just setting up the info I need. You need to consider how to get the data from database and replace the constructor
    $this->row = array(
        1 => array("category" => "cars", "parent" => 0),
        2 => array("category" => "realestate", "parent" => 0),
        3 => array("category" => "clothes", "parent" => 0),
        4 => array("category" => "bmw", "parent" => 1),
        5 => array("category" => "audi", "parent" => 1),
        6 => array("category" => "100", "parent" => 5),
        7 => array("category" => "80", "parent" => 5),
        8 => array("category" => "A4", "parent" => 5),
        9 => array("category" => "QUATRO", "parent" => 8),
        10 => array("category" => "TDI", "parent" => 8),
        11 => array("category" => "Black", "parent" => 9),
        12 => array("category" => "White", "parent" => 9),
        13 => array("category" => "doors", "parent" => 11),
        14 => array("category" => "doors", "parent" => 11)
    );
    $this->targetId = $targetId;
}

public function getCount() {
    // Entry point
    $count = 0;
    foreach ($this->row as $id => $row) {
        if ($this->isMatchTarget($id)) {
            $count++;
        }
    }
    return $count;
}
private function getParent($id) {
    $parentId = $this->row[$id]["parent"];
    if (array_key_exists($parentId, $this->row)) {
        return $parentId;
    } else {
        return false;
    }
}

private function isMatchTarget($id) {
    // 1. If the supplied id is the target id, job is done and return true;
    // 2. If not:
    //      Get the parent ud;
    //      If parent id is not 0 (Meaning it has a parent), keep on checking
    //          What to check? Check if the parent id is matching
    //          If the the parent id is still not equal to target or 0, it will check the parent of parent until it they are equal or it is 0
    //      if there is no parent, and the id dont match (Ending condidtion)
    //          return false;
    if ($id == $this->targetId) {
        return true;
    } else {
        $parentId = $this->getParent($id);
        if (0 != $parentId) {
            return $this->isMatchTarget($parentId);
        } else {
            return false;
        }
    }
}

}

答案 4 :(得分:1)

您可以在php中使用递归函数来获取类别及其子项中的产品数量

public function get_number_of_products_in_category($cat_id)
{
    $qty = 0;

    //get number of product in this category
    $nb_products = $this->system->_getRowsCount('products', array(array('category_id' => $cat_id)));
    $qty += $nb_products;

    //get all child categories
    $items = $this->system->_getCustomTableData('categories', array(array('category_parent' => $cat_id)), 'id DESC');

    //add number of products in the child category
    if(!empty($items)) {
        foreach ( $items as $key => $value ) {
            $qty += get_number_of_products_in_category($value['id']);
        }
    }

    return $qty;
}

要加快查询速度,您应该为这些列设置关键字:

    产品表中的
  • category_id
  • category_parent in categories table

答案 5 :(得分:1)

如果正确使用索引,在MySQL中使用索引几乎不使用任何资源。因此,您可以计算每个类别的产品数量,然后使用PHP来完成这项工作。

$query="SELECT c.*, count(p.*) total
      FROM category, product p on c.id=p.category_id
      GROUP BY c.id;";

假设您将上一个查询的结果存储在$result_categories数组中

 function getMatchingItemsNb($result_categories, $category_id) {
    $sum=0;
    foreach ($result_categories as $row) {
      if ($row['id']==$category_id) {
         $sum+=$row['total'];
      } else if ($row['category_parent']==$category_id) {
         $sum+=getMatchingItemsNb($result_categories, $row['category_parent']);
      }
    }
    return $sum;
 }

这只需要1个SQL请求,这将有利于索引和缓存。它可用于获取多个类别,而无需两次查询mysql。

为了获得更好的性能,您还可以考虑在total表中添加category字段,即使它意味着一些冗余,但我认为修改数据结构不是此处的主题。 / p>

答案 6 :(得分:0)

基本上无论数据库表中的数据是什么,我们都必须首先通过java将其加载到内存中,而不是下面的magic begin,希望它找到好

ORA-08103: Object No Longer Exists