Как разработать схему базы данных для структуры дерева (Направленный ациклический граф)

Я использую ниже древовидную структуру и планирую разработать схему db для ниже.

enter image description here

То, что у меня есть до сих пор, ниже,

enter image description here

Проблема, с которой я столкнулась, - это если я ищу Y, под деревом должно быть сгенерировано.

enter image description here

Логика, которую я использую, у Y есть две перекрестные ссылки X, Z, эти два узла должны быть на диаграмме, а родительские узлы - всем исходным родителям node.

Учитывая, что я использую PHP для генерации этого дерева, используя таблицу mysql db, как показано выше. Структура БД может быть изменена. Я искал в google для подобной древовидной структуры, но не нашел никакой помощи.

Примечание

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

Я нашел ниже полезный, но по-прежнему отличающийся от моего сценария

Каков наиболее эффективный/элегантный способ разобрать плоскую таблицу в дерево?

Как представить структуру, подобную дереву, в db

Если кто-нибудь может рассказать мне, какие библиотеки php следует использовать для генерации дерева и какую подходящую структуру db использовать?

Ответы

Ответ 1

Структура вашей базы данных не нормирована, потому что у вас есть несколько идентификаторов как в node_parent_id, так и в cross_refer. Вы должны разделить эту информацию на отдельные таблицы.

Итак, у вас будет таблица nodes плюс вторая таблица для описания отношений родитель-потомок; эта таблица будет иметь дочерний node id и родительский node id.

Перекрестные ссылки должны быть в третьей таблице, в которой снова есть два столбца node id, но есть два способа сделать это, потому что перекрестные ссылки двунаправлены. Один из способов - сохранить каждую перекрестную ссылку только один раз, что означает, что при запросе таблицы вы должны проверить обе возможности (перекрестная ссылка между X и Y может быть сохранена с X в первом столбце и Y во втором столбце, или наоборот, чтобы найти X, вам нужно будет проверить оба столбца). Другой способ - хранить каждую перекрестную ссылку дважды, один раз для каждого направления. Это упрощает запрос, но оно хранит избыточные данные и может привести к несогласованности, например. если по какой-то причине одна ссылка удалена, а другая - нет.

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

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

Для получения дополнительной информации изучите "нормализацию базы данных". (Или вы можете записать его с помощью "z", если вы так склонны; -P)

Ответ 2

Ваша структура db не кажется деревом, это просто график.

Я предлагаю вам выбросить реляционную базу данных для этой структуры и взглянуть на некоторые из данных диаграммы, например Neo4j, OrientDB и Бесконечный график.

Но если вы вынуждены использовать MySQL, вы можете использовать FlockDB, который может использоваться для перемещения MySQL Node (см. строку как node), но с некоторые ограничение. или вы можете протестировать другой движок MySQL, например OQGRAPH, который предоставляет графический движок для MySQL и MariaDB.

Ответ 3

Позвольте мне повторить этот вопрос, чтобы убедиться, что я правильно понимаю. У вас есть Узлы и два вида отношений - стрелки и вертикальные линии. Затем, учитывая Node N, вы хотите сгенерировать подмножество S (N) узлов со следующими рекурсивными правилами:

  • Правило 0: N находится в S (N).
  • Правило 1: Если Node M связано с N по вертикальному отношению, оно также находится в S (N).
  • Правило 2: Если Node M находится в S (N), то любой Node со стрелкой, указывающей на M, также находится в S (N).

Множество S (N) - минимальное множество узлов, удовлетворяющих этим правилам.

Пример вашего подарка с N, являющимся Y, похоже, подтверждает эти правила.

Однако есть и другой, но (по крайней мере, мне) более естественный набор правил, где правило 1 выше заменяется на

  • Правило 1 ': Если Node M находится в S (N), то любой Node, связанный с M по вертикальному отношению, также находится в S (N).

В дальнейшем я буду принимать Правила 0,1,2, подтвержденные вашим примером, но аналогичный подход может быть использован для правил 0,1 ', 2 или любой модификации.


Также я понимаю, что в вашей таблице в строке 7 есть ошибка, это должно быть:

7    B2    B    B1,B3 (not B2,B3)

Теперь к предлагаемому решению.

