Php 如何为树结构(有向无环图)开发数据库模式

Php 如何为树结构(有向无环图)开发数据库模式,php,mysql,database-design,tree,nodes,Php,Mysql,Database Design,Tree,Nodes,我正在使用树下结构,并计划为下面的内容开发一个db模式 到目前为止,我的发展情况如下: 我遇到的问题是,如果我搜索Y,应该生成树的下方 我使用的逻辑是,Y有两个交叉引用X,Z,这两个节点应该在图中,父节点一直到起始父节点 假设我使用PHP使用mysql db表生成此树,如上所示。数据库结构可以改变。我在谷歌上搜索类似的树结构,但找不到任何帮助 注 我不是要你为我写代码。我所要问的是一些应该如何做的准则 我发现下面的内容很有用,但与我的场景不同 如果有人能告诉我应该使用什么php库来生成

我正在使用树下结构,并计划为下面的内容开发一个db模式

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

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

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

假设我使用PHP使用mysql db表生成此树,如上所示。数据库结构可以改变。我在谷歌上搜索类似的树结构,但找不到任何帮助

注 我不是要你为我写代码。我所要问的是一些应该如何做的准则

我发现下面的内容很有用,但与我的场景不同


如果有人能告诉我应该使用什么php库来生成树,以及使用什么样的数据库结构?您的数据库结构没有正常化,因为您在
节点\u父\u id
交叉\u引用
中都有多个id。您应该将这些信息分离到单独的表中

因此,您将拥有
节点
表,再加上第二个表来描述父子关系;此表将具有子节点id和父节点id

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

使用这种结构查找路径变得简单得多,因为您不必额外解析逗号分隔的字符串,这不仅更复杂,而且效率更低

您还可以使用它来确保引用完整性,例如,一个节点没有在数据库中实际不存在的父id


有关更多信息,请研究“数据库规范化”。(如果你愿意的话,也可以用“z”来拼写;-P)

你的数据库结构似乎不是一棵树,它只是一个图形

我建议您抛弃这种结构的关系数据库,看看一些图形数据库,比如,和


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

我发现的唯一一个用于处理图形的PHP库是PEAR“Structures\u graph”包()。当前未对其进行维护,重要功能(如删除节点)未实现,严重错误尚未解决(如无法在Windows 7下安装)。看起来,以目前的形式来看,这个包并没有什么用处

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

变异操作
  • CreateNode($nodeName)返回$nodeID–请注意,创建节点时,节点没有将其连接到图形中其他节点的边
  • DeleteNode($nodeID)–为了强制引用完整性,只有在连接到节点的所有边之前都已被删除,并且在其他情况下引发异常时,才允许这样做
  • UpdateNodeName($nodeID,$newNodeName)–如果允许更改现有节点的名称
  • CreateHorizontalEdge($soucondeid,$destinationNodeID)–这是一条定向边。如果边已存在,或如果添加边创建循环,则引发异常
  • DeleteHorizontalEdge($sourceNodeID,$destinationNodeID)
  • CreateVerticalEdge($firstNodeID,$secondNodeID)–这是一条双向边,可以切换第一个和第二个节点ID,并且对图形的效果相同。如果边已存在或两个节点没有相同的水平父节点,则引发异常
  • 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)–返回真/假
  • GetNodeIDByName($nodeName)–返回$nodeID
  • GetNodeName($nodeID)
  • HorizontalEdgeExists($sourceNodeID,$destinationNodeID)–返回true/false
  • VerticalEdgeExists($firstNodeID,$secondNodeID)–返回true/false,无论参数顺序如何,结果都相同
  • HorizontalConnectionExists($startNodeID,$endNodeID)–返回true/false–在水平箭头之后,是否有从开始节点到结束节点的路径?要测试从$nodeID1到$nodeID2创建新的水平边是否会创建循环,请调用HorizontalConnectionExists($nodeID2,$nodeID1)。
  • GetHorizontalAncestorNodes($nodeID)–返回所有节点的数组,这些节点具有从它们到参数节点的水平路径
  • GetHorizontalDownentNodes($nodeID)–返回所有节点的数组,这些节点具有从参数节点到参数节点的水平路径
  • GetVerticalSiblingNodes($nodeID)–
    A, A2
    A, A2B1
    A, A2B1B2
    A, X
    A, Y
    A, Z
    
    A, A2B1
    A2, A2B1
    B, A2B1
    B1, A2B1
    A2B1, A2B1B2
    A2B1, X
    A2B1, Y
    A2B1, Z
    
    101, 10, B, B1
    102, 10, B1, A2B1
    103, 10, A2B1, A2B1B2
    104, 11, B, B2
    105, 11, B2, A2B1B2
    
    1, A1, B, 1
    2, A2, B, 1
    3, C, D1, 1
    4, C, D2, 1
    
    1, 1, A1, B
    2, 2, A2, B
    3, 3, C, D1
    4, 4, C, D2
    
    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
    7    B2    B    B1,B3 (not B2,B3)
    
    15    'Y'    [13]    [14,16]
    
    $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;
    }