How to "group" SQL results under separate PHP arrays - php

I can't find the solution to this one, as I don't even know how to properly explain it in one sentence...
I have this kind of SQL table
id name type locale
-----------------------------
1 foo video en
2 bar video en
3 baz pdf fr
And I'd like to query it to end up with this PHP array
$array =
[
'en' =>
[
[
'name' => 'foo',
'type' => 'video',
],
[
'name' => 'bar',
'type' => 'video',
],
],
'fr' => [
'name' => 'baz',
'type' => 'pdf',
]
]
How can I achieve this? Thanks!

Consider the following:
<?php
/*
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,name VARCHAR(12) NOT NULL
,type VARCHAR(12) NOT NULL
,locale CHAR(2) NOT NULL
);
INSERT INTO my_table VALUES
(1,'foo','video','en'),
(2,'bar','video','en'),
(3,'baz','pdf','fr');
SELECT id, name, type, locale FROM my_table ORDER BY id;
+----+------+-------+--------+
| id | name | type | locale |
+----+------+-------+--------+
| 1 | foo | video | en |
| 2 | bar | video | en |
| 3 | baz | pdf | fr |
+----+------+-------+--------+
*/
require('path/to/connection/stateme.nts');
$query = "
SELECT id, name, type, locale FROM my_table ORDER BY id;
";
$result = mysqli_query($db,$query) or die(mysqli_error());
$old_array = array();
while($row = mysqli_fetch_assoc($result)){
$old_array[] = $row;
}
$new_array = array();
foreach($old_array as $row){
$new_array[$row['locale']][] = $row['name'];
}
print_r($new_array);
?>
Outputs:
Array
(
[en] => Array
(
[0] => foo
[1] => bar
)
[fr] => Array
(
[0] => baz
)
)

Related

check if array values contain another array's values and get the relevant key

I have an array of country names like this:
$countryList = array(
"AF" => "Afghanistan",
"AL" => "Albania",
"DZ" => "Algeria",
# [...]
);
Another array, $uniList, contains strings with university affiliations, e.g.:
$uniList = array(
14 => array("id" => 14, "uni" => "University of Kabul, Afghanistan"),
20 => array("id" => 21, "uni" => "University Tirana, Albania, Department of Chemistry"),
23 => array("id" => 23, "uni" => "Oxford, U.K."),
# [...]
);
I want to loop through all $uniList-values and see whether any of the country names in $countryList appear there.
If a country is found, I would like to insert the country code (the array key from $countryList, e.g. AF for Afghanistan) into a MySQL row.
Something like this:
foreach($uniList as $row) {
if($row['uni'] contains any of $countryList) { # perhaps strpos()?
$stmt = $conn->prepare("UPDATE `table` SET column = :countrycode WHERE id = :id");
$stmt->bindValue(":countrycode", $countryList[key]);
$stmt->bindValue(":id", $row['id']);
$stmt->execute();
}
}
How is it possible to achieve it?
My goal, in the end, is to have a MySQL table that should look similar to this:
| id | uni | country |
| -- | --------------------------------------------------- | ----------- |
| 14 | University of Kabul, Afghanistan | Afghanistan |
| 20 | University Tirana, Albania, Department of Chemistry | Albania |
| 23 | Oxford, U.K. | U.K. |
| 45 | University of Vienna | NULL |
Seems like you just need a nested loop and strpos().
Something like this
foreach($countryList as $abr => $country) {
foreach($uniList as $uni) {
if(strpos($uni['uni'], $country) !== false) {
//insert
$stmt = $conn->prepare("UPDATE `table` SET column = :countrycode WHERE id = :id");
$stmt->execute([
'countrycode' => $country,
'id' => $uni['id']
]);
}
}
}
If you want the text to match regardless of case, use stripos()

Merge result after query with JOIN clause in Mysql

