简体   繁体   English

具有两个表的邻接列表模型

[英]Adjacency List Model with two tables

So I think my issue boils down to two questions: 所以我认为我的问题归结为两个问题:

  1. How do I build a traversable tree structure in PHP when the tree is stored in MySQL (between two tables) using the Adjacency List Model approach while keeping performance in mind? 当使用Adjacency List Model方法将树存储在MySQL(两个表之间)时,如何在保持性能的同时,如何在PHP中构建可遍历的树结构?

  2. What's a maintainable approach to displaying the tree in the needed formats without duplicating traversal code and littering the logic with if/else and switch statements? 在不重复遍历代码并使用if / else和switch语句乱丢逻辑的情况下,以所需格式显示树的可维护方法是什么?

Below are more details: 以下是更多细节:

I'm using the Zend Framework. 我正在使用Zend Framework。

I'm working with a questionnaire. 我正在调查问卷。 It's stored in a MySQL database between two separate tables: questions and question_groups. 它存储在两个独立的表之间的MySQL数据库中:questions和question_groups。 Each table extends the appropriate Zend_Db_Table_* classes. 每个表都扩展了适当的Zend_Db_Table_ *类。 The hierarchy is represented using the Adjacency List Model approach. 使用邻接列表模型方法表示层次结构。

I realize the problems I'm running into are likely due to the fact that I'm stuffing a tree structure into an RDBMS so I'm open to alternatives. 我意识到我遇到的问题很可能是因为我将树结构填充到RDBMS中,因此我对替代方案持开放态度。 However, I'm also storing questionnaire respondents and their responses so alternative approaches would need to support that. 但是,我也在存储调查问卷受访者及其回答,因此需要采用其他方法来支持。

The questionnaire needs to be displayed in various HTML formats: 问卷需要以各种HTML格式显示:

  1. As a form for entering responses (using Zend_Form) 作为输入回复的表单(使用Zend_Form)
  2. As an ordered list (nested) with questions (and some groups) as links to view responses by question or by group. 作为有序列表(嵌套),问题(和一些组)作为按问题或按组查看响应的链接。
  3. As an ordered list (nested) with responses appended to each question. 作为有序列表(嵌套),并在每个问题后附加响应。

Questions are leaf nodes and question_groups can contain other question_groups and/or questions. 问题是叶节点,question_groups可以包含其他question_groups和/或问题。 Combined, there are a little over 100 rows to process and display. 合并后,处理和显示的行数将超过100行。

Currently, I have a view helper that does all the processing using recursion to retrieve a question_group's children (a query which performs a UNION between the two tables: QuestionGroup::getChildren($id)). 目前,我有一个视图助手,它使用递归执行所有处理以检索question_group的子节点(在两个表之间执行UNION的查询:QuestionGroup :: getChildren($ id))。 Plus when displaying questionnaire with the question response an additional two queries are needed to retrieve the respondent and their response to each question. 此外,在显示问卷回答的问卷时,需要另外两个查询来检索受访者及其对每个问题的回答。

While the page load time isn't very long this approach feels wrong. 虽然页面加载时间不是很长,但这种方法感觉不对。 Recursion plus multiple database queries for almost every node does not make me feel very warm and fuzzy inside. 递归加上几乎每个节点的多个数据库查询都不会让我觉得内心非常温暖和模糊。

I've tried recursion-less and recursive methods on the full tree array returned from the UNION to build a hierarchical array to traverse and display. 我在UNION返回的完整树数组上尝试了无递归和递归方法,以构建一个遍历和显示的分层数组。 However, that seems to break down since there are duplicated node ids due to the fact that groups and questions are stored in separate tables. 但是,由于组和问题存储在单独的表中,因此存在重复的节点ID,这似乎会中断。 Maybe I'm missing something there... 也许我错过了那里的东西......

