получить все предметы категории и ее ребенка

Я собираюсь дать 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 . Поэтому в основном я должен передать идентификатор этой категории (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']; } } } 

Скорее всего, вы захотите сделать вложенные наборы. Они немного сложны в настройке, но делают запросы намного проще. Итак, вместо родительской категории у вас будет два столбца – rgt и rgt . Влево и вправо – это в основном границы категории, если идентификатор категории элементов находится между этими значениями, вы знаете, что он является дочерним элементом этой категории.

 +----+---------------+-----+------+ | 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 и получить элементы в ЛЮБОЙ категории.

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

Я только делал автомобили, так как понял, что вы можете экстраполировать, чтобы получить другие категории.

введите описание изображения здесь

Поэтому, если вы выписываете свои категории следующим образом:

 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] 

Или, если вы нарисуете его как дерево, вы можете пометить его таким образом, чтобы вы пометили левый самый узел номером и указали только нужный узел, когда вы отметили все его дочерние элементы:

введите описание изображения здесь

У меня есть новая идея, я думаю, это будет хорошо. Идея такова: в столбце category_parent мы вставим ссылку на всех родителей этого узла.

 + ---- + --------------- + ----------------- +
 |  id |  название категории |  иерархия |
 + ---- + --------------- + ----------------- +
 |  1 |  автомобили |  1 |
 + ---- + --------------- + ----------------- +
 |  2 |  недвижимость |  2 |
 + ---- + --------------- + ----------------- +
 |  3 |  одежда |  3 |
 + ---- + --------------- + ----------------- +
 |  4 |  bmw |  1-4 |
 + ---- + --------------- + ----------------- +
 |  5 |  аудио |  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 |  Черный |  1-4-8-9-11 |
 + ---- + --------------- + ----------------- +
 |  12 |  Белый |  1-4-8-9-12 |
 + ---- + --------------- + ----------------- +
 |  13 |  2 двери |  1-4-8-9-11-13 |
 + ---- + --------------- + ----------------- +
 |  14 |  5 дверей |  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, разбивая поле иерархии на символе '-')

3- Чтобы увидеть все категории, с уровнем и прямым родителем:

 select *, SUBSTR(hierarchy, 1, (LENGTH(hierarchy) - LENGTH(id) - 1)) as parent, LENGTH(hierarchy) - LENGTH(REPLACE(hierarchy, '-', '')) as level From table_name 
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  id |  название категории |  иерархия |  родитель |  уровень |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  1 |  автомобили |  1 |  |  0 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  2 |  недвижимость |  2 |  |  0 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  3 |  одежда |  3 |  |  0 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  4 |  bmw |  1-4 |  1 |  1 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  5 |  аудио |  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 |  Черный |  1-4-8-9-11 |  1-4-8-9 |  4 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  12 |  Белый |  1-4-8-9-12 |  1-4-8-9 |  4 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  13 |  2 двери |  1-4-8-9-11-13 | 1-4-8-9-11 |  5 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +
 |  14 |  5 дверей |  1-4-8-9-11-14 | 1-4-8-9-11 |  5 |
 + ---- + --------------- + ----------------- + ---------- - + -------- +

Это новая идея и нуждается в улучшении. Надеюсь, вам это удастся.

Вы можете посмотреть эту статью на предложениях по обработке древовидных данных в MySQL.

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

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

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

Проблема с вложенным набором методов заключается в том, что обновление таблицы обычно сложнее в управлении.

Чтобы вызвать это, просто создайте объект 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; } } } 

}

Вы можете использовать рекурсивную функцию в 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 в таблице категорий

Использование индексов в 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; } 

Для этого требуется только один запрос SQL, который будет полезен для индексов и кеша. И его можно использовать, чтобы получить общее количество категорий без запроса mysql дважды.

Для повышения производительности вы также можете рассмотреть возможность добавления total поля в таблицу category , даже если это подразумевает некоторую избыточность, но я предполагаю, что изменение структуры данных здесь не является предметом.