I have two table like this:
Product
id | title
-------------------------------
1 | Skirt
2 | Pants
Product_thumbnail
id | product_id | image
-------------------------------
1 | 1 | pant.jpg
2 | 1 | shoes.png
When I want to get product with the thumbnail, I query like this:
SELECT p.*, pt.image FROM product p
LEFT JOIN product_thumbnail pt ON pt.product_id = p.id;
The output I expected
[0]=> [
[id] => 1
[image] =>
[
[0] => pant.jpg
[1] => shoes.jpg
]
]
[1]=> [
[id] => 2
[image] => null
]
But the real output
[0]=> [
id => 1
image => pant.jpg
]
[1]=> [
id => 1
image => shoes.jpg
]
[2]=> [
id => 2
image => shoes.jpg
]
As you see, there is 2 element duplicate, so I need to merge it by hand, is there any way to achieve this more easy? Because my table have many tables relate together more than this, I'm using PHP, I use array_merge_recursive() to merge them but if do like that I get duplicate value in each field, like this:
[0]=> [
[id] =>
[
[0] => 1
[1] => 1
]
[image] =>
[
[0] => pant.jpg
[1] => shoes.jpg
]
]
It's not what I want, can anyone give me an idea?
Consider the following. The code could I'm sure be written more economically, but hopefully you get the idea...
<?php
/*
DROP TABLE IF EXISTS product;
CREATE TABLE product
(id SERIAL PRIMARY KEY
,title VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO product VALUES
(1,'Hat'),
(2,'Shoe');
DROP TABLE IF EXISTS product_thumbnail;
CREATE TABLE product_thumbnail
(id SERIAL PRIMARY KEY
,product_id INT NOT NULL
,image VARCHAR(12) NOT NULL UNIQUE
);
INSERT INTO product_thumbnail VALUES
(1,1,'sombrero.jpg'),
(2,1,'stetson.png');
SELECT p.id
, p.title
, t.image
FROM product p
LEFT
JOIN product_thumbnail t
ON t.product_id = p.id;
+----+-------+--------------+
| id | title | image |
+----+-------+--------------+
| 1 | Hat | sombrero.jpg |
| 1 | Hat | stetson.png |
| 2 | Shoe | NULL |
+----+-------+--------------+
*/
require('path/to/connection/stateme.nts');
$query = "
SELECT p.id
, p.title
, t.image
FROM product p
LEFT
JOIN product_thumbnail t
ON t.product_id = p.id;
";
$result = mysqli_query($db,$query);
$old_array = array();
while($row = mysqli_fetch_assoc($result)){
$old_array[] = $row;
}
$new_array = array();
foreach ($old_array as $row) {
$new_array[$row['id']]['title'] = $row['title'];
$new_array[$row['id']]['image'][] = $row['image'];
}
$new_array = array_values($new_array); // reindex
print_r($new_array);
?>
Outputs
Array
(
[0] => Array
(
[title] => Hat
[image] => Array
(
[0] => sombrero.jpg
[1] => stetson.png
)
)
[1] => Array
(
[title] => Shoe
[image] => Array
(
[0] =>
)
)
)
MySQL doesn't support arrays, but you can aggregate the data into a string:
SELECT p.*, GROUP_CONCAT(pt.image) as images
FROM product p LEFT JOIN
product_thumbnail pt
ON pt.product_id = p.id
GROUP BY p.id;
Note that aggregating only by the id and selecting p.* is allowed and even standard SQL -- assuming that id is a primary key or declared to be unique. This is the one situation when this syntax is permitted.

How to execute that in 1 query?