Сначала я немного изменил бы структуру вашей таблицы: Поскольку "id" - ваш первичный ключ, правило для внешнего ключа - указать первичный ключ соответствующей записи. То есть, в вашем случае я бы заменил "node_id" на "node_name" или что-то вроде этого, чтобы просто не путать с "id" , и заменить записи "node_parent_id" и "cross_ref" на их "id" s. Например, строка номер 15 будет выглядеть так:

15    'Y'    [13]    [14,16]

В качестве альтернативы, если вы предпочитаете соображения читаемости, вы можете использовать A, B, X, Y и т.д. в качестве первичных ключей, при условии, что они уникальны, конечно, тогда ваша таблица останется прежней, за исключением "id" поле, которое не требуется. Я возьму первый случай, но вы можете адаптировать его ко второму с простой заменой.

Это все, что вам нужно, насколько это происходит в таблице.


Теперь вам нужна рекурсивная функция для генерации подграфа S (N) для каждого заданного Node N.

Я реализую множество S как массив $set из всего 'id его узлов. Затем я определю две функции: одну, чтобы развернуть набор изначально на основе правил 1,2 а другой - для расширения набора, основанного только на правиле 2.

Для простоты я предположим, что ваша таблица импортируется в память как ассоциативный массив $строк строк, так что $rows [$ id] представляет строку с 'id', равную $id, как, опять же, ассоциативный массив. Итак,

$rows[15] = array('id'=>15, 
                  'node_name'=>'Y', 
                  'node_parent_id'=>array(13), 
                  'cross_ref'=>array(14,16)
);

Вот код для функций:

function initial_expand_set ($node_id) {
    global($rows); // to access the array from outside
    $set = array($node_id);    // Rule 0
    $row = $rows[$node_id];    // Get current Node

    $parents = $row['node_parent_id'];  // Get parents of the Node
    $set = $parents ? array_merge ($set, $parents) : $set;   // Join parents to the set

    $vert_brothers = $row['cross_ref'];  // Get vertical relations
    $set = $vert_brothers ? array_merge ($set, $vert_brothers) : $set;

    $set = expand_set($set);  // Recursive function defined below
    return $set;
}

И рекурсивная функция:

// iterate over nodes inside the set, expand each node, and merge all together
function expand_set (array $set) {
    global($rows);
    $expansion = $set;  //  Initially $expansion is the same as $set
    foreach ($set as $node_id) {
        $row = $rows[$node_id];    // Get Node 

        // Get the set of parents of the Node - this is our new set
        $parents = $row['node_parent_id'];  // Get parents of the Node

        // apply the recursion
        $parents_expanded = expand_set($parents);

        // merge with previous expansion
        $expansion = array_merge($expansion, $parents_expanded);
    }
    // after the loop is finished getting rid of redundant entries
    // array_unique generates associative array, for which I only collect values
    $expansion = array_values( array_unique($expansion) );
    return $expansion;
}

Надеюсь, это сработает для вас.:)

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

PS. Для педантов среди читателей обратите внимание, что я использую пространство перед '(' для определения функции и свободного места для вызовов функций, как рекомендовал Дуглас Крокфорд.

Ответ 4

Единственной библиотекой PHP, которую я нашел для манипулирования графиками, является пакет PEAR "Structures_Graph" (http://pear.php.net/manual/en/package.structures.structures-graph.php). В настоящее время он не поддерживается, важные функции (например, удаление node) не реализованы, и серьезные ошибки открыты (например, невозможность установки под Windows 7). Это не похоже, что этот пакет в его текущей форме был бы полезен.

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

Мутирующие операции

  • CreateNode ($ nodeName), возвращает $nodeID - Обратите внимание, что при создании узлов они не имеют границ, соединяющих их с другими узлами в графике.
  • DeleteNode ($ nodeID). Чтобы обеспечить ссылочную целостность, это должно быть разрешено только в том случае, если все ребра, связанные с node, были удалены ранее, а исключение было иначе.
  • UpdateNodeName ($ nodeID, $newNodeName). Если разрешено изменять имя существующего node.
  • CreateHorizontalEdge ($ souceNodeID, $destinationNodeID) - это направленный край. Выбрасывает исключения, если край уже существует, или если добавление краевой раны создает цикл.
  • УдалитьHorizontalEdge ($ sourceNodeID, $destinationNodeID)
  • CreateVerticalEdge ($ firstNodeID, $secondNodeID). Это двунаправленный край, первый и второй идентификаторы node могут переключаться, а эффект на графике будет одинаковым. Выдает исключение, если край уже существует или два узла не имеют одного и того же горизонтального родителя.
  • DeleteVerticalEdge ($ firstNodeID, $secondNodeID). Поскольку ребро не направлено, оно удаляет ребро, даже если аргументы были в обратном порядке для создания.

Примеры: Чтобы построить раздел вашего графика, который имеет имена node, начинающиеся с "B" вручную, код будет выглядеть следующим образом:

$nodeID_B = CreateNode("B");
$nodeID_B1 = CreateNode("B1");
$nodeID_B2 = CreateNode("B2");
$nodeID_B3 = CreateNode("B3");
CreateHorizontalEdge($nodeID_B, $nodeID_B1);
CreateHorizontalEdge($nodeID_B, $nodeID_B2);
CreateHorizontalEdge($nodeID_B, $nodeID_B3);
CreateVerticalEdge($nodeID_B1, $nodeID_B2);
CreateVerticalEdge($nodeID_B2, $nodeID_B3);

Код для ручного удаления node с именем "B3":

// Must remove all edges that connect to node first
DeleteVerticalEdge($nodeID_B2, $nodeID_B3);
DeleteHorizontalEdge($nodeID_B, $nodeID_B3);
// Now no edges connect to the node, so it can be safely deleted
DeleteNode($nodeID_B3);

Не мутирующие операции

  • NodeExists ($ nodeID) - возвращает true/false
  • GetNodeIDByName ($ nodeName) - возвращает $nodeID
  • GetNodeName ($ NodeId)
  • HorizontalEdgeExists ($ sourceNodeID, $destinationNodeID) - возвращает true/false
  • VerticalEdgeExists ($ firstNodeID, $secondNodeID) - возвращает true/false, тот же результат независимо от порядка аргументов.
  • HorizontalConnectionExists ($ startNodeID, $endNodeID) - Возвращает true/false - После горизонтальных стрелок есть путь от начала node до конца node? Чтобы проверить, создает ли новый горизонтальный край от $nodeID1 до $nodeID2, вызовет HorizontalConnectionExists ($ nodeID2, $nodeID1).
  • GetHorizontalAncestorNodes ($ nodeID) - возвращает массив всех узлов, у которых есть горизонтальные пути, ведущие от них к аргументу node.
  • GetHorizontalDescendentNodes ($ nodeID) - возвращает массив всех узлов, у которых есть горизонтальные пути, ведущие от аргумента node к аргументу node.
  • GetVerticalSiblingNodes ($ nodeID) - возвращает массив всех узлов, имеющих вертикальные соединения с аргументом node. Поскольку (в соответствии с вашим ответом на мой уточняющий вопрос) вертикальные отношения не являются транзитивными, функция VerticalEdgeExists (выше) является единственной, необходимой для запроса вертикальных отношений.

Пример: Чтобы получить список узлов, которые должны быть включены в поддерево, которое вы описываете в своем вопросе, объедините результаты GetHorizontalAncestorNodes ($ nodeID) и GetVerticalSiblingNodes ($ nodeID).

Структуры данных

Вам всегда понадобится таблица "Узлы" для хранения nodeID и nodeName. Эта таблица может быть расширена для хранения другой информации, связанной с узлами.

Поскольку вертикальные ребра не являются транзитивными, информацию о них можно просто поместить в таблицу "VerticalEdges" с столбцами vEdgeID, firstNodeID, secondNodeID.

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

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

Обратите внимание, что все структуры данных имеют таблицы "Узлы" и "Вертикальные элементы", которые обсуждались выше.

Минимальная структура данных

Первая структура данных имеет таблицу "HorizontalEdges" с столбцами hEdgeID, sourceNodeID и destinationNodeID. Мутирующие функции просты, и большая часть кода будет кодом проверки ошибок, который генерирует исключения. Не мутирующие функции HorizontalConnectionExists, GetHorizontalAncestorNodes и GetHorizontalDescendentNodes будут сложными и потенциально медленными. Каждый раз, когда они вызывают, они будут рекурсивно проходить таблицу HorizontalEdges и собирать идентификаторы узлов. Эти коллекции возвращаются непосредственно для последующих двух функций, тогда как HorizontalConnectionExists могут завершать и возвращать true, если он находит конец node при поиске потомков начала node. Он вернет false, если поиск завершится, не найдя конца node.

Таблица транзитного закрытия

Вторая структура данных также имеет таблицу HorizontalEdges, идентичную той, которая описана выше, но также имеет вторую таблицу "HorizontalTransitiveClosures" с столбцами hTCID, startNodeID и endNodeID. В этой таблице есть строка для каждой пары start node и end node, так что путь с использованием горизонтальных ребер можно проследить от начала node до конца node.

Пример: Для графа в вопросе строки в этой таблице, содержащие node A (для упрощения записи я буду использовать имена, а не целые идентификаторы node для идентификации узлов и опустить столбец hTCID):

A, A2
A, A2B1
A, A2B1B2
A, X
A, Y
A, Z

Строки, содержащие node A2B1 (первый из них также находится в приведенном выше наборе):

A, A2B1
A2, A2B1
B, A2B1
B1, A2B1
A2B1, A2B1B2
A2B1, X
A2B1, Y
A2B1, Z

В худшем случае эта таблица масштабируется как квадрат числа узлов.

С этой структурой данных HorizontalConnectionExists, GetHorizontalAncestorNodes и GetHorizontalDescendentNodes могут быть реализованы как простые поиски таблицы HorizontalTransitiveClosures. Сложность переносится в функции CreateHorizontalEdge и DeleteHorizontalEdge. DeleteHorizontalEdge особенно сложна и требует справедливого объяснения того, почему работает алгоритм.

Переходное закрытие с путями

Окончательная структура данных, которую я буду обсуждать, хранит информацию о горизонтальном крае в двух таблицах. Во-первых, "HorizontalTransitiveClosurePaths" имеет столбцы hTCPathID, startNodeID, endNodeID, pathLength. Вторая таблица "PathLinks" имеет столбцы PathLinkID, hTCPathID, sourceNodeID, destinationNodeID.

Таблица HorizontalTransitiveClosurePaths похожа на таблицу HorizontalTransitiveClosures в структуре данных, описанной выше, но имеет одну строку для каждого возможного пути, который может выполнять переходное закрытие, а не одну строку на транзитивное закрытие. Например, в графике, показанном в вопросе, таблица HorizontalTransitiveClosures имела бы одну строку (B, A2B1B2) (сокращенную обозначение, как указано выше) для закрытия, которое начиналось с B и заканчивалось A2B1B2. Таблица HorizontalTransitiveClosurePaths имела бы две строки: (10, B, A2B1B2, 3) и (11, B, A2B1B2, 2), так как есть два способа получить от B до A2B1B2. В таблице PathLinks описывается каждый из этих путей с одной строкой на каждый край, составляющий путь. Для двух путей от B до A2B1B2 строки:

101, 10, B, B1
102, 10, B1, A2B1
103, 10, A2B1, A2B1B2
104, 11, B, B2
105, 11, B2, A2B1B2

Нет необходимости в таблице HorizonalEdges, так как ребра можно найти, выбрав строки в таблице HorizontalTransitiveClosurePaths с длиной 1.

Функции запроса реализуются так же, как и в структуре данных Transitive Closure, описанной выше. Поскольку для замыкания может существовать несколько путей, необходимо использовать оператор GROUP BY. Например, SQL-запрос, который возвращает все узлы, являющиеся предками node с идентификатором: nodeid: SELECT startNodeID от HorizontalTransitiveClosurePaths WHERE endNodeID =: nodeid GROUP BY startNodeID

Чтобы реализовать DeleteHorizontalEdge, выполните поиск PathLinks для hTCPathID всех путей, содержащих ребро. Затем удалите эти пути из таблицы HorizontalTransitiveClosurePaths и всех ребер, связанных с путями из таблицы PathLinks.

Чтобы реализовать CreateHorizontalEdge ($ souceNodeID, $destinationNodeID), сначала найдите таблицу HorizontalTransitiveClosurePaths для путей, которые заканчиваются на $souceNodeID - это "набор путей предка". Найдите HorizontalTransitiveClosurePaths для путей, которые начинаются с адреса назначенияNodeID - это "набор путей потомков". Теперь вставьте новые пути из следующих 4 групп (некоторые из которых могут быть пустыми) в таблицу HorizontalTransitiveClosurePaths и вставьте ссылки для этих путей в таблицу PathLinks.

  1. Длина пути 1 от $souceNodeID до $destinationNodeID
  2. Для каждого пути в наборе путей предков новый путь, который расширяет конец пути на одну дополнительную ссылку от $souceNodeID до $destinationNodeID
  3. Для каждого пути в пути пути потомков новый путь, который расширяет начало пути одной дополнительной ссылкой из $souceNodeID в $destinationNodeID
  4. Для каждой комбинации одного пути из набора путей предка и одного пути из набора путей потомка путь, созданный путем разрезания пути предка, к ссылке от $souceNodeID к $destinationNodeID к пути потомков.

Пример: График состоит из 6 узлов: A1, A2, B, C, D1 и D2. Он имеет 4 ребра, (A1, B), (A2, B), (C, D1), (C, D2). Таблица HorizontalTransitiveClosurePaths (с использованием имени node, а не числа):

1, A1, B, 1
2, A2, B, 1
3, C, D1, 1
4, C, D2, 1

Таблица PathLinks:

1, 1, A1, B
2, 2, A2, B
3, 3, C, D1
4, 4, C, D2

Теперь мы добавим ребро из B в C. Набор путей предка (1, 2) и набор путей потомка (3, 4) Пути, добавленные в каждую из 4 групп:

  1. Новый край: HorizontalTransitiveClosurePaths: (5, B, C, 1); PathLinks (5, 5, B, C)
  2. Расширить каждый путь предка по одной ссылке в конце:
    HorizontalTransitiveClosurePaths: 
        6, A1, C, 2
        7, A2, C, 2
    
    PathLinks:
        6, 6, A1, B
        7, 6, B, C
        8, 7, A2, B
        9, 7, B, C
    
  3. Расширение каждого пути потомства по одной ссылке в начале:
    HorizontalTransitiveClosurePaths:
        8, B, D1, 2
        9, B, D2, 2
    
    PathLinks:
        10, 8, B, C
        11, 8, C, D1
        12, 9, B, C
        13, 9, C, D2
    
  4. Соедините все комбинации, содержащие один путь предка и один путь потомка:
    HorizontalTransitiveClosurePaths:
        10, A1, D1, 3
        11, A1, D2, 3
        12, A2, D1, 3
        13, A2, D2, 3
    
    PathLinks:
        14, 10, A1, B
        15, 10, B, C
        16, 10, C, D1
        17, 11, A1, B
        18, 11, B, C  
        19, 11, C, D2
        20, 12, A2, B
        21, 12, B, C
        22, 12, C, D1
        23, 13, A2, B
        24, 13, B, C
        25, 13, C, D2
    

Сообщите мне, если какая-либо часть ответа нуждается в дополнительном разъяснении.

Ответ 5

это может помочь вам

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

Классическая проблема проектирования каталогов заключается в том, как хранить отношения между категориями. Наличие пары id/parentid в таблице является простым решением и достаточно эффективным для каталогов с неглубокой глубиной, но как насчет более крупных структур, таких как дамп каталога DMOZ?

Некоторое чтение материала на деревьях SQL может представлять интерес для вас, если вы планируете построить большую иерархию.

С точки зрения PHP, Кевин ван Зонневельд создал симпатичную небольшую проекте кода. Пример структур дерева в действии

Следующий script будет генерировать древовидную структуру и хранить ее в MySQL. Он довольно длительный, но он должен дать вам хорошую визуализацию того, как использовать древовидные структуры и достаточно легко адаптироваться для ваших собственных нужд.

Эта первая часть сообщения из 2-х частей дает вам script, позволяющую создавать и хранить структуры каталогов. Заметки у подножия страницы должны вести вас через script, если вы столкнетесь с проблемами или заинтересованы в том, чтобы поработать с ним для своих собственных потребностей. Второе сообщение предоставляет основные функции графического интерфейса и администратора для просмотра и изменения древовидной структуры.

Если я правильно понимаю лингво (это может немного тяжело), ​​этот script является "обходом дерева предзаказов", который может привести к новому способу поиска и экстраполяции ваших данных.

This method avoids recursion, you can fetch breadcrumbs for a category thats 14 levels, 20 levels or even 50 levels deep using one query. In this particular script both the parent and child categories are fetched in one query.
All subcategories are methodically encapsulated within their parent nodes, and each node can give you a calculation of how many subcategories there are without any further querying
Generally, for static and/or large tree structures, this structuring of your categories is advantageous for speed and ease of querying.
The cost is that updates to the tree structure can be expensive, i.e. removing or adding (and sometimes editing) a node in the middle of the tree, which requires altering all records to the 'right side' of the tree to avoid having gaps or collisions in the tree structure. In general, you want to avoid having to update a large tree often, or save the updates for one particular moment where the whole tree can be rebuilt with its updates.

Сохраните и запустите следующий script, чтобы сгенерировать некоторые данные примера. Предполагается, что вы установили MySQL и уже создали базу данных под названием "test". Загрузите список категорий 300K (1.7MB) в качестве примерных данных для работы с

Пример структур дерева в действии

Следующий script будет генерировать древовидную структуру и хранить ее в MySQL. Он довольно длительный, но он должен дать вам хорошую визуализацию того, как использовать древовидные структуры и достаточно легко адаптироваться для ваших собственных нужд.

Эта первая часть сообщения из 2-х частей дает вам script, позволяющую создавать и хранить структуры каталогов. Заметки у подножия страницы должны вести вас через script, если вы столкнетесь с проблемами или заинтересованы в том, чтобы поработать с ним для своих собственных потребностей. Второе сообщение предоставляет основные функции графического интерфейса и администратора для просмотра и изменения древовидной структуры.

Если я правильно понимаю лингво (это может немного тяжело), ​​этот script является "обходом дерева предзаказов", который может привести к новому способу поиска и экстраполяции ваших данных.

This method avoids recursion, you can fetch breadcrumbs for a category thats 14 levels, 20 levels or even 50 levels deep using one query. In this particular script both the parent and child categories are fetched in one query.
All subcategories are methodically encapsulated within their parent nodes, and each node can give you a calculation of how many subcategories there are without any further querying
Generally, for static and/or large tree structures, this structuring of your categories is advantageous for speed and ease of querying.
The cost is that updates to the tree structure can be expensive, i.e. removing or adding (and sometimes editing) a node in the middle of the tree, which requires altering all records to the 'right side' of the tree to avoid having gaps or collisions in the tree structure. In general, you want to avoid having to update a large tree often, or save the updates for one particular moment where the whole tree can be rebuilt with its updates.

Сохраните и запустите следующий script, чтобы сгенерировать некоторые данные примера. Предполагается, что вы установили MySQL и уже создали базу данных под названием "test".

<?php
// buildtree.php

mysql_connect('localhost','root','root') or die('Cant connect to MySQL');
mysql_select_db('test') or die('Cant connect to MySQL');

/*
Using the dmozregional.txt file on innvo.com, otherwise you can pass a different file or even an array
Contains around 314,000 categories
Will take about 40 seconds to process, ensure you have enough memory (around 200MB in this case)
and roughly 10x the size of your input file in general cases
*/
$tree = new generate_tree(fopen('dmozregional.txt','r'));
$tree->to_mysql_data();

class generate_tree {
var $cats = array();
var $thiscat = array();
var $depths = array();
var $lftrgt = array();
var $depth = 0;
var $inc = 0;
var $catid = 0;
var $fp1,$fp2;
/*
Run generate_tree once if your dataset is small or memory is not an issue.
*/
public function generate_tree(&$linearcats)
{
$this->depth = 0;
$this->cats = array();
echo "Step 1: Gathering Data: ".date('H:i:s')."\n";
if(is_array($linearcats)) // Run through array
{
foreach($linearcats as $cat)
{
$this->cats[$cat] = array(); // Adding to 2 dimension list of categories with $cat as the key
array_shift($linearcats);
}   
}
elseif(is_resource($linearcats))
{
while(!feof($linearcats))
{
if(!trim($cat = trim(fgets($linearcats))))
continue;
$this->cats[$cat] = array(); // Adding to 2 dimension list of categories with $cat as the key
}
}


if(!is_resource($this->fp1)) // 1st Pass, open files
{
$this->fp1 = fopen('/tmp/tree.txt','w');
$this->fp2 = fopen('/tmp/top.txt','w');
}
echo "Step 2: Tree Structure: ".date('H:i:s')."\n";
$this->cats = $this->explodeTree($this->cats);
echo "Step 3: Pre-Order Tree Traversal: ".date('H:i:s')."\n";
$this->mptt($this->cats);
}

/********************************
Function explodeTree with thanks to Kevin van Zonneveld
info: http://kevin.vanzonneveld.net/techblog/article/convert_anything_to_tree_structures_in_php/
Altered from original version but serves largely the same purpose
Example input data is same/similar as example data in the above URL
********************************/

public function explodeTree($array) {

if(!is_array($array) || !count($array))
return array();
$returnArr = array();
foreach ($array as $key => $val)
{
// Get parent parents and the current leaf
$parents = preg_split("'/'",$key,-1,PREG_SPLIT_NO_EMPTY);
$leaf = array_pop($parents);

// Build parent structure
// Might be slow for really deep and large structures
$parentArr = &$returnArr;
foreach($parents as $parent)
{
if(!isset($parentArr[$parent]))
$parentArr[$parent] = array();
elseif(!is_array($parentArr[$parent]))
$parentArr[$parent] = array();
$parentArr = &$parentArr[$parent];
}
// Add the final part to the structure
if(empty($parentArr[$leaf]))
$parentArr[$leaf] = $val;
elseif(is_array($parentArr[$leaf]))
$parentArr[$leaf][] = $val;
}
return $returnArr;
}
/********************************
Function mptt (modified pre-order tree traversal)
Used to recursively walk through the array and provide lft and rgt values (and category depth)
********************************/
public function mptt(&$cats) {
foreach($cats as $catname => $array)
{
$this->depths[$this->depth] = 0;
$this->thiscat[$this->depth] = $catname; // Marking this depth of categories as this category
$imp = implode('/',$this->thiscat); // Full category path
$this->lftrgt[$imp] = array(++$this->inc,++$this->catid); // Assign lft
if(count($array))
{
++$this->depth; // Deeper
$this->mptt($array); // Reiterate
}
fwrite($this->fp1,$this->lftrgt[$imp][0]."\t".(++$this->inc)."\t".count($this->thiscat)."\t".$this->lftrgt[$imp][1]."\t".strtoupper(md5($imp))."\t".$catname."\n"); // $lft $rgt $depth $id $hash $name
if($this->depth == 0)
fwrite($this->fp2,$this->lftrgt[$imp][1]."\n");
unset($this->lftrgt[$imp]);
}
--$this->depth; // Shallower
array_pop($this->thiscat); // Pop this category depth as we're moving up from here
}

/********************************
Function mysql_data
Creates schema, deletes any old data and creates indexes if not already made.
Deletes temporary file data that MySQL uses to load data into tables
********************************/
public function to_mysql_data() {
echo "Step 4: MySQL Schema: ".date('H:i:s')."\n";
// Creating DB Schema and populating with data. Deleting any old data if present

mysql_query('CREATE TABLE IF NOT EXISTS category_tree (
lft mediumint(8) unsigned NOT NULL,
rgt mediumint(8) unsigned NOT NULL,
depth tinyint(3) unsigned NOT NULL,
id mediumint(8) unsigned NOT NULL,
hash binary(16) NOT NULL,
name varbinary(255) NOT NULL)
ENGINE=InnoDB;')
or die(mysql_error());
mysql_query('TRUNCATE TABLE category_tree')
or die(mysql_error());
mysql_query('LOAD DATA INFILE \'/tmp/tree.txt\' INTO TABLE category_tree FIELDS TERMINATED BY \'\t\' (lft,rgt,depth,id,@hash,name) SET hash = UNHEX(@hash)')
or die(mysql_error());
mysql_query('INSERT INTO category_tree (lft,rgt,depth,id) SELECT 0,MAX(rgt)+1,0,0 FROM category_tree') or die(mysql_error());

mysql_query('CREATE TABLE IF NOT EXISTS category_top (id MEDIUMINT(8) unsigned NOT NULL,PRIMARY KEY (id)) ENGINE=MyISAM')
or die(mysql_error());
mysql_query('TRUNCATE TABLE category_top')
or die(mysql_error());
mysql_query('LOAD DATA INFILE \'/tmp/top.txt\' INTO TABLE category_top FIELDS TERMINATED BY \'\t\'')
or die(mysql_error());

// Delete temporary data
unlink('/tmp/tree.txt');
unlink('/tmp/top.txt');

echo "Step 5: MySQL Indexes: ".date('H:i:s')."\n";
// Adding indexes for SELECT speed. Script will die appropriately here if you have already created the indexes
mysql_query('ALTER TABLE category_tree ADD PRIMARY KEY (lft,rgt,depth);') or die('I1 '.mysql_error());
mysql_query('ALTER TABLE category_tree ADD UNIQUE (id);') or die('I2 '.mysql_error());
mysql_query('ALTER TABLE category_tree ADD UNIQUE (hash);') or die('I3 '.mysql_error());
}
}

?>

Script Обзор

Lines 4-5: Connect to MySQL or terminate script
Line 13: Generate the tree structure. You can call this more than once if you have a large dataset. One method would be to call generate_tree() for every top level category you have so that tree sizes are limited to them.
Line 14: $tree->to_mysql_data() is called at the end to write data to MySQL, after all the following events have occurred
Lines 28-61: Function generate_tree, invoked when the class is initiated.
- Lines 33-49: Loads the data you pass into an array, with values situated in the array keys. You can pass an array to this function or a file resource. For files, ensure there is one record per line.
- Lines 52-55: On first invocation, a couple of temporary files are created for storing data deriving from the tree structure
- Line 58: Calls explodeTree() which converts the array to a multi-dimensional tree array
- Line 60: mptt() to create the lft, rgt and depth values in order to use our tree in MySQL. Data is written to temporary files that are then used by MySQL to populate the tables.

Ответ 6

Если я правильно вас понимаю, у вас есть классическая многосвязная логическая структура логических таблиц. Это легко справляется, создавая три таблицы: один для представления ваших "узлов", другой - для представления ассоциаций "родитель-ребенок" между узлами, а другой для представления отношений между братьями и сестрами. Я не уверен, что вам нужно напрямую представлять родственные отношения, поскольку они могут быть выведены из отношений родитель-ребенок. Однако, поскольку вы не отображали "зеленые" линии отношений для всех братьев и сестер, я предполагаю, что это "особые" отношения. Таблицы/столбцы можно смоделировать следующим образом:

Node

  • node_id (pk)
  • .,.

Node_Map

  • parent_id (fk to node.node_id)
  • child_id (fk to node.node_id)

node_sibling_map

  • node_id (fk to node.node_id)
  • sibling_id (fk to node.node_id)

Чтобы заполнить таблицу, описанную в этой модели, вам нужно будет указать следующее. (цитаты опущены).

  • вставить в значения node (node_id) (A);
  • вставить в значения node (node_id) (B);
  • вставить в значения node (node_id) (C);
  • вставить в значения node (node_id) (A1);
  • вставить в значения node (node_id) (A2);
  • вставить в значения node (node_id) (B1);
  • вставить в значения node (node_id) (B2);
  • вставить в значения node (node_id) (B3);
  • вставить в значения node (node_id) (A2B1);
  • вставить в значения node (node_id) (CB3);
  • вставить в значения node (node_id) (A2B1B2);
  • вставить в значения node (node_id) (X);
  • вставить в значения node (node_id) (Y);
  • вставить в значения node (node_id) (Z);
  • вставить в значения node_map (parent_id, child_id) (A, A1);
  • вставить в значения node_map (parent_id, child_id) (A, A2);
  • вставить в значения node_map (parent_id, child_id) (B, B1);
  • вставить в значения node_map (parent_id, child_id) (B, B2);
  • вставить в значения node_map (parent_id, child_id) (B, B3);
  • вставить в значения node_map (parent_id, child_id) (C, CB3);
  • вставить в значения node_map (parent_id, child_id) (A2, A2B1);
  • вставить в значения node_map (parent_id, child_id) (B1, A2B1);
  • введите значения node_map (parent_id, child_id) (B2, A2B1B2);
  • вставить в значения node_map (parent_id, child_id) (B3, CB3);
  • вставить в значения node_map (parent_id, child_id) (C, CB3);
  • вставить в значения node_map (parent_id, child_id) (A2B1B2, X);
  • вставить в значения node_map (parent_id, child_id) (A2B1B2, Y);
  • введите значения node_map (parent_id, child_id) (A2B1B2, Z);
  • вставить в node_sibling_map (node_id, sibling_id) значения (B1, B2);
  • введите значения node_sibling_map (node_id, sibling_id) (B2, B3);
  • вставить в node_sibling_map (node_id, sibling_id) значения (X, Y);
  • вставить в node_sibling_map (node_id, sibling_id) значения (Y, Z);