将数据库结果转换为数组
我刚刚为组织查询分层数据的“Closure table”方式更新/添加/删除部分,这些数据在此幻灯片中的第70页中显示: http ://www.slideshare.net/billkarwin/sql-antipatterns-strike -背部
我的数据库看起来像这样:
表类别:
ID Name 1 Top value 2 Sub value1
表类别树:
child parent level 1 1 0 2 2 0 2 1 1
但是,我有一个问题从单个查询获取完整的树作为multidimensional array。
这是我想回来的:
array ( 'topvalue' = array ( 'Subvalue', 'Subvalue2', 'Subvalue3) ); );
更新:find这个链接,但我仍然很难将其转换为数组: http : //karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html
Update2:现在我可以将深度添加到每个类别,如果这可以有任何帮助的话。
好的,我已经编写了扩展Zend Framework数据表表格,行和行集的PHP类。 无论如何,我一直在开发这个function,因为我在几个星期内在PHP Tek-X上谈论了分层数据模型。
我不想将所有的代码发布到Stack Overflow,因为如果我这样做的话,他们隐式地获得了Creative Commons的许可。 更新:我将代码提交给Zend Framework extras孵化器 ,我的演示文稿是在slideshare中使用SQL和PHP分层数据的模型 。
我将用伪码来描述解决scheme。 我使用动物学分类学作为testing数据,从ITIS.gov下载。 该表是长longnames
:
CREATE TABLE `longnames` ( `tsn` int(11) NOT NULL, `completename` varchar(164) NOT NULL, PRIMARY KEY (`tsn`), KEY `tsn` (`tsn`,`completename`) )
我为分类法层次结构中的path创build了一个闭包表 :
CREATE TABLE `closure` ( `a` int(11) NOT NULL DEFAULT '0', -- ancestor `d` int(11) NOT NULL DEFAULT '0', -- descendant `l` tinyint(3) unsigned NOT NULL, -- levels between a and d PRIMARY KEY (`a`,`d`), CONSTRAINT `closure_ibfk_1` FOREIGN KEY (`a`) REFERENCES `longnames` (`tsn`), CONSTRAINT `closure_ibfk_2` FOREIGN KEY (`d`) REFERENCES `longnames` (`tsn`) )
给定一个节点的主键,可以通过这种方式得到所有的后代:
SELECT d.*, pa AS `_parent` FROM longnames AS a JOIN closure AS c ON (ca = a.tsn) JOIN longnames AS d ON (cd = d.tsn) LEFT OUTER JOIN closure AS p ON (pd = d.tsn AND pl = 1) WHERE a.tsn = ? AND cl <= ? ORDER BY cl;
closure AS p
的连接包括每个节点的父节点id。
查询使得索引非常好用:
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+ | 1 | SIMPLE | a | const | PRIMARY,tsn | PRIMARY | 4 | const | 1 | Using index; Using filesort | | 1 | SIMPLE | c | ref | PRIMARY,d | PRIMARY | 4 | const | 5346 | Using where | | 1 | SIMPLE | d | eq_ref | PRIMARY,tsn | PRIMARY | 4 | itis.cd | 1 | | | 1 | SIMPLE | p | ref | d | d | 4 | itis.cd | 3 | | +----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
考虑到我在长longnames
有490,032行,在closure
有4,299,883行,它运行得相当不错:
+--------------------+----------+ | Status | Duration | +--------------------+----------+ | starting | 0.000257 | | Opening tables | 0.000028 | | System lock | 0.000009 | | Table lock | 0.000013 | | init | 0.000048 | | optimizing | 0.000032 | | statistics | 0.000142 | | preparing | 0.000048 | | executing | 0.000008 | | Sorting result | 0.034102 | | Sending data | 0.001300 | | end | 0.000018 | | query end | 0.000005 | | freeing items | 0.012191 | | logging slow query | 0.000008 | | cleaning up | 0.000007 | +--------------------+----------+
现在我后处理上面的SQL查询的结果,根据层次结构(伪代码)将行sorting为子集:
while ($rowData = fetch()) { $row = new RowObject($rowData); $nodes[$row["tsn"]] = $row; if (array_key_exists($row["_parent"], $nodes)) { $nodes[$row["_parent"]]->addChildRow($row); } else { $top = $row; } } return $top;
我也为行和行集定义类。 行集基本上是一个行数组。 一行包含行数据的关联数组,并且还包含子集的行集。 一个叶节点的子Rowset是空的。
行和行集还定义称为toArrayDeep()
方法, toArrayDeep()
数据内容作为普通数组recursion地转储。
那么我可以像这样一起使用整个系统:
// Get an instance of the taxonomy table data gateway $tax = new Taxonomy(); // query tree starting at Rodentia (id 180130), to a depth of 2 $tree = $tax->fetchTree(180130, 2); // dump out the array var_export($tree->toArrayDeep());
输出如下:
array ( 'tsn' => '180130', 'completename' => 'Rodentia', '_parent' => '179925', '_children' => array ( 0 => array ( 'tsn' => '584569', 'completename' => 'Hystricognatha', '_parent' => '180130', '_children' => array ( 0 => array ( 'tsn' => '552299', 'completename' => 'Hystricognathi', '_parent' => '584569', ), ), ), 1 => array ( 'tsn' => '180134', 'completename' => 'Sciuromorpha', '_parent' => '180130', '_children' => array ( 0 => array ( 'tsn' => '180210', 'completename' => 'Castoridae', '_parent' => '180134', ), 1 => array ( 'tsn' => '180135', 'completename' => 'Sciuridae', '_parent' => '180134', ), 2 => array ( 'tsn' => '180131', 'completename' => 'Aplodontiidae', '_parent' => '180134', ), ), ), 2 => array ( 'tsn' => '573166', 'completename' => 'Anomaluromorpha', '_parent' => '180130', '_children' => array ( 0 => array ( 'tsn' => '573168', 'completename' => 'Anomaluridae', '_parent' => '573166', ), 1 => array ( 'tsn' => '573169', 'completename' => 'Pedetidae', '_parent' => '573166', ), ), ), 3 => array ( 'tsn' => '180273', 'completename' => 'Myomorpha', '_parent' => '180130', '_children' => array ( 0 => array ( 'tsn' => '180399', 'completename' => 'Dipodidae', '_parent' => '180273', ), 1 => array ( 'tsn' => '180360', 'completename' => 'Muridae', '_parent' => '180273', ), 2 => array ( 'tsn' => '180231', 'completename' => 'Heteromyidae', '_parent' => '180273', ), 3 => array ( 'tsn' => '180213', 'completename' => 'Geomyidae', '_parent' => '180273', ), 4 => array ( 'tsn' => '584940', 'completename' => 'Myoxidae', '_parent' => '180273', ), ), ), 4 => array ( 'tsn' => '573167', 'completename' => 'Sciuravida', '_parent' => '180130', '_children' => array ( 0 => array ( 'tsn' => '573170', 'completename' => 'Ctenodactylidae', '_parent' => '573167', ), ), ), ), )
把你的评论关于计算深度 – 或者每条path的长度。
假设你刚刚在你的表中插入一个新的节点来保存实际的节点(上面例子中的长longnames
),新节点的id由MySQL中的LAST_INSERT_ID()
返回,否则你可以以某种方式得到它。
INSERT INTO Closure (a, d, l) SELECT a, LAST_INSERT_ID(), l+1 FROM Closure WHERE d = 5 -- the intended parent of your new node UNION ALL SELECT LAST_INSERT_ID(), LAST_INSERT_ID(), 0;
build议的解决scheme
下面的例子给出了比你所要求的更多的一点,但是这是一个非常好的方法,并且仍然展示了每个阶段信息的来源。
它使用下面的表结构:
+--------+------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------+------------------+------+-----+---------+----------------+ | id | int(10) unsigned | NO | PRI | NULL | auto_increment | | parent | int(10) unsigned | NO | | NULL | | | name | varchar(45) | NO | | NULL | | +--------+------------------+------+-----+---------+----------------+
这里是:
<?php // Connect to the database mysql_connect('localhost', 'root', ''); mysql_select_db('test'); echo '<pre>'; $categories = Category::getTopCategories(); print_r($categories); echo '</pre>'; class Category { /** * The information stored in the database for each category */ public $id; public $parent; public $name; // The child categories public $children; public function __construct() { // Get the child categories when we get this category $this->getChildCategories(); } /** * Get the child categories * @return array */ public function getChildCategories() { if ($this->children) { return $this->children; } return $this->children = self::getCategories("parent = {$this->id}"); } //////////////////////////////////////////////////////////////////////////// /** * The top-level categories (ie no parent) * @return array */ public static function getTopCategories() { return self::getCategories('parent = 0'); } /** * Get categories from the database. * @param string $where Conditions for the returned rows to meet * @return array */ public static function getCategories($where = '') { if ($where) $where = " WHERE $where"; $result = mysql_query("SELECT * FROM categories$where"); $categories = array(); while ($category = mysql_fetch_object($result, 'Category')) $categories[] = $category; mysql_free_result($result); return $categories; } }
testing用例
在我的数据库中,我有以下几行:
+----+--------+-----------------+ | id | parent | name | +----+--------+-----------------+ | 1 | 0 | First Top | | 2 | 0 | Second Top | | 3 | 0 | Third Top | | 4 | 1 | First Child | | 5 | 1 | Second Child | | 6 | 2 | Third Child | | 7 | 2 | Fourth Child | | 8 | 4 | First Subchild | | 9 | 4 | Second Subchild | +----+--------+-----------------+
因此脚本输出以下(冗长的)信息:
Array ( [0] => Category Object ( [id] => 1 [parent] => 0 [name] => First Top [children] => Array ( [0] => Category Object ( [id] => 4 [parent] => 1 [name] => First Child [children] => Array ( [0] => Category Object ( [id] => 8 [parent] => 4 [name] => First Subchild [children] => Array ( ) ) [1] => Category Object ( [id] => 9 [parent] => 4 [name] => Second Subchild [children] => Array ( ) ) ) ) [1] => Category Object ( [id] => 5 [parent] => 1 [name] => Second Child [children] => Array ( ) ) ) ) [1] => Category Object ( [id] => 2 [parent] => 0 [name] => Second Top [children] => Array ( [0] => Category Object ( [id] => 6 [parent] => 2 [name] => Third Child [children] => Array ( ) ) [1] => Category Object ( [id] => 7 [parent] => 2 [name] => Fourth Child [children] => Array ( ) ) ) ) [2] => Category Object ( [id] => 3 [parent] => 0 [name] => Third Top [children] => Array ( ) ) )
用法示例
我build议创build一些recursion函数,如果你要从数据创build菜单:
function outputCategories($categories, $startingLevel = 0) { $indent = str_repeat(" ", $startingLevel); foreach ($categories as $category) { echo "$indent{$category->name}\n"; if (count($category->children) > 0) outputCategories($category->children, $startingLevel+1); } } $categories = Category::getTopCategories(); outputCategories($categories);
这将输出以下内容:
First Top First Child First Subchild Second Subchild Second Child Second Top Third Child Fourth Child Third Top
请享用
我喜欢icio的答案,但我更喜欢有数组的数组,而不是对象的数组。 这里是他的脚本修改为无需制作对象的工作:
<?php require_once('mysql.php'); echo '<pre>'; $categories = Taxonomy::getTopCategories(); print_r($categories); echo '</pre>'; class Taxonomy { public static function getTopCategories() { return self::getCategories('parent_taxonomycode_id = 0'); } public static function getCategories($where = '') { if ($where) $where = " WHERE $where"; $result = mysql_query("SELECT * FROM taxonomycode $where"); $categories = array(); // while ($category = mysql_fetch_object($result, 'Category')) while ($category = mysql_fetch_array($result)){ $my_id = $category['id']; $category['children'] = Taxonomy::getCategories("parent_taxonomycode_id = $my_id"); $categories[] = $category; } mysql_free_result($result); return $categories; } }
我认为这是公平的,我的答案和icios都没有直接解决你的问题。 它们都依赖主表中的父id链接,并且不使用闭包表。 但是recursion查询数据库肯定是要做的,但是不是recursion地传递父ID,而是必须传递父ID和深度级别(每次recursion应该增加一级),以便查询在每个级别都可以使用parent + depth从闭包表中获取直接的父信息,而不是在主表中。
HTH,-FT
当你想把输出作为一个无序列表时,你可以按如下方式改变outputCategories方法(基于数组中的ftrotters数组):
public function outputCategories($categories, $startingLevel = 0) { echo "<ul>\n"; foreach ($categories as $key => $category) { if (count($category['children']) > 0) { echo "<li>{$category['name']}\n"; $this->outputCategories($category['children'], $startingLevel+1); echo "</li>\n"; } else { echo "<li>{$category['name']}</li>\n"; } } echo "</ul>\n"; }
对不起,但我不认为你不能从你的(或任何)数据库查询中得到一个multidimensional array。