繁体   English   中英

如何为树结构开发数据库模式(定向非循环图)

[英]How To Develop A Database Schema For A Tree Structure(Directed acyclic graph)

我正在使用下面的树结构并计划为下面的开发数据库模式。

在此输入图像描述

我到目前为止的发展如下,

在此输入图像描述

我遇到的问题是如果我搜索Y,应该生成树下面。

在此输入图像描述

我正在使用的逻辑是,Y有两个交叉引用X,Z,这两个节点应该在图中,父节点一直到起始父节点。

鉴于我正在使用PHP使用mysql db表生成此树,如上所示。 DB结构可以改变。 我在谷歌搜索了类似的树结构,但我找不到任何帮助。

注意

不是要求你为我编写代码。 所有我要问的是如何做到这一点的指导。

我发现下面有帮助,但仍然与我的情况不同

将平台解析成树的最有效/优雅的方法是什么?

如何在db中表示树状结构

如果有人可以请告诉我应该使用什么PHP库来生成树,以及使用什么是合适的数据库结构?

您的数据库结构似乎不是树,它只是图形。

我建议你抛弃这个结构的关系数据库,看一下像Neo4jOrientDBInfinite Graph这样的图形数据库。

但是如果你被迫使用MySQL,你可以使用FlockDB ,它可以用来遍历MySQL节点(参见行作为节点),但有一些限制。 或者您可以测试另一个像OQGRAPH这样的MySQL引擎,它为MySQL和MariaDB提供图形引擎。

让我重申一下这个问题,以确保我理解正确。 你有节点和两种关系 - 箭头和垂直线。 然后给定节点N,您希望生成具有以下递归规则的节点的子集S(N):

  • 规则0:N在S(N)中。
  • 规则1:如果节点M通过垂直关系与N连接,则它也在S(N)中。
  • 规则2:如果节点M在S(N)中,则具有指向M的箭头的任何节点也在S(N)中。

集合S(N)是满足那些规则的最小节点集合。

你给N的例子是Y,似乎证实了这些规则。

然而,还有另一种不同但(至少对我来说)更自然的规则集,其中上面的规则1被替换为

  • 规则1':如果节点M在S(N)中,则通过垂直关系与M连接的任何节点也在S(N)中。

在续集中,我将假设您的示例确认规则0,1,2,但类似的方法可用于规则0,1',2或任何修改。


另外我知道第7行的表中有一个错误,它应该是:

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

现在提出解决方案。

我首先稍微修改一下你的表结构:由于“id”是你的主键,外键的规则是指向相关条目的主键。 也就是说,在你的情况下,我将“node_id”替换为“node_name”或类似的东西,只是不要与“id”混淆,并用“id”替换“node_parent_id”和“cross_ref”的条目。 例如,行号15看起来像这样:

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

或者,如果您希望出于可读性的原因,您可以使用A,B,X,Y等作为键,前提是它们是唯一的,那么您的表格将保持不变,但“id”字段除外不需要。 我将假设第一种情况,但您可以通过简单的替换将其调整到第二种情况。

就表而言,这就是你所需要的一切。


现在你需要一个递归函数来为每个给定的Node N生成子图S(N)。

我将实现集合S作为其节点的所有'id'的数组$ set。 然后我将定义两个函数 - 一个用于最初基于规则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。 对于读者中的小学生,请注意我在'('用于函数定义之前使用空格,而没有用于函数调用的空间,正如Douglas Crokford所推荐的那样。

您的数据库结构未规范化,因为node_parent_idcross_refer都有多个ID。 您应该将此信息分离到单独的表中。

因此,您将拥有您的nodes表,以及第二个表来描述父子关系; 此表将具有子节点标识和父节点标识。

