За последние 24 часа нас посетили 18033 программиста и 1700 роботов. Сейчас ищут 1530 программистов ...

использовние графов для организации древовидной структуры.

Тема в разделе "PHP и базы данных", создана пользователем dark-demon, 16 апр 2007.

  1. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    каждый узел фактически указывает на список узлов-предков. у братьев это идентичные списки. когда мы добавляем узлу первое дитя, мы формирует для этого дитя список предков, который состоит из самого узла к которому добавляют дитя и всех его предков.а уже добавляя к нему братьев мы просто копируем указатель на список предков.
     
  2. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Народ, появилась задачка, для которой нужно реализовать дерево в таблице.

    Дерево будет являться каталогом с произвольной вложенностью (на деле дальше 5-ого уровня вряд-ли дойдёт, но всё же и это тоже возможно).
    К этой структуре будут привязываться миллионы записей, прявязать запись можно к любому элементу любого уровня (к рутовому тоже), даже если у этого элемента есть дети.
    Планируеться высокая нагрузка (и работать всё это будет на кластере, поэтому служебных данных должно быть минимально на столько, на сколько это возможно), добавляться записи будут очень часто и много.

    Подскажите оптимальный вариант пожайлуста (переносить юзлы нельзя - их можно только создать или удалить, при этом при удалении все записи удаляемого узла и его потомков присваиваються родителю; все узлы-дети становяться детями родительского узла (либо они вообще остаються вне дерева)
     
  3. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    дык эта, последний мой вариант просто идеально подходит :)
     
  4. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    Psih
    Не слушай его, он тебе насоветует ;) если записи будут отдельно, дерево отдельно, то вводить дополнительно третью таблицу для связей - крах для нагруженной системы.
    Если само дерево категорий будет сравнительно небольшим, и изменяться редко - Nested Sets подходят идеально.
     
  5. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    1. многомилионный каталог изменяется редко, но метко :)
    2. откуда ты взял третюю таблицу??? в моей схеме только две таблицы: таблица данных и таблица связей. третяя таблица предначена только для формирования уникальных идентификаторов родительских групп. в выборках она не учавствует вообще. при изменениях её влияние стремится к нулю.

    более того, я ещё подумал и готов выложить третюю редакцию :)
    если в кратце, то в качестве "уникальных идентификаторов родительских групп" может выступать сам родитель. таким образом мы убиваем двух зайцев: у нас есть и связи "родитель-ребёнок" и связи "предок-потомок".
    если развёрнуто, то логика такая:
    первым делом реализуем классическую таблицу смежностей.
    вторым - добавляем таблицу "потомственных связей", где для каждого узла не являющегося листом (имеющего от 1 ребёнка) задаются связи его ид с ид его предков, плюс - связь его с им же (можно и без этого, но тогда во все выборки придётся добавлять дополнительное условие).

    выборки получаются примерно такими же, как и в предыдущем варианте. плюс, мы можем быстро выбирать детей и родителя.
     
  6. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Третья таблица - это сами записи (содержимое каталога), частью каталога они не являются, и судя по всему, у них будут другие совсем атрибуты.
    "в выборках она не учавствует вообще" - а зачем она тогда вообще нужна? :shock:
     
  7. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    Народ, давайте мож какие-то конкретные предложения? К примеру хотя бы структуру таблиц, дальше то я сам разберусь. Прост реально надо и было бы хорошо если бы опытные люди показали хороший пример (а я не настолько туп, что бы применить самому не подумав)
     
  8. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    почему содержимое каталога не является частью каталога?

    уже не нужно :) читай третюю редакцию :D

    ну думай :) выбор-то не особо и большой... таблицы смежности или вложенные множества. плюс их модификации в том числе и моя. плюсы и минусы тебе известны - примеряй на свою проблему :)
     
  9. Dagdamor

    Dagdamor Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    2.095
    Симпатии:
    1
    Адрес:
    Барнаул
    dark-demon
    Потому что структура данных различается. Каталог - одна структура (id, parentid и название), записи - другая (например, для каталога сайтов - id, catid, URL, название, ключевики и пр.) К тому же сайт не должен находиться внутри другого сайта, он должен находиться внутри каталога. По крайней мере я так понял задачу.
     
  10. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    Psih, могу поделиться своей либой для работы с бинарными деревьями.
    Мне самому интересны результаты её работы в нагруженном приложении.

    Надо?
     
  11. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    ONK
    Мог бы сразу выложить, я бы заюзал :)
    Ибо в любом случае надо будет перебирать кучу вариантов пока достигну оптимального. Потому что всё это работать будет на кластере, а это ещё более строгие ограничения на запросы чем на обычную базу (работает то всё за исключением FULLTEXT SEARCH на нём, но вот кое-какие вещи работают в в разы а то и десятки раз медленее чем в обычной базе, так что приходиться извращаться так, что мама не горюй) :)
     
  12. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    какие?
    а что за БД, кстати?
     
  13. Psih

    Psih Активный пользователь
    Команда форума Модератор

    С нами с:
    28 дек 2006
    Сообщения:
    2.678
    Симпатии:
    6
    Адрес:
    Рига, Латвия
    dark-demon
    MySQL, NDB Cluster
     
  14. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    Посмотрел код библиотеки.
    В среднем требуется 5 sql запросов при модификации структуры дерева + 3 запроса проверки структуры таблицы от которых можно избавиться (исполняются в конструкторе объекта).

    Работа с базой данных у меня идёт через собственный "драйвер", её придётся поправить.

    Назначение библиотеки - поддержка служебной таблицы связей для некой таблицы, хранящей древовидную структуру.

    код:
    PHP:
    1.  
    2. <?php
    3.  
    4. class Tree_helper_table{
    5.     function Tree_helper_table(&$oDb,$sTable,$sChild_cl,$sParent_cl){
    6.         global $CCSIS_NS;
    7.         $this->db = $oDb;
    8.         if(!$this->db->getOne("SHOW tables LIKE '".$sTable."'")){
    9.             $SQL = "CREATE TABLE ".$sTable." ( ".$sChild_cl." int(12) NOT NULL default '0', ".$sParent_cl." int(12) NOT NULL default '0',  KEY k1 (".$sChild_cl."), KEY k2 (".$sParent_cl.")) TYPE=MyISAM";
    10.             $this->db->query($SQL);
    11.             if(!$this->db->getOne("SHOW tables LIKE '".$sTable."'")){
    12.                 return;
    13.             }
    14.         }
    15.         if(!$this->db->getOne("SHOW COLUMNS FROM `".$sTable."` LIKE '".$sChild_cl."'")){
    16.             return;
    17.         }
    18.         if(!$this->db->getOne("SHOW COLUMNS FROM `".$sTable."` LIKE '".$sParent_cl."'")){
    19.             return;
    20.         }
    21.         $this->table    = $sTable;
    22.         $this->child    = $sChild_cl;
    23.         $this->parent   = $sParent_cl;
    24.     }
    25.  
    26.     function get_parents($iId){
    27.         global $CCSIS_NS;
    28.         $aParent_ids = array();
    29.         if(!empty($this->table)){
    30.             $SQL = "SELECT ".$this->parent." FROM `".$this->table."` WHERE ".$this->child." = '%d'";
    31.             $oResult = $this->db->safe_query($SQL,$iId);
    32.             if($oResult->numRows() > 0){
    33.                 while($aRow = $oResult->fetchRow()){
    34.                     $aParent_ids[] = $aRow[0];
    35.                 }
    36.             }
    37.         }
    38.         return $aParent_ids;
    39.     }
    40.  
    41.     function get_children($iId){
    42.         global $CCSIS_NS;
    43.         $aChaild_ids = array();
    44.         if(!empty($this->table)){
    45.             $SQL = "SELECT ".$this->child." FROM `".$this->table."` WHERE ".$this->parent." = '%d'";
    46.             $oResult = $this->db->safe_query($SQL,$iId);
    47.             if($oResult->numRows() > 0){
    48.                 while($aRow = $oResult->fetchRow()){
    49.                     $aChaild_ids[] = $aRow[0];
    50.                 }
    51.             }
    52.         }
    53.         return $aChaild_ids;
    54.     }
    55.  
    56.     function move_to($iId,$iNew_parent_id = 0,$bAS_a_child = 0){
    57.         global $CCSIS_NS;
    58.         if(!empty($this->table)){
    59.             $aChaild_ids        = $this->get_children($iId);
    60.             $aParent_ids        = $this->get_parents($iId);
    61.             $aNew_Parent_ids    = $this->get_parents($iNew_parent_id);
    62.             $aChaild_ids[]      = $iId;
    63.             if($bAS_a_child && $iNew_parent_id != 0){
    64.                 $aNew_Parent_ids[]  = $iNew_parent_id;
    65.             }
    66.             if(count($aParent_ids)){
    67.                 $this->db->query("DELETE FROM `".$this->table."` WHERE ".$this->child." IN(".implode(',',$aChaild_ids).") AND ".$this->parent." IN(".implode(',',$aParent_ids).")");
    68.             }
    69.             if(count($aNew_Parent_ids)){
    70.                 $bEmpty = 1;
    71.                 $SQL = "INSERT INTO `".$this->table."` (".$this->child.",".$this->parent.")VALUES";
    72.                 foreach($aNew_Parent_ids AS $k => $v){
    73.                     foreach($aChaild_ids AS $iChid){
    74.                         if($bEmpty){
    75.                             $SQL .= "(".$iChid.",".$v.")";
    76.                             $bEmpty = 0;
    77.                         }else{
    78.                             $SQL .= ",(".$iChid.",".$v.")";
    79.                         }
    80.                     }
    81.                 }
    82.                 $this->db->query($SQL);
    83.             }
    84.         }
    85.     }
    86.  
    87.     function remove($iId){
    88.         global $CCSIS_NS;
    89.         if(!empty($this->table)){
    90.             $aChaild_ids = $this->get_children($iId);
    91.             $aParent_ids = $this->get_parents($iId);
    92.             $sWehere_segment = "";
    93.             if(count($aChaild_ids)){
    94.                 $sWehere_segment = $this->child." IN('".implode(',',$aChaild_ids)."')";
    95.             }
    96.             if(count($aParent_ids)){
    97.                 if($sWehere_segment != ''){
    98.                     $sWehere_segment .= " AND ".$this->parent." IN('".implode(',',$aParent_ids)."')";
    99.                 }else{
    100.                     $sWehere_segment = $this->parent." IN('".implode(',',$aParent_ids)."')";
    101.                 }
    102.             }
    103.             if($sWehere_segment != ''){
    104.                 $this->db->query("DELETE FROM `".$this->table."` WHERE ".$sWehere_segment);
    105.             }
    106.             $this->db->safe_query("DELETE FROM `".$this->table."` WHERE ".$this->child." = '%1\$d' OR ".$this->parent." = '%1\$d'",$iId);
    107.         }
    108.     }
    109. }
    110. ?>
    111.  
    пример использования:
    PHP:
    1.  
    2.  
    3. $oHelper_table = new Tree_helper_table($CCSIS_NS->db,$CCSIS_NS->CNF['TAB_PREF'].'_mod_relations','mod_id','parent_mod_id');
    4.  
    5. //Добавление нового потомка к существующему узлу (если $mod_id - новый идентификатор) или перенос существующей ветки на новое место (ветка начинается с $mod_id и переносится к узлу $parent_id, если третий параметр 1, то как потомок $parent_id).
    6.  
    7. $oHelper_table->move_to($mod_id,$parent_id,1);
    8.  
    9. //удаление узла. После удаления все потомки узла становятся прямыми потомками узла, которому принадлежал удаляемый узел.
    10.  
    11. $oHelper_table->remove($mod_id);
    12.  
    13.  
    Предполагается, что работа с таблицей, в которой хранится структура дерева производится параллельно с операциями по модификации таблицы отношений.

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

    Чувствую, что не очень понятно и полно описал, но пока незнаю что дополнить. Может примерами выборки ветвей и поиском пути к корню со связыванием таблиц? Вобщем жду вопросы. -)
     
  15. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    5-8 запросов в среднем - это многовато, особенного для кластера.
     
  16. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    dark-demon, вопрос в том, какие альтернативы. Если требуется такая функциональность, то аналог только NS, а этот алгоритм просто завалит сервер на дереве в десяток тысяч элементов. К том-же 5 запросов это только модификация отношений элементов. Простые выборки - 1 очень эффективный запрос.


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

    PHP:
    1.  
    2.  
    3. class CCSIS_Node{
    4.     var $child      = array();
    5.     var $aChild_ids = array();
    6.     var $id;
    7.     var $property;
    8.  
    9.     function CCSIS_Node($id,$prop){
    10.         $this->id       = $id;
    11.         $this->property = $prop;
    12.     }
    13.  
    14.     function prop_replace($prop){
    15.         $this->property = $prop;
    16.     }
    17.  
    18.     function add_child(/*CCSIS_Node */&$child){
    19.         if(empty($this->aChild_ids[$child->id])){
    20.             $this->child[] = &$child;
    21.             $this->aChild_ids[$child->id] = 1;
    22.         }
    23.     }
    24. }
    25.  
    26. class CCSIS_Tree{
    27.     var $aTree      = array();
    28.     var $aTree_ids  = array();
    29.     var $aNodes     = array();
    30.     var $aNeeded    = array();
    31.     var $aChecked   = array();
    32.  
    33.     function add_node($id,$prop,$parentID){
    34.         if(!empty($this->aNodes[$id])){
    35.             $node = &$this->aNodes[$id];
    36.             $node->prop_replace($prop);
    37.             if(isset($this->aNeeded[$id])){
    38.                 unset($this->aNeeded[$id]);
    39.             }
    40.         }else{
    41.             $node = new CCSIS_Node($id,$prop);
    42.             $this->aNodes[$id] = &$node;
    43.         }
    44.         if(empty($parentID)){
    45.             if(empty($this->aTree_ids[$id])){
    46.                 $this->aTree[]  = &$this->aNodes[$id];
    47.                 $this->aTree_ids[$id]   = 1;
    48.             }
    49.         }
    50.         if(!empty($this->aNodes[$parentID])){
    51.             $parent_node = &$this->aNodes[$parentID];
    52.             $parent_node->add_child($node);
    53.         }else{
    54.             if($parentID){
    55.                 $parent_node = new CCSIS_Node($parentID,null);
    56.                 $this->aNodes[$parentID] = &$parent_node;
    57.                 $parent_node->add_child($node);
    58.                 $this->aNeeded[$parentID] = 1;
    59.             }
    60.         }
    61.     }
    62.  
    63.     function get_needed(){
    64.         $aTmp = array_keys(array_diff_assoc($this->aNeeded,$this->aChecked));
    65.         $this->aChecked = $this->aNeeded;
    66.         return $aTmp;
    67.     }
    68.  
    69.     function complete_tree(){
    70.         foreach($this->aNeeded AS $k => $v){
    71.             foreach($this->aNodes[$k]->child AS $node){
    72.                 $this->aTree[]  = &$this->aNodes[$node->id];
    73.             }
    74.         }
    75.     }
    76. }
    77.  

    Пример использования:

    Выдрано из рабочего кода, строящего дерево с отношением node_id -> parent_id без вспомогательных таблиц связей.
    PHP:
    1.  
    2. $oTree  = new CCSIS_Tree();
    3.     $oResult = $CCSIS_NS->db->safe_query("SELECT a.message_id,a.parent_mid,a.create_date,a.author_user_id,SUBSTRING(a.message_text,1,%2\$d),b.name,a.mod_flag FROM `%1\$s_messages` a LEFT JOIN `%1\$s_user_names` b ON(b.name_id = a.author_name_id) WHERE a.topic_id = '%4\$d' ORDER BY a.parent_mid,a.message_id LIMIT %3\$d",$CONFIG['TAB_PREF'],$CONFIG[108],$CONFIG[107],$CCSIS_NS->tid);
    4.     if($oResult->numRows() > 0){
    5.         while ($row = $oResult->fetchRow()){
    6.             if($row[6] == 1 || CCSIS::check_mod_priv(47,$CCSIS_NS->gid,$CCSIS_NS->fid)){
    7.                 $oTree->add_node($row[0],$row,$row[1]);
    8.             }
    9.         }
    10.         while ($aNeeded = $oTree->get_needed()){
    11.             $oResult = $CCSIS_NS->db->safe_query("SELECT a.message_id,a.parent_mid,a.create_date,a.author_user_id,SUBSTRING(a.message_text,1,%2\$d),b.name,a.mod_flag FROM `%1\$s_messages` a LEFT JOIN `%1\$s_user_names` b ON(b.name_id = a.author_name_id) WHERE a.message_id IN ('".implode("','",$aNeeded)."') ORDER BY a.parent_mid,a.message_id LIMIT %3\$d",$CONFIG['TAB_PREF'],$CONFIG[108],$CONFIG[107]);
    12.             if($oResult->numRows() > 0){
    13.                 while ($row = $oResult->fetchRow()){
    14.                     if($row[6] == 1 || CCSIS::check_mod_priv(47,$CCSIS_NS->gid,$CCSIS_NS->fid)){
    15.                         $oTree->add_node($row[0],$row,$row[1]);
    16.                     }
    17.                 }
    18.             }else{
    19.                 break;
    20.             }
    21.         }
    22.     }
    23.     $oTree->complete_tree();
    24. print_r($oTree->aTree);
    25.  
     
  17. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    а как ты решаешь проблему сортировки? например, нужно выбрать всё поддерево и вывести в виде вложенных ul-ов да так, чтобы каждый li был на своём месте и никуда не скакал при изменении структуры дерева.
     
  18. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    dark-demon, в вышеприведённом примере за сортировку отвечает вот эта строка ORDER BY a.parent_mid,a.message_id

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

    В некоторых случаях, со сложной логикой сортировки, я поддерживаю синтетический указатель положения узла среди узлов его уровня. ( ...ORDER BY parent_id,position... )
     
  19. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    1. более новые записи вовсе не обязательно будут иметь больший идентификатор
    2. более новые записи могут появляться в любой части дерева.

    при выборке непосредственных детей это годится, но при выборке всей ветви у нас опять всё идёт в разнобой.

    а делать сквозной индекс сортировки для всех узлов - это считай повторить половину функционала вложенных множеств...
     
  20. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    dark-demon, давай ты не будеш высказывать в утвердительной форме свои ошибочные предположения? ;)

    1. В моем случае будут и имеют, в других случаях всё зависит от логики конкретного приложения / модуля.
    2. Это не имеет значения, дерево от этого деревом не перестаёт быть и алгоритм его обработки не меняется.

    3. Это годится для выборки любой произвольной части дерева.

    4. Ничего дополнительно делать не надо.

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

    Готов ответить на любые твои вопросы, но пусть это будут вопросы, а не утверждения. :)
     
  21. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    1. это зависит от субд
    2. ну вот, например, нужно сделать постраничный вывод большого древа. предложишь выбрать всё дерево, отсортировать и потом отсечь лишнее?

    издеваешься? там же чёрт ногу сломит...
     
  22. ONK

    ONK Активный пользователь

    С нами с:
    4 фев 2006
    Сообщения:
    281
    Симпатии:
    0
    Адрес:
    СПб
    1. Это зависит от логики приложения, если логика такова, что численно больший идентификатор может принадлежать только более новой по дате создания сущности, но его можно использовать для сортировки, вместо даты создания.
    2. Постраничная навигация по дереву возможна без чтения всего дерева. Достаточно выбирать n-ную пачку элементов дерева, строить из них дерево и выбирать из базы данных недостающую часть элементов. В результате на каждой странице (кроме первой) будет отображаться больше узлов дерева, за счёт узлов, необходимых для достройки дерева до корня.

    Для этого в классе CCSIS_Tree есть метод get_needed(), возвращающий идентификаторы требуемых для достройки дерева узлов.

    Впрочем, можно обойтись и без этого, принудительно построив дерево в такм виде, в котором оно выбралось (не красивое решение). Для этой цели служит метод CCSIS_Tree::complete_tree(). Этот же метод рекомендуется вызывать на всякий случай перед обработкой полученной древовидной структуры, т.к. иногда из-за ошибок или удаления некоторых узлов могут возникать "потерянные" ветви дерева.
     
  23. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    поколупался сегодня с сабжем.
    добавление узлов получилось примерно таким:
    PHP:
    1. <?php
    2.     function addnode($node) {
    3.         $parentid= ifs(@$node[$this->parentfield],0);
    4.         $id= $this->db->_insert($this->listable,$node);
    5.         if (!$parentid):
    6.             $this->db->_insert($this->linktable,array('anc'=>$id,'desc'=>$id));
    7.         else:
    8.             $ancs= $this->db->get0d("
    9.                 select count(anc)
    10.                 from {$this->linktable}
    11.                 where desc={$parentid}
    12.             ");
    13.             if(!$ancs):
    14.                 $this->db->query("
    15.                     insert into {$this->linktable}
    16.                     select anc,{$parentid}
    17.                     from {$this->linktable}
    18.                     where desc=(
    19.                         select {$this->parentfield}
    20.                         from {$this->listable}
    21.                         where id={$parentid}
    22.                     )
    23.                 ");
    24.                 /*$this->db->query("
    25.                     insert into {$this->linktable}
    26.                     select anc,{$parentid}
    27.                     from {$this->listable},{$this->linktable}
    28.                     where id={$parentid} and desc={$this->parentfield}
    29.                 ");*/
    30.                 $this->db->_insert($this->linktable,array('anc'=>$parentid,'desc'=>$parentid));
    31.             endif;
    32.         endif;
    33.         return $id;
    34.     }
    35. ?>
    где $parentid - идетификатор узла к которому приконнекчиваем
    _insert - это хелпер, который инсертит массив в таблицу и возвращает идентификатор
    $this->parentfield - название поля с id родителя
    $this->linktable - название таблицы со связями предок-потомок
    $this->listable - название таблицы содержащей список узлов.
    если родитель не задан (корневой узел), то мы просто создаём линк на него же и успокаиваемся.
    если задан - проверяем есть ли к нему линки от предков.
    если есть, значит всё пучком (уже есть братья) и мы успокаиваемся
    если нет, значит добавляемый узел - первый ребёнок и потому нужно скопировать линки к родителю родителя так, чтобы новые линки шли от тех же предков уже к самому родителю. плюс добавляем линк от родителя к самому себе.
    закомменченный вариант с объединением вместо подзапроса в sqlite работает медленнее (проверял кучей инсертов внутри транзакции, на полях - индексы), но под мускулом, например, может быть всё с точностью до наоборот.
    anc - предок
    desc - потомок
     
  24. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    PHP:
    1. <?php
    2.     function getancs ($parentid) {
    3.         $nodes= unprefix($this->db->get2d("
    4.             select *
    5.             from {$this->linktable}, {$this->listable}
    6.             where desc={$parentid} and anc=id
    7.         "),array($this->listable.'.',$this->linktable.'.'));
    8.         return $nodes;
    9.     }
    10.     /*function getancs ($parentid) {
    11.         $nodes= $this->db->get2d("
    12.             select *
    13.             from {$this->listable}
    14.             where id in (
    15.                 select anc
    16.                 from {$this->linktable}
    17.                 where desc={$parentid}
    18.             )
    19.         ");
    20.         return $nodes;
    21.     }*/
    22. ?>
    - это реализация функции получения всех предков по идентификатору родителя.
    тут быстрее оказалось как ни странно объединение, не смотря на использование unprefix.
    sqlite при объединении добавляет ко всем полям имена таблиц, что очень не удобно в дальнейшей работе. unprefix обрезает префиксы у полей. процедура, скажем так, не быстрая..
     
  25. dark-demon

    dark-demon Активный пользователь

    С нами с:
    16 фев 2007
    Сообщения:
    1.920
    Симпатии:
    1
    Адрес:
    леноград
    PHP:
    1. <?php
    2.     function getdescs ($nodeid) {
    3.         $nodes= $this->db->get2d("
    4.             select *
    5.             from {$this->listable}
    6.             where {$this->parentfield} in (
    7.                 select desc
    8.                 from {$this->linktable}
    9.                 where anc={$nodeid}
    10.             )
    11.         ");
    12.         return $nodes;
    13.     }
    14.     /*function getdescs ($nodeid) {
    15.         $nodes= unprefix($this->db->get2d("
    16.             select *
    17.             from {$this->linktable},{$this->listable}
    18.             where anc={$nodeid} and desc={$this->parentfield}
    19.         "),array($this->listable.'.',$this->linktable.'.'));
    20.         return $nodes;
    21.     }*/
    22. ?>
    - получение всех детей узла. тут оба варианта были наравне (при условии использования индексов - без них объеднение ужасно тормозило, а подзапросу пофиг ^_^), но использование unprefix на больших выборках уводило скрипт в даун.