Turn database result into array - php

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.

Related

Why is my recursive function not working when used within a loop?

I have a database that stores how users were referred into the system like so:
| user_id | referrer_id |
| 3 | 2 |
| 4 | 3 |
| 5 | 3 |
| 6 | 4 |
| 7 | 4 |
| 8 | 5 |
| 9 | 5 |
| 10 | 6 |
| 11 | 10 |
| 12 | 10 |
and I would like to display a specific user's network, i.e., the user, the other users which s/he referred directly, and also other users referred by the direct referrals, in an array.
So far, I've been able to create a recursive function
// The actual get_downlines() function
function get_downlines($upline_id) {
// Init the UsersMatrix model
$um_model = model('UsersMatrix');
$um_model->select('user_id')->where('upline_id', $upline_id);
$downlines_check = $um_model->findAll(5);
$downlines = [];
if ( ! empty($downlines_check) ) {
foreach($downlines_check as $check) {
$downlines[] = $check->user_id;
}
}
return $downlines;
}
// User to check
$user = 3;
// The initial network array
$network_array['user_network'][0] = [
'id' => $user,
'text' => "user-$user"
];
// Get the user's network
function get_network($referrer, $network_array){
// This function basically runs a SELECT user_id FROM users WHERE referrer_id = $referrer_id
// and returns the user_ids in an array: get_downlines()
$downlines = get_downlines($referrer);
if (count($downlines) > 0){
// Loop through the downlines
foreach($downlines as $downline){
// Update the array
array_push($network_array['user_network'], [
'id' => $downline,
'text' => "user-$downline",
]);
// Check for the downlines of this user and inner levels
get_network($downline, $network_array);
}
}
// Return the network array
return $network_array;
}
With this, I expect that when I run the get_network(3, $network_array);, I should get an array like so:
Array
(
[user_network] => Array
(
[0] => Array
(
[id] => 3
[text] => user-3
),
[1] => Array
(
[id] => 4
[text] => user-4
),
[2] => Array
(
[id] => 5
[text] => user-5
),
[3] => Array
(
[id] => 6
[text] => user-6
),
[4] => Array
(
[id] => 7
[text] => user-7
),
[5] => Array
(
[id] => 8
[text] => user-8
),
[6] => Array
(
[id] => 9
[text] => user-9
),
[7] => Array
(
[id] => 10
[text] => user-10
)
)
)
instead, the page dies with a maximum execution time error. But if I use a user ID without an indirect referrer, it works well. E.g user id 10.
Where am I going wrong?

left join tables for many relation data

I have two table. first for insert sliders options and second for insert relation slide for sliders.
slider table:
| id | title | status | slider_options |
---------------------------------------------
| 1 | slider1 | 1 | [{"speed":"5000"}]
slides table
| id | slider_id | image | content | order
------------------------------------------------------------
| 1 | 1 | upload/images/xxx.jpg | NULL | 1
| 2 | 1 | upload/images/yyy.jpg | NULL | 2
| 3 | 1 | upload/images/zzz.jpg | NULL | 3
now in update page I need to show data for sliders and relation slide.
public function getSliderData(int $id)
{
return $this->db->table('sliders')
->select('sliders.*, slides.id as slide_id, slides.image as image, slides.order as order')
->join('slides', 'slides.slider_id = sliders.id', 'left')
->where('sliders.id', $id)
->get()
->getResultObject();
}
output is:
Array
(
[0] => stdClass Object
(
[id] => 1
[title] => slider1
[slider_options] => [{"speed":5000}]
[status] => 1
[slide_id] => 1
[image] => uploads/images/xxx.jpg
[order] => 1
)
[1] => stdClass Object
(
[id] => 1
[title] => slider1
[slider_options] => [{"speed":5000}]
[status] => 1
[slide_id] => 1
[image] => uploads/images/yyy.jpg
[order] => 2
)
[2] => stdClass Object
(
[id] => 1
[title] => slider1
[slider_options] => [{"speed":5000}]
[status] => 1
[slide_id] => 1
[image] => uploads/images/zzz.jpg
[order] => 3
)
)
in output result i see three array Object and sliders data(id, title, slider_options, status) for each slide. i need to show sliders data and show relation slides data(images data) into sliders(for update page). how do can i fix this?
I think this is the best solution to handle such situation.. Try this.
public function getSliderData(int $id)
{
$data = $this->db->table('sliders')->where('id',$id)->get()->result();
foreach ($data as $key => $value) {
$value->slides = $this->db->table('slides')->where('slider_id',$value->id)->get()->result();
}
return $data;
}

Select where HSTORE value is $value Laravel