I have 2 tables customer and orders:
customers:
| custID | Name | Age |
|--------|-------|-----|
| 1 | Peter | 23 |
| 2 | Julie | 34 |
| 3 | Tom | 45 |
orders:
| custID | product | color |
|--------|---------|-------|
| 1 | shirt | blue |
| 1 | jacket | black |
| 2 | jacket | green |
| 3 | hat | grey |
| 3 | shirt | white |
I now want to get all customers and their orders, ordered as a list. So something like that:
Array
(
[0] => Array
(
[ID] => 1
[name] => Peter
[age] => 23
[orders] => Array
(
[0] => Array
(
[product] => shirt
[color] => blue
)
[1] => Array
(
[product] => jacket
[color] => black
)
)
)
[1] => Array
(
[ID] => 2
[name] => Julie
[age] => 34
[orders] => Array
(
[0] => Array
(
[product] => jacket
[color] => green
)
)
)
[2] => Array
(
[ID] => 3
[name] => Tom
[age] => 45
[orders] => Array
(
[0] => Array
(
[product] => hat
[color] => grey
)
[1] => Array
(
[product] => shirt
[color] => white
)
)
)
)
When I do:
SELECT name, age, product, color
FROM `customers`, `orders`
where `customers`.`id` = `orders`.id
group by name
I get:
| name | age | product | color |
|-------|-----|---------|-------|
| Peter | 23 | jacket | green |
| Julie | 34 | shirt | blue |
| Tom | 45 | hat | grey |
Is this even possible with only one query?
You could simply make the query below:
SELECT *
FROM customers
JOIN orders
USING custID
GROUP BY Name
ORDER BY custID ASC;
A couple of steps here...
First, you should run the following query:
SELECT
`customers`.`id`,
`customers`.`name`,
`customers`.`age`,
`orders`.`product`,
`orders`.`color`
FROM `customers`, `orders`
where `customers`.`id` = `orders`.`id`
order by `customers`.`id`
Which should give you de-normalized tabular data that looks something like this:
$array = array(
array("id" => 1, "name" => "Peter", "age" => 23, "product" => "shirt", "color" => "blue"),
array("id" => 1, "name" => "Peter", "age" => 23, "product" => "jacket", "color" => "black"),
array("id" => 2, "name" => "Julie", "age" => 34, "product" => "jacket", "color" => "green"),
array("id" => 3, "name" => "Tom", "age" => 45, "product" => "hat", "color" => "grey"),
array("id" => 3, "name" => "Tom", "age" => 45, "product" => "shirt", "color" => "white")
);
You can then transform the that data into your desired format as follows:
$transformed = array();
$i = 0;
while ($i < count($array)) {
$id = $array[$i]["id"];
$name = $array[$i]["name"];
$age = $array[$i]["age"];
$products = array();
while ($i < count($array) && $id == $array[$i]["id"]) {
array_push($products, array("product" => $array[$i]["product"], "color" => $array[$i]["color"]));
$i++;
}
array_push($transformed, array("id" => $id, "name" => $name, "age" => $age, "products" => $products));
}
http://sandbox.onlinephpfunctions.com/code/6fe856e1f71f699e84215b6f66d25589f71e255e
i think you should user INNER JOIN:
SELECT * FROM customers AS c INNER JOIN orders AS o ON c.custID = o.custID GROUP BY c.custID ORDER BY c.custID ASC;
There is no point in trying to achieve the desired outcome with single query. With the first query get the list of customers. Then perform a second query, which will load all the orders for the customers from the first query. And in a loop match orders to respective customers.
EDIT: something like this
$result = [];
$customers = $pdo->query("SELECT * FROM `customers`")->fetchAll();
foreach ($customers as $c) {
$result[$c['id']] = $c;
}
$orders = $pdo->query("SELECT * FROM `orders` WHERE `custID` IN (".implode(', ', array_keys($result).")");
foreach ($orders as $o) {
if (!isset($result[$o['custId']]['orders']))
$result[$o['custId']]['orders'] = [];
$result[$o['custId']]['orders'][] = $o;
}
Your sql SELECT name, age, product, color FROMcustomers,orderswherecustomers.id=orders.id group by name is fine, just add the customer id and order id also in the sql. Then, in PHP, as you iterate the result set, populate the customer info first (id, name, age) with customer id as the key and name, age as value as an array. Likewise, populate the order for that customer in the key 'orders' with the order id as key and value as an array (product, color).
Once you have the array populated, iterate over that array and keep putting things into a new array since the output you want is an array with sequential keys (0, 1, 2 etc) instead of customer id.
$initialList = array();
while($row = $result->fetch_assoc()) {
if(!array_key_exists($row['customer_id'], $initialList)) {
$initialList[$row['customer_id']] = array(
'name' => $row['name'],
'age' => $row['age'],
'orders' => array()
);
}
$initialList[$row['customer_id']]['orders'][] = array(
'product' => $row['product'],
'color' => $row['color']
);
}
$finalList = array();
foreach($initialList as $customerId => $customer) {
$customer['ID'] = $customerId;
$finalList[] = $customer;
}
//to print and check the array
print_r($finalList);
try this:
SELECT c.custID,c.name, c.age, group_concat(CONCAT_WS(':',o.product,o.color)SEPARATOR ',') as productos
FROM customers AS c
INNER JOIN orders AS o ON c.custID = o.custID
GROUP BY c.custID;
You just have to parse the products.
$array=array();
while($r = $res->fetch_assoc()) {
$id=$row['custID'];
$name=$row['name'];
$age=$row['age'];
$productos=$row['productos'];
$productsSeparats = explode(',',$productos);
$orders=array();
foreach ($productsSeparats as $value) {
$ar=explode(':',$value);
array_push($orders, array("product" => $ar[0], "color" => $ar[1]));
}
array_push($array, array("id" => $id, "name" => $name, "age" => $age, "orders" => $orders));
}

mysql - delete sql row where data not match with data in array

Please can I have some help on this. I'm trying to delete SQL row in a table that are not match the data in an array.
SQL Table:
| id | Name | No
------------------
| 1 | john | 14
| 2 | rui | 156
| 3 | ted | 148
| 4 | alex | 123
| 5 | depay | 56
Array:
Array
(
[0] => Array
(
[name] => john
[no] => 14
)
[1] => Array
(
[name] => ted
[no] => 148
)
[2] => Array
(
[name] => depay
[no] => 56
)
)
This may help you
[akshay#localhost tmp]$ cat test.php
<?php
$array = array (
array (
'name' => 'john',
'no' => '14'
),
array (
'name' => 'ted',
'no' => '148'
),
array (
'name' => 'depay',
'no' => '56'
)
);
$table = "some_table";
$sql = "DELETE FROM ".$table." WHERE (NAME,NO) NOT IN (". implode(",",array_map(function($_){ return sprintf("('%s',%d)",$_['name'],$_['no']);},$array)).")";
print $sql."\n";
?>
Output
[akshay#localhost tmp]$ php test.php
DELETE FROM some_table WHERE (NAME,NO) NOT IN (('john',14),('ted',148),('depay',56))
It would be much easier if you can return "id" aka primary key of the table as array. In that case you can just grab those ids and during delete operation exclude those from the equation.
$selectQ = 'select id from table_name'; // and no>10
$result = mysql_result($selectQ);
$ids = array();
while ($info = mysql_fetch_assoc($result)) {
$ids[] = $info['id'];
}
if($ids) {
$deleteQ = 'delete from table_name where id not in ('.implode(",", array_values($ids)).')';
mysql_query($deleteQ);
}
P.S : During select query make sure to include logic for finding those users. For example, who's "no" is greater than X.Otherwise this query in default will return all ids and eventually nothing will be deleted.

Turn database result into array

I have just made the update/add/delete part for the "Closure table" way of organizing query hierarchical data that are shown on page 70 in this slideshare: http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back
My database looks like this:
Table Categories:
ID Name
1 Top value
2 Sub value1
Table CategoryTree:
child parent level
1 1 0
2 2 0
2 1 1
However, I have a bit of an issue getting the full tree back as an multidimensional array from a single query.
Here's what I would like to get back:
array (
'topvalue' = array (
'Subvalue',
'Subvalue2',
'Subvalue3)
);
);
Update:
Found this link, but I still have a hard time to convert it into an array:
http://karwin.blogspot.com/2010/03/rendering-trees-with-closure-tables.html
Update2 :
I was able to add depths to each of the categories now, if that can be of any help.
Okay, I've written PHP classes that extend the Zend Framework DB table, row, and rowset classes. I've been developing this anyway because I'm speaking at PHP Tek-X in a couple of weeks about hierarchical data models.
I don't want to post all my code to Stack Overflow because they implicitly get licensed under Creative Commons if I do that. update: I committed my code to the Zend Framework extras incubator and my presentation is Models for Hierarchical Data with SQL and PHP at slideshare.
I'll describe the solution in pseudocode. I'm using zoological taxonomy as test data, downloaded from ITIS.gov. The table is longnames:
CREATE TABLE `longnames` (
`tsn` int(11) NOT NULL,
`completename` varchar(164) NOT NULL,
PRIMARY KEY (`tsn`),
KEY `tsn` (`tsn`,`completename`)
)
I've created a closure table for the paths in the hierarchy of taxonomy:
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`)
)
Given the primary key of one node, you can get all its descendants this way:
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;
The join to closure AS p is to include each node's parent id.
The query makes pretty good use of indexes:
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
| 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 | |
+----+-------------+-------+--------+---------------+---------+---------+----------+------+-----------------------------+
And given that I have 490,032 rows in longnames and 4,299,883 rows in closure, it runs in pretty good time:
+--------------------+----------+
| 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 |
+--------------------+----------+
Now I post-process the result of the SQL query above, sorting the rows into subsets according to the hierarchy (pseudocode):
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;
I also define classes for Rows and Rowsets. A Rowset is basically an array of rows. A Row contains an associative array of row data, and also contains a Rowset for its children. The children Rowset for a leaf node is empty.
Rows and Rowsets also define methods called toArrayDeep() which dump their data content recursively as a plain array.
Then I can use the whole system together like this:
// 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());
The output is as follows:
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',
),
),
),
),
)
Re your comment about calculating depth -- or really length of each path.
Assuming you've just inserted a new node to your table that holds the actual nodes (longnames in the example above), the id of the new node is returned by LAST_INSERT_ID() in MySQL or else you can get it somehow.
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;
Proposed Solution
This following example gives a little more than you ask for, but it's a really nice way of doing it and still demonstrates where the information comes from at each stage.
It uses the following table structure:
+--------+------------------+------+-----+---------+----------------+
| 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 | |
+--------+------------------+------+-----+---------+----------------+
Here it is:
<?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;
}
}
Test Case
In my database I have the following rows:
+----+--------+-----------------+
| 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 |
+----+--------+-----------------+
And thus the script outputs the following (lengthy) information:
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
(
)
)
)
Example Usage
I'd suggest creating some kind of recursive function if you're going to create menus from the data:
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);
which would output the following:
First Top
First Child
First Subchild
Second Subchild
Second Child
Second Top
Third Child
Fourth Child
Third Top
Enjoy
I loved the answer from icio, but I prefer to have arrays of arrays, rather than arrays of objects. Here is his script modified to work without making objects:
<?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;
}
}
I think it fair to note that both my answer, and icios do not address your question directly. They both rely on having a parent id link in the main table, and make no use of the closure table. However, recursively querying the database is definitely the way to do, but instead of recursively passing the parent id, you have to pass in the parent id AND the level of the depth (which should increase by one on each recursion) so that the queries at each level can use parent + depth to get the direct parent information from the closure table rather than having it in the main table.
HTH,
-FT
When you want the output as a unordered list you can change the outputCategories method as follows (based on ftrotters arrays in arrays):
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";
}
Sorry but I don't think you can't get a multi-dimensional array out of your (or any) database query.

Categories