我正在使用下面的树结构并计划为下面开发一个数据库模式。
到目前为止我的发展如下:
我遇到的问题是如果我搜索Y,则应生成树下面。
我正在使用的逻辑是,Y有两个交叉引用X,Z,这两个节点应该在图中,父节点一直到起始父节点。
鉴于我正在使用PHP使用mysql db表生成此树,如上所示。 DB结构可以改变。我在谷歌搜索了类似的树结构,但我找不到任何帮助。
我不要求你为我编写代码。我要问的是如何做到这一点。
我发现以下内容有帮助,但仍与我的情景不同
What is the most efficient/elegant way to parse a flat table into a tree?
How to represent a tree like structure in a db
如果有人可以请告诉我应该使用哪些php库来生成树以及使用哪种合适的数据库结构?
答案 0 :(得分:5)
您的数据库结构似乎不是树,它只是图形。
我建议您抛弃此结构的关系数据库,并查看一些图表数据库,如Neo4j,OrientDB和Infinite Graph。
但是,如果您被迫使用MySQL,您可以使用FlockDB,它可用于遍历MySQL节点(请参阅行作为节点),但有一些限制。或者您可以测试另一个像OQGRAPH这样的MySQL引擎,它为MySQL和MariaDB提供图形引擎。
答案 1 :(得分:5)
让我重申一下这个问题,以确保我理解正确。你有节点和两种关系 - 箭头和垂直线。然后给定节点N,您希望生成具有以下递归规则的节点的子集S(N):
集合S(N)是满足这些规则的最小节点集。
你给N的例子是Y,似乎证实了这些规则。
然而,还有另一种不同但(至少对我来说)更自然的规则, 其中上述规则1由
代替在续集中,我将假设您的示例确认规则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”字段。我将假设第一种情况,但您可以通过简单的替换来适应第二种情况。
就桌子而言,这就是你所需要的一切。
现在需要一个递归函数来为每个给定的节点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所推荐。
答案 2 :(得分:3)
您的数据库结构未规范化,因为node_parent_id
和cross_refer
都有多个ID。您应该将这些信息分成不同的表格。
所以,你会得到你的nodes
表,再加上第二个表来描述父子关系;此表将具有子节点标识和父节点标识。
交叉引用应该在第三个表中,该表又有两个节点id列,但是有两种方法可以做到这一点,因为交叉引用是双向的。一种方法是只存储每个交叉引用一次,这意味着当您查询表时,您必须检查两种可能性(X和Y之间的交叉引用可以与第一列中的X和第二列中的Y一起存储,或者相反,所以要找到X,你必须检查两列。另一种方法是将每个交叉引用存储两次,每个方向一次。这使得查询更简单,但是它存储冗余数据并且可能导致不一致,例如,如果由于某种原因删除了一个引用,但另一个引用没有。
使用此结构查找路径变得更加简单,因为您不必另外解析以逗号分隔的字符串,这会更复杂且效率更低。
您也可以使用它来确保参照完整性,例如,某个节点没有父数据库中实际不存在的父ID。
有关更多信息,请研究“数据库规范化”。 (或者你可以用“z”拼写它,如果你这么倾向的话;-P)
答案 3 :(得分:2)
我找到的唯一用于操作图形的PHP库是PEAR“Structures_Graph”包(http://pear.php.net/manual/en/package.structures.structures-graph.php)。它目前尚未维护,未实现重要功能(例如删除节点),并且严重错误已打开(例如无法在Windows 7下安装)。它看起来并不像目前的形式那样有用。
操纵图形的基本操作可以分为更改图形(变异)和查询图形(非变异)的基本操作。
实施例: 要构建图形中节点名称以“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);
实施例: 要获取要包含在问题中描述的子树中的节点列表,请结合GetHorizontalAncestorNodes($ nodeID)和GetVerticalSiblingNodes($ nodeID)的结果。
您将始终需要一个“节点”表来保存nodeID和nodeName。可以扩展此表以保存与节点关联的其他信息。
由于垂直边不可传递,因此可以将有关它们的信息放在带有列vEdgeID,firstNodeID,secondNodeID的“VerticalEdges”表中。
如何存储水平边缘信息有几种选择。一方面,数据结构和变异操作可以很简单,但代价是使一些查询操作变得更慢和更复杂。另一方面,数据结构可能稍微复杂一些,但可能更大(在更糟糕的情况下随着节点数量呈指数增长),更复杂的变异操作,但更简单,更快速的查询。确定哪种实施最适合您将取决于图表的大小以及它们与查询次数相比的变化频率。
我将描述三种可能的数据结构来表示图形,从简单到复杂。我将详细介绍上面列出的操作算法,仅针对最后一组数据结构。我认为这组结构最适用于查询与变化比率较高的较小图形。
请注意,所有数据结构都有我上面讨论的“节点”和“VerticalEdges”表。
第一个数据结构有一个“HorizontalEdges”表,其中包含hEdgeID,sourceNodeID和destinationNodeID列。变异函数很简单,大多数代码都是错误检查代码,抛出异常。非变异函数HorizontalConnectionExists, GetHorizontalAncestorNodes和GetHorizontalDescendentNodes将很复杂且可能很慢。每次调用它们时,它们将递归遍历HorizontalEdges表并收集nodeID。这些集合直接返回后两个函数,而HorizontalConnectionExists可以终止并返回true,如果它在搜索起始节点的后代时找到结束节点。如果搜索结束而没有找到结束节点,它将返回false。
第二个数据结构还有一个与上述相同的HorizontalEdges表,但也有第二个表“HorizontalTransitiveClosures”,其中包含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
在更糟糕的情况下,此表会缩放为节点数的平方。
使用此数据结构,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运算符。例如,返回所有具有ID:nodeid的节点的祖先节点的SQL查询是: 从HorizontalTransitiveClosurePaths中选择startNodeID WHERE endNodeID =:nodeid GROUP BY startNodeID
要实现DeleteHorizontalEdge,请在PathLinks中搜索包含边缘的所有路径的hTCPathID。然后从HorizontalTransitiveClosurePaths表中删除这些路径,并从PathLinks表中删除与路径关联的所有边。
要实现CreateHorizontalEdge($ souceNodeID,$ destinationNodeID),首先搜索HorizontalTransitiveClosurePaths表,查找以$ souceNodeID结尾的路径 - 这是“祖先路径集”。在HorizontalTransitiveClosurePaths中搜索从destinationNodeID开始的路径 - 这是“后代路径集”。现在,将以下4个组(其中一些可能为空)中的新路径插入到HorizontalTransitiveClosurePaths表中,并在PathLinks表中插入这些路径的链接。
实施例: 图形由6个节点组成:A1,A2,B,C,D1和D2。它有4个边,(A1,B),(A2,B),(C,D1),(C,D2)。 HorizontalTransitiveClosurePaths表(使用节点名而不是数字)是:
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个组中每个组中添加的路径为:
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
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
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
如果答案的任何部分需要进一步澄清,请告诉我。
答案 4 :(得分:1)
这可能会对你有所帮助
层次结构对于网站和关系数据库来说很常见,一个流行的例子是经典的“web目录”,其中项目存储在目录树中,用户点击进入类别结构以查找他们感兴趣的项目。
目录设计的一个典型问题是如何存储类别之间的关系。在表中使用id / parentid对是一个简单的解决方案,对于深度较浅的目录足够有效,但对于较大的结构,例如DMOZ目录转储呢?
如果您正在考虑构建大型层次结构,那么您可能会对某些阅读material on SQL trees感兴趣。
在PHP方面,Kevin van Zonneveld创建了一个很好的小explodeTree function来表示多维数组中的数据。
为了防止本教程变得庞大,我建议特别阅读第一个链接以了解为什么不在SQL中使用树结构。可以在code project.找到有关嵌套集和数据层次结构的更多睡前阅读 树形结构的实例
以下脚本将生成树结构并将其存储在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.
答案 5 :(得分:1)
如果我理解正确,那么你就拥有了一个经典的多对多自引用逻辑表结构。这可以通过创建三个表来轻松处理:一个用于表示“节点”,另一个用于表示节点之间的父子关联,另一个用于表示兄弟关系。我不相信你需要直接代表兄弟关系,因为这些可以从父子关系中推断出来。但是,由于你没有为所有兄弟姐妹呈现“绿色”关系线,我会假设这是一种“特殊”关系。表/列可以建模如下:
节点
Node_Map
node_sibling_map
为了填充您在此模型中描述的表格,您需要发出以下内容。 (引用省略)。
答案 6 :(得分:0)
这是一个非常复杂的问题,是你正在处理的问题。可能值得查看以下文章:
http://www.codeproject.com/Articles/22824/A-Model-to-Represent-Directed-Acyclic-Graphs-DAG-o http://www.freepatentsonline.com/6633886.html