Currently, the logic to display the tree in the formats listed above is quite a mess. 目前,以上面列出的格式显示树的逻辑非常混乱。 I'd prefer not to duplicate the traversal logic all over the place. 我不想在整个地方复制遍历逻辑。 However, conditionals all over the place don't produce the most easily maintainable code either. 但是,整个地方的条件也不会产生最容易维护的代码。 I've read up on Visitors, Decorators and some of the PHP SPL iterators but I'm still feeling unclear as to how that would all work together with the classes that are extending Zend_Db_Table, Zend_Db_Table_Rowset and Zend_Db_Table_Row. 我已经阅读了访问者,装饰器和一些PHP SPL迭代器,但我仍然不清楚如何与扩展Zend_Db_Table,Zend_Db_Table_Rowset和Zend_Db_Table_Row的类一起工作。 Especially since I haven't solved the previous problem of building the hierarchy from the database. 特别是因为我还没有解决以前从数据库构建层次结构的问题。 It would be nice to add new display formats (or modify existing ones) somewhat easily. 在某种程度上轻松添加新的显示格式(或修改现有格式)会很不错。

  • The Adjacency List traditionally gives you a parent_id column in each row that links a row to its immediate parent. 邻接列表传统上为每行提供一个parent_id列,用于将行链接到其直接父级。 The parent_id is NULL if the row is the root of a tree. 如果行是树的根,则parent_id为NULL。 But this leads you to run many SQL queries, which is expensive. 但这导致您运行许多SQL查询,这是昂贵的。

  • Add another column root_id so each row knows what tree it belongs to. 添加另一列root_id以便每行知道它属于哪个树。 That way you can fetch all nodes of a given tree with a single SQL query. 这样,您可以使用单个SQL查询获取给定树的所有节点。 Add a method to your Table class to fetch a Rowset by the tree's root id. Table类添加一个方法,以通过树的根id获取Rowset

     class QuestionGroups extends Zend_Db_Table_Abstract { protected $_rowClass = 'QuestionGroup'; protected $_rowsetClass = 'QuestionGroupSet'; protected function fetchTreeByRootId($root_id) { $rowset = $this->fetchAll($this ->select() ->where('root_id = ?', $root_id) ->order('id'); ); $rowset->initTree(); return $rowset; } } 
  • Write a custom class extending Zend_Db_Table_Row and write functions to retrieve the given row's parent and also a Rowset of its children. 编写一个扩展Zend_Db_Table_Row的自定义类,并编写函数来检索给定行的父级以及其子级的Rowset The Row class should contain protected data objects to reference the parent and the array of children. Row类应包含受保护的数据对象,以引用父级和子级数组。 A Row object can also have a getLevel() function and a getAncestorsRowset() function for breadcrumbs. Row对象还可以具有getLevel()函数和面包屑的getAncestorsRowset()函数。

     class QuestionGroup extends Zend_Db_Table_Row_Abstract { protected $_children = array(); protected $_parent = null; protected $_level = null; public function setParent(Zend_Db_Table_Row_Abstract $parent) { $this->_parent = $parent; } public function getParent() { return $this->_parent; } public function addChild(Zend_Db_Table_Row_Abstract $child) { $this->_children[] = $child; } public function getChildren() { return $this->_children; } public function getLevel() {} public function getAncestors() {} } 
  • Write a custom class extending Zend_Db_Table_Rowset that has a function to iterate over the rows in the rowset, setting parent and children references so that you can subsequently traverse them as a tree. 编写一个扩展Zend_Db_Table_Rowset的自定义类,它具有迭代行集中行的功能,设置父引用和子引用,以便随后可以将它们作为树进行遍历。 Also the Rowset should have a getRootRow() function. Rowset应该有一个getRootRow()函数。

     class QuestionGroupSet extends Zend_Db_Table_Rowset_Abstract { protected $_root = null; protected function getRootRow() { return $this->_root; } public function initTree() { $rows = array(); $children = array(); foreach ($this as $row) { $rows[$row->id] = $row; if ($row->parent_id) { $row->setParent($rows[$row->parent_id]); $rows[$row->parent_id]->addChild($row); } else { $this->_root = $row; } } } } 

Now you can call getRootRow() on a rowset, and it returns the root node. 现在,您可以getRootRow()集上调用getRootRow() ,并返回根节点。 Once you have the root node, you can call getChildren() and loop over them. 获得根节点后,可以调用getChildren()并对其进行循环。 Then you can call getChildren() also on any of these intermediate children, and recursively output a tree in any format you want. 然后,您也可以在任何这些中间子项上调用getChildren() ,并以您想要的任何格式递归输出树。

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

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