将数据库结果转换为数组
Posted
技术标签:
【中文标题】将数据库结果转换为数组【英文标题】:Turn database result into array 【发布时间】:2011-02-17 04:31:02 【问题描述】:我刚刚为“关闭表”组织查询分层数据的方式制作了更新/添加/删除部分,这些数据显示在此幻灯片共享中的第 70 页:http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back
我的数据库如下所示:
表格类别:
ID Name
1 Top value
2 Sub value1
表格分类树:
child parent level
1 1 0
2 2 0
2 1 1
但是,我在从单个查询中将完整的树作为多维数组返回时遇到了一点问题。
以下是我想要回复的内容:
array (
'topvalue' = array (
'Subvalue',
'Subvalue2',
'Subvalue3)
);
);
更新: 找到了这个链接,但我仍然很难将其转换为数组: http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html
更新2: 如果有帮助的话,我现在可以为每个类别添加深度。
【问题讨论】:
+1,用于使用闭包表模式。 谢谢,如果你知道如何用它制作一个又大又漂亮又闪亮的阵列,请告诉我:) SQL 对 php 多维数组一无所知,因此当您从查询返回结果时,您将不得不在 PHP 代码中进行一些后处理。我必须出去,但我会在今天晚些时候添加一个答案。 嗨,比尔,我知道这一点。期待听到你的方法!非常感谢! 【参考方案1】:好的,我已经编写了扩展 Zend Framework DB 表、行和行集类的 PHP 类。无论如何,我一直在开发这个,因为几周后我将在PHP Tek-X 发表关于分层数据模型的演讲。
我不想将我的所有代码发布到 Stack Overflow,因为如果我这样做,它们会隐含地获得知识共享许可。 更新:我将我的代码提交给Zend Framework extras incubator,我的演示文稿是Models for Hierarchical Data with SQL and PHP at slideshare。
我将用伪代码描述解决方案。我使用动物分类学作为测试数据,从ITIS.gov 下载。表是longnames
:
CREATE TABLE `longnames` (
`tsn` int(11) NOT NULL,
`completename` varchar(164) NOT NULL,
PRIMARY KEY (`tsn`),
KEY `tsn` (`tsn`,`completename`)
)
我为分类层次结构中的路径创建了一个闭包表:
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.*, p.a AS `_parent`
FROM longnames AS a
JOIN closure AS c ON (c.a = a.tsn)
JOIN longnames AS d ON (c.d = d.tsn)
LEFT OUTER JOIN closure AS p ON (p.d = d.tsn AND p.l = 1)
WHERE a.tsn = ? AND c.l <= ?
ORDER BY c.l;
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.c.d | 1 | |
| 1 | SIMPLE | p | ref | d | d | 4 | itis.c.d | 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 查询结果进行后处理,根据层次结构将行排序为子集(伪代码):
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 基本上是一个行数组。 Row 包含行数据的关联数组,还包含其子项的 Rowset。叶节点的子行集为空。
Rows 和 Rowsets 还定义了称为 toArrayDeep()
的方法,它们将其数据内容递归地转储为普通数组。
然后我可以像这样一起使用整个系统:
// 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',
),
),
),
),
)
关于计算深度的评论 - 或每条路径的实际长度。
假设您刚刚在包含实际节点的表中插入了一个新节点(上例中的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;
【讨论】:
嗨,比尔!非常感谢您的广泛回复。非常感激!我想知道您是否可以提供一个关于如何在此设计中进行插入的示例,因为在使用您之前的 SQL 反模式幻灯片中的示例时,我遇到了奇怪的外键问题。谢谢! 您能否编辑上面的问题,并根据具体插入内容和您遇到的错误显示您迄今为止所做的尝试?或者你甚至可以打开一个单独的问题。 嗨,比尔,别担心。我写错了查询。一个问题。你介意发布一个关于插入查询如何自动计算深度(l 列)的示例吗? 你好比尔。注意到您编辑了您的帖子。只是想感谢您的帮助!太棒了,我终于得到了完整的 add/list/deletve/move/count Closure 表并运行了:)【参考方案2】:提出的解决方案
以下示例提供的内容超出了您的要求,但这是一种非常好的方法,并且仍然演示了每个阶段的信息来自何处。
它使用如下表结构:
+--------+------------------+------+-----+---------+----------------+
| 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 (i.e. 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;
测试用例
在我的数据库中,我有以下行:
+----+--------+-----------------+
| 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
(
)
)
)
示例用法
如果您要根据数据创建菜单,我建议创建某种递归函数:
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
享受
【讨论】:
【参考方案3】:我喜欢 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 链接,并且不使用闭包表。但是,递归查询数据库绝对是一种方法,但不是递归传递父 id,而是必须传入父 id 和深度级别(每次递归时应该增加一个),以便查询在每个级别都可以使用 parent + depth 从闭包表中获取直接父信息,而不是在主表中。
HTH, -FT
【讨论】:
【参考方案4】:当您希望输出为无序列表时,您可以按如下方式更改 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";
【讨论】:
【参考方案5】:抱歉,我认为您不能从(或任何)数据库查询中获取多维数组。
【讨论】:
以上是关于将数据库结果转换为数组的主要内容,如果未能解决你的问题,请参考以下文章