交叉引用应该在第三个表中,该表又有两个节点id列,但是有两种方法可以做到这一点,因为交叉引用是双向的。 一种方法是只存储每个交叉引用一次,这意味着当您查询表时,您必须检查两种可能性(X和Y之间的交叉引用可以与第一列中的X和第二列中的Y一起存储,或者相反,所以要找到X,你必须检查两列。 另一种方法是将每个交叉引用存储两次,每个方向一次。 这使查询更简单,但它存储冗余数据并可能导致不一致,例如,如果由于某种原因删除了一个引用而另一个引用没有。

使用此结构查找路径变得更加简单,因为您不必另外解析以逗号分隔的字符串,这更复杂且效率更低。

您还可以使用它来确保参照完整性,例如,节点没有父数据库,实际上不存在于数据库中。

有关更多信息,请研究“数据库规范化”。 (或者你可以用“z”拼写它,如果你这么倾向的话;-P)

我发现用于操作图形的唯一PHP库是PEAR“Structures_Graph”包( http://pear.php.net/manual/en/package.structures.structures-graph.php )。 它目前尚未维护,未实现重要功能(例如删除节点),并且严重错误已打开(例如无法在Windows 7下安装)。 它看起来并不像目前的形式那样有用。

操作图形的基本操作可以分为更改图形(变异)和查询图形(非变异)的操作。

变异操作

  • CreateNode($ nodeName),返回$ nodeID - 请注意,创建节点时,它们没有边缘将它们连接到图中的其他节点。
  • DeleteNode($ nodeID) - 为了强制引用完整性,只有在先前已删除连接到节点的所有边缘时才允许这样做,否则抛出异常。
  • UpdateNodeName($ nodeID,$ newNodeName) - 如果允许更改现有节点的名称。
  • CreateHorizo​​ntalEdge($ souceNodeID,$ destinationNodeID) - 这是一个有向边。 如果边缘已存在,或者添加边缘伤口创建循环,则抛出异常。
  • DeleteHorizo​​ntalEdge($ sourceNodeID,$ destinationNodeID)
  • CreateVerticalEdge($ firstNodeID,$ secondNodeID) - 这是双向边缘,可以切换第一个和第二个节点ID,并且对图形的影响将是相同的。 如果edge已存在或者两个节点没有相同的水平父节点,则抛出异常。
  • DeleteVerticalEdge($ firstNodeID,$ secondNodeID) - 由于edge是非定向的,即使参数的创建顺序相反,它也会删除edge。

示例:要构建图形的节点名称手动以“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);

用于手动删除名为“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($节点ID)
  • Horizo​​ntalEdgeExists($ sourceNodeID,$ destinationNodeID) - 返回true / false
  • VerticalEdgeExists($ firstNodeID,$ secondNodeID) - 返回true / false,无论参数顺序如何,都返回相同的结果。
  • Horizo​​ntalConnectionExists($ startNodeID,$ endNodeID) - 返回true / false - 在水平箭头之后,是否有从起始节点到结束节点的路径? 要测试是否从$ nodeID1到$ nodeID2创建新的水平边缘将创建一个循环,请调用Horizo​​ntalConnectionExists($ nodeID2,$ nodeID1)。
  • GetHorizo​​ntalAncestorNodes($ nodeID) - 返回所有节点的数组,这些节点具有从它们到参数节点的水平路径。
  • GetHorizo​​ntalDescendentNodes($ nodeID) - 返回具有从参数节点到参数节点的水平路径的所有节点的数组。
  • GetVerticalSiblingNodes($ nodeID) - 返回与参数节点具有垂直连接的所有节点的数组。 由于(根据您对我的澄清问题的回答),垂直关系不可传递,因此VerticalEdgeExists(上图)函数是查询垂直关系所需的唯一函数。

示例:要获取要包含在问题中描述的子树中的节点列表,请合并GetHorizo​​ntalAncestorNodes($ nodeID)和GetVerticalSiblingNodes($ nodeID)的结果。

数据结构

您将始终需要一个“节点”表来保存nodeID和nodeName。 可以扩展此表以保存与节点关联的其他信息。

由于垂直边不可传递,因此可以将有关它们的信息放在带有列vEdgeID,firstNodeID,secondNodeID的“VerticalEdges”表中。

如何存储水平边缘信息有几种选择。 一方面,数据结构和变异操作可以很简单,但代价是使一些查询操作变得更慢和更复杂。 另一方面,数据结构可能稍微复杂一些,但可能更大(在更糟糕的情况下随着节点数量呈指数增长),更复杂的变异操作,但更简单,更快速的查询。 确定哪种实施最适合您将取决于图表的大小以及它们与查询次数相比的变化频率。

我将描述三种可能的数据结构来表示图形,从简单到复杂。 我将详细介绍上面列出的操作算法,仅针对最后一组数据结构。 我认为这组结构最适合具有较高查询比率的较小图形。

请注意,所有数据结构都具有上面讨论的“节点”和“VerticalEdges”表。

最小的数据结构

第一个数据结构有一个“Horizo​​ntalEdges”表,其中包含列hEdgeID,sourceNodeID和destinationNodeID。 变异函数很简单,大多数代码都是错误检查代码,抛出异常。 非变异函数Horizo​​ntalConnectionExists,GetHorizo​​ntalAncestorNodes和GetHorizo​​ntalDescendentNodes将很复杂且可能很慢。 每次调用它们时,它们将递归遍历Horizo​​ntalEdges表并收集nodeID。 这些集合直接返回后两个函数,而Horizo​​ntalConnectionExists可以终止并返回true,如果它在搜索起始节点的后代时找到结束节点。 如果搜索结束而没有找到结束节点,它将返回false。

传递闭包表

第二个数据结构还有一个与上述相同的Horizo​​ntalEdges表,但也有第二个表“Horizo​​ntalTransitiveClosures”,其中包含hTCID,startNodeID和endNodeID列。 对于每对起始节点和结束节点,该表中有一行,使得可以从起始节点到结束节点跟踪使用水平边缘的路径。

示例:对于问题中的图形,此表中包含节点A的行(为了简化表示法,我将使用名称,而不是整数节点ID来标识节点,并省略hTCID列):

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

包含节点A2B1的行(第一个也在上面的集合中)是:

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

在更糟糕的情况下,该表按比例缩放为节点数的平方。

使用此数据结构,Horizo​​ntalConnectionExists,GetHorizo​​ntalAncestorNodes和GetHorizo​​ntalDescendentNodes可以实现为Horizo​​ntalTransitiveClosures表的简单搜索。 复杂性转移到CreateHorizo​​ntalEdge和DeleteHorizo​​ntalEdge函数。 DeleteHorizo​​ntalEdge特别复杂,需要对算法的工作原理进行一些解释。

具有路径的传递闭包

我将讨论的最终数据结构将水平边缘信息存储在两个表中。 第一个“Horizo​​ntalTransitiveClosurePaths”具有列hTCPathID,startNodeID,endNodeID,pathLength。 第二个表“PathLinks”包含PathLinkID,hTCPathID,sourceNodeID,destinationNodeID列。

Horizo​​ntalTransitiveClosurePaths表类似于上述数据结构中的Horizo​​ntalTransitiveClosures表,但是每个可能的路径都有一行可以完成传递闭包,而不是每个传递闭包一行。 例如,在问题中显示的图表中,Horizo​​ntalTransitiveClosures表将具有一行(B,A2B1B2)(如上所述的简写符号),用于从B开始并结束A2B1B2的闭包。 Horizo​​ntalTransitiveClosurePaths表有两行:(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

不需要Horizo​​nalEdges表,因为可以通过选择Horizo​​ntalTransitiveClosurePaths表中长度为1的行来找到边。

查询函数以与上述传递闭包数据结构相同的方式实现。 由于闭包可能存在多个路径,因此需要使用GROUP BY运算符。 例如,返回具有ID:nodeid的节点的祖先的所有节点的SQL查询是:来自Horizo​​ntalTransitiveClosurePaths的SELECT startNodeID WHERE endNodeID =:nodeid GROUP BY startNodeID

要实现DeleteHorizo​​ntalEdge,请在PathLinks中搜索包含边的所有路径的hTCPathID。 然后从Horizo​​ntalTransitiveClosurePaths表中删除这些路径,并从PathLinks表中删除与路径关联的所有边。

要实现CreateHorizo​​ntalEdge($ souceNodeID,$ destinationNodeID),首先搜索Horizo​​ntalTransitiveClosurePaths表以查找以$ souceNodeID结尾的路径 - 这是“祖先路径集”。 在Horizo​​ntalTransitiveClosurePaths中搜索从destinationNodeID开始的路径 - 这是“后代路径集”。 现在,将以下4个组(其中一些可能为空)中的新路径插入到Horizo​​ntalTransitiveClosurePaths表中,并在PathLinks表中插入这些路径的链接。

  1. 从$ souceNodeID到$ destinationNodeID的长度为1的路径
  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)。 Horizo​​ntalTransitiveClosurePaths表(使用节点名而不是数字)是:

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. 新边缘本身:Horizo​​ntalTransitiveClosurePaths:(5,B,C,1); PathLinks(5,5,B,C)
  2. 通过末尾的一个链接扩展每个祖先路径:
      Horizo​​ntalTransitiveClosurePaths: \n     6,A1,C,2\n     7,A2,C,2\n\n PathLinks:\n     6,6,A1,B\n     7,6,B,C\n     8,7,A2,B\n     9,7,B,C\n
  3. 在开始时通过一个链接扩展每个后代路径:
    \n Horizo​​ntalTransitiveClosurePaths:\n     8,B,D1,2\n     9,B,D2,2\n\n PathLinks:\n     10,8,B,C\n     11,8,C,D1\n     12,9,B,C\n     13,9,C,D2\n
  4. 将包含一个祖先路径和一个后代路径的所有组合拼接在一起:
    \n Horizo​​ntalTransitiveClosurePaths:\n     10,A1,D1,3\n     11,A1,D2,3\n     12,A2,D1,3\n     13,A2,D2,3\n\n PathLinks:\n     14,10,A1,B\n     15,10,B,C\n     16,10,C,D1\n     17,11,A1,B\n     18,11,B,C  \n     19,11,C,D2\n     20,12,A2,B\n     21,12,B,C\n     22,12,C,D1\n     23,13,A2,B\n     24,13,B,C\n     25,13,C,D2\n

如果答案的任何部分需要进一步澄清,请告诉我。

这可能对你有帮助

层次结构对于网站和关系数据库来说是常见的,一个流行的例子是经典的“web目录”,其中项目存储在目录树中,用户点击类别结构以查找它们感兴趣的项目。

目录设计的一个典型问题是如何存储类别之间的关系。 在表中使用id / parentid对是一个简单的解决方案,对于深度较浅的目录足够有效,但对于较大的结构,例如DMOZ目录转储呢?

如果您正在考虑构建大型层次结构,那么SQL树上的一些阅读材料可能会引起您的兴趣。

在PHP方面,Kevin van Zonneveld创建了一个很好的小型explodeTree函数来表示多维数组中的数据。

为了防止本教程变得庞大,我建议特别阅读第一个链接以了解为什么不在SQL中使用树结构。 可以在代码项目中找到有关嵌套集和数据层次结构的更多睡前读数 树形结构的实例

以下脚本将生成树结构并将其存储在MySQL中。 这是相当冗长的,但它应该为您提供如何利用树结构的良好可视化,并且很容易适应您自己的需要。

2部分帖子的第一部分为您提供了脚本,使您可以创建和存储目录结构。 页面底部的注释应该指导您在遇到问题时通过脚本,或者有兴趣根据自己的要求修改它。 第二篇文章提供了一个基本的GUI和管理功能,用于查看和更改树结构。

如果我正确地理解了术语(它可能会有点沉重),这个脚本就是一个'预订树遍历',它可以带来一种新的方式来查看和推断你的数据。

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.

保存并运行以下脚本以生成一些示例数据。 假设您已经安装了MySQL并且已经创建了一个名为“test”的数据库。 下载此300K类别列表(1.7MB)作为样本数据

树形结构的实例

以下脚本将生成树结构并将其存储在MySQL中。 这是相当冗长的,但它应该为您提供如何利用树结构的良好可视化,并且很容易适应您自己的需要。

2部分帖子的第一部分为您提供了脚本,使您可以创建和存储目录结构。 页面底部的注释应该指导您在遇到问题时通过脚本,或者有兴趣根据自己的要求修改它。 第二篇文章提供了一个基本的GUI和管理功能,用于查看和更改树结构。

如果我正确地理解了术语(它可能会有点沉重),这个脚本就是一个'预订树遍历',它可以带来一种新的方式来查看和推断你的数据。

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.

保存并运行以下脚本以生成一些示例数据。 假设您已经安装了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());
}
}