Using the query builder to try and select rows where a value in the HSTORE key $type is present.
My Where Statement:
->where("meta_data_fields->'$type'", 'like', '%'.$query.'%')
Not sure what I'm doing wrong. I get this error:
Undefined column: 7 ERROR: column "meta_data_fields->'Entity::hstore'" does not exist
Any ideas?
to get all records in table that have HSTORE
Model::where("meta_data_fields", 'like', '%'.$query.'%')->get();
This should do normally do the trick. In your case, data in meta_data_fields is a json string. if the search is performed on all rows in the table, will select all. So here is What I did,
I created a queryScope in the model
1 - fetch all data
2 - extract meta_data_field and decode it
3 - loop through meta_data_field
4 - select only that match the criteria and build the array
here is a table
+----+-------------+------------------------------------+-----------+
| id | name | desc | vendor_id |
+----+-------------+------------------------------------+-----------+
| 1 | apples | {"type":"fruit","origin":"mexico"} | 1 |
| 2 | oranges | {"type":"fruit","origin":"peru"} | 1 |
| 3 | Peaches | {"type":"fruit","origin":"mexico"} | 2 |
| 4 | Cherries | {"type":"fruit","origin":"us"} | 1 |
| 5 | banans | {"type":"fruit","origin":"brazil"} | NULL |
| 6 | Water Melon | {"type":"fruit","origin":"mexico"} | 1 |
+----+-------------+------------------------------------+-----------+
public function scopeItems($name ="mexico"){
$items = array();
$data = Self::get();
$meta_fields = json_decode($data -> pluck('desc') ,true);
foreach ($meta_fields as $key => $value) {
if (isset($value['origin']) && $value['origin'] == $name ){
$items[$key] = $data[$key] -> toArray();
}
}
return $items;
}
// output
Array
(
[0] => Array
(
[id] => 1
[name] => apples
[desc] => Array
(
[type] => fruit
[origin] => mexico
)
[vendor_id] => 1
)
[2] => Array
(
[id] => 3
[name] => Peaches
[desc] => Array
(
[type] => fruit
[origin] => mexico
)
[vendor_id] => 2
)
[5] => Array
(
[id] => 6
[name] => Water Melon
[desc] => Array
(
[type] => fruit
[origin] => mexico
)
[vendor_id] => 1
)
)
Hope this works for you this time.
So here's what worked in my situation with Laravel.
$result = \DB::select(\DB::raw("SELECT hstore_fields, id from table where lower(hstore_fields->'$type') like lower('%$query%')"));

php (PDO) simple way to turn rows in lookup table into simple array

given a very simple table structure thus:
mysql> describe songpart;
+----------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+---------+------+-----+---------+----------------+
| id | int(11) | NO | MUL | NULL | auto_increment |
| partName | text | NO | | NULL | |
+----------+---------+------+-----+---------+----------------+
which results in an array like this in php (when queried)
Array ( [0] => Array ( [id] => 1 [0] => 1 [partName] => Lead Guitar [1] => Lead Guitar )
[1] => Array ( [id] => 2 [0] => 2 [partName] => Bass Guitar [1] => Bass Guitar )
[2] => Array ( [id] => 3 [0] => 3 [partName] => Drums [1] => Drums )
[3] => Array ( [id] => 4 [0] => 4 [partName] => Keyboard [1] => Keyboard ) )
Am I missing some simple trick to turn this into a simple array with id as the key like so:
Array ( [1] => Lead Guitar
[2] => Bass Guitar
[3] => Drums
[4] => Keyboard )
or is it possible to get PDO to deliver an array like this?
TiA
You can simply use the PDO::FETCH_KEY_PAIR is you have only 2 columns in your result.
You can also use the PDO::FETCH_GROUP and PDO::FETCH_ASSOC together if you have more. Example :
$result = $db->query('select * from channels')->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC);
This will yield an array indexed with the first column containing at each index an array of the result for this key. You can fix this by using array_map('reset', $result) to get your goal.
Example :
$result = array_map('reset', $db->query('select * from channels')->fetchAll(PDO::FETCH_GROUP|PDO::FETCH_ASSOC));
Try this:
$records = $pdo->query('SELECT id, partName FROM myTable');
$records->setFetchMode(PDO::FETCH_KEY_PAIR);
print_r($records->fetchAll());
For that you need to set only your desired fields in iteration.
$part_name = [];
$records = $pdo->query('SELECT * FROM your_table');
foreach ($records as $row) {
$part_name[$row['id']] = $row['partName'];
}

How to retrieve tree nodes children (recursive function help)

I have a binary, the database table of relationships looks like this:
+----+----------+---------+-----+
| id | parentID | childID | pos |
+----+----------+---------+-----+
| 1 | 1 | 2 | l |
| 2 | 1 | 3 | r |
| 3 | 2 | 4 | l |
| 4 | 3 | 5 | r |
| 5 | 4 | 6 | l |
| 6 | 5 | 7 | r |
+----+----------+---------+-----+
I am able to extract or children of for example 1 - but I have very clumsy function for that, so I need something that works better.
The output I need should look like this:
Array
(
[0] => Array
(
[id] => 2
[parentID] => 1
[pos] => l
)
[1] => Array
(
[id] => 4
[parentID] => 2
[pos] => l
)
[2] => Array
(
[id] => 6
[parentID] => 4
[pos] => l
)
[3] => Array
(
[id] => 3
[parentID] => 1
[pos] => r
)
[4] => Array
(
[id] => 5
[parentID] => 3
[pos] => r
)
[5] => Array
(
[id] => 7
[parentID] => 5
[pos] => r
)
)
So far I came up with this function, however it returns nested array, I want it flattened ... but whenever I tried it it just fails.
function children($pid) {
//set sql
$sql = "SELECT * FROM relationships WHERE parentID = ".$pid;
//save query to result
$result = mysql_query ($sql)
or die("Bad request " . mysql_error());
while ($item = mysql_fetch_array($result)):
$topchild["id"] = $item["childID"];
$topchild["parentID"]= $item["parentID"];
$topchild["pos"] = $item["pos"];
$children[] = $topchild;
$children[] = children($item["childID"]);
endwhile;
return $children;
}
What do I do wrong there?
I want it flattened
$children[] = children($item["childID"]);
instead add each of the items in the return value separately:
foreach (children($item['childID'] as $child)
$children[]= $child;
(Also shouldn't $topchild be initialised inside the loop?)
If you are doing a lot of recursive queries like this, a parent-child relation table is not a good choice of data structure. Consider one of the hierarchically-oriented solutions such as nested sets.

Categories