?>

脚本概述

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.

如果我理解正确,那么你就拥有了一个经典的多对多自引用逻辑表结构。 这可以通过创建三个表来轻松处理:一个用于表示“节点”,另一个用于表示节点之间的父子关联,另一个用于表示兄弟关系。 我不相信你需要直接代表兄弟关系,因为这些可以从父子关系中推断出来。 但是,由于你没有为所有兄弟姐妹呈现“绿色”关系线,我会假设这是一种“特殊”关系。 表/列可以建模如下:

节点

  • node_id(pk)

Node_Map

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

node_sibling_map

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

为了填充您在此模型中描述的表,您需要发出以下内容。 (引用省略)。

  • 插入节点(node_id)值(A);
  • 插入节点(node_id)值(B);
  • 插入节点(node_id)值(C);
  • 插入节点(node_id)值(A1);
  • 插入节点(node_id)值(A2);
  • 插入节点(node_id)值(B1);
  • 插入节点(node_id)值(B2);
  • 插入节点(node_id)值(B3);
  • 插入节点(node_id)值(A2B1);
  • 插入节点(node_id)值(CB3);
  • 插入节点(node_id)值(A2B1B2);
  • 插入节点(node_id)值(X);
  • 插入节点(node_id)值(Y);
  • 插入节点(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);

这是一个非常复杂的问题,你正在处理的问题。 可能值得查看以下文章:

http://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG-o http://www.freepatentsonline.com/6633886.html

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM