Say I have 2 tables. parent & child where a parent can have many children.
I want to get the data in the following structure so:
[
{ parent_id:1, children: [{child_id: 1, parent_id:1},{ child_id:2, parent_id:1}],
{ parent_id:2, children: [{child_id: 3, parent_id:2},{ child_id:4, parent_id:2}],
{ parent_id:3, children: [{child_id: 5, parent_id:3},{ child_id:6, parent_id:3}]
]
JOIN gives me duplicate parent data for each child, and GROUP BY parent_id only gives me 1 child per parent.
Currently my solution is: SELECT * from parent loop through the results and then SELECT * FROM child WHERE parent_id = :parent_id. I feel there has to be a better way.
Is MySQL capable of performing this in fewer queries, if so how would I go about it?
EDIT: Hacky solution based on using GROUP_CONCAT:
$q = "SELECT p.*, GROUP_CONCAT(c.id,'|#|', c.column_2, '|#|', c.column3, '|#|', c.column4 SEPARATOR '###') AS children
FROM parent p
INNER JOIN child c ON p.id = c.parent_id";
$parents= query($q);
foreach ($parents as &$parent) {
$children = explode('###', $parent->children);
foreach ($children as &$child){
$child = array_combine(['id','col1','col2','col3'], explode('|#|', $child));
}
$parent->children = $children;
}
Essentially get the string with separators, explode into records, then explode into columns and remap the column values with hardcoded column names.
Leaving this open to tidier solutions.
If there are no better solutions, which would be more efficient.
1 query:group_concat where php manipulates the data into the desired structure.
n* queries: get all parents, then loop through them and get each parents children.
2 queries: get all parents, get all children, loop through and map them (currently using this)
I think you can use GROUP_CONCAT:
SELECT p.parent_id, GROUP_CONCAT(c.child_id) AS childs
FROM parent p
JOIN child c
ON p.parent_id = c.parent_id
GROUP BY p.parent_id
Related
My Models are related as follows:
Post BELONGS_TO Parent
Parent HAS_MANY ParentAdmin
Now I wish to find all Parent admins of all post if a post's attribute's value is met. The equivalent sql query is :
SELECT parent_admins.* FROM posts
LEFT JOIN parents AS Parent ON Parent.id = posts.target_id
LEFT JOIN parent_admins ON parent_admins.parent_id = Parent.id
WHERE posts.admin_notification = 0 AND Parent.maxtime > posts.created
Assuming Models are linked in cakephp accordingly and recursive = 2, what will be the condition array in find query?
You can both use the bindModel & unbindModel methods to get the exact query you want or you can use the Containable behavior.
More information about how to create and destroy associations on the fly with CakePHP: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#creating-and-destroying-associations-on-the-fly
And you'll need this array of conditions:
$conditions = array(
'Post.admin_notification' => 0,
'Parent.maxtime >' => 'Post.created'
);
according to your query :-
$conditions=array(
'posts.admin_notification'=>0,
'Parent.maxtime >'=>'posts.created'
);
If your query do the job:
Inside PostsController:
$query = "SELECT parent_admins.* FROM posts
LEFT JOIN parents AS Parent ON Parent.id = posts.target_id
LEFT JOIN parent_admins ON parent_admins.parent_id = Parent.id
WHERE posts.admin_notification = 0 AND Parent.maxtime > posts.created"
$result = $this->Posts->query($query);
$debug($result);
I have mysql query that gets all the parent ids to child map into an array, then i have a function to print out all child ids for a parent id.
what i want to accomplish now is get the username for each echoed child id using this function.
and paginate the result on each page if possible.
the mysql query + function to get the ids to childs map into array:
$res = $db->query('SELECT p.id AS parent, GROUP_CONCAT( c.id ) AS children
FROM rev_r_clients AS p
JOIN rev_r_clients AS c ON c.parent_client_id = p.id
GROUP BY p.id');
$parents = Array();
while($row = $res->fetch_assoc()) {
$parents[$row['parent']] = explode(',',$row['children']);
}
Now the Function that print out the children for a specific parent ID:
function subtree($id, $parents) {
echo $id;
echo ' <br> ';
if (isset($parents[$id]))
foreach ($parents[$id] as $child)
{
subtree($child, $parents);
}
Now any idea how to get the username from another MYSQL table ( rev_users )
and if possible paginate the result on each page?
Can't make a query for each row as the result is huge , thousand of child ids for each parent id.
Rewrite your query to return all the fields you need. Execute a new query for each row of the cursor is not a solution, but a workaround.
I have three tables like below:
[Category]->[SubCategory]->[Leaf]
-> means one-to-many relation;
Is it possible to make nested list with just one query?
Actual Query:
Select * from Category
join SubCategory
join Leaf
Intended Output:
Category
--Subcategory
----Leaf
----Leaf
--Subcategory
----Leaf
----Leaf
I know about composite design pattern; but lets say I have the result set from query and I want to manipulate the list above with minimal attempt.
Update
My question is without for loop and sub query, can it be done with the one query above, or what is the best way to generate 3 nested list like above from three tables?
Update:
What is the most optimized way to create a nested list like above from three tables in any database and in any language?
Use this query:
SELECT *
FROM Category c
JOIN SubCategory s ON s.CategoryID = c.ID
JOIN Leaf l ON l.SubCategoryID = s.ID
ORDER BY c.ID, s.ID, l.ID
Then in your PHP loop, print the Category name whenever it changes, print the SubCategory name whenever it changes, and print every Leaf name.
$last_cat = null;
while ($row = fetch()) {
if ($row['CategoryName'] !== $last_cat) {
echo $row['CategoryName'] . "\n";
$last_cat = $row['CategoryName'];
$last_subcat = null;
}
if ($row['SubCategoryName'] != $last_subcat) {
echo "--{$row['SubCategoryName']}\n";
$last_subcat = $row['SubCategoryName'];
}
echo "----{$row['LeafName']\n";
}
I have category table which stores all category info [parent and child both] , category_child table which stores parent and child category relation, and product_category table which stores relation between child_category and product.
category - All Category {cid, cname, isParent, status} column.
category_child - Realtion {parent_id, child_id}.
category_product - Relation {product_id, child_category_id}
product - All product details {pid, pname, pimage, pprice,pstock}
I am displaying all Parent Category Link in Front page. Now, Whenever any person will click on parent category Link, I want to display 4 product information from each child category of parent category.
here is my code, which is horrible at the moment, and i am looking for some help in minimising it as much as possible.
$fetchChild = $mysqli->query("SELECT child_id from category_child where parent_id='".$mysqli->real_escape_string($pid)."'");
$listchild = array();
if($fetchChild->num_rows > 0) {
$n = 1;
while($storeChild = $fetchChild->fetch_assoc()) {
$listchild['child_id'][$n] = $mysqli->query("SELECT product_id from category_product where child_category_id='".$mysqli->real_escape_string($storeChild[$n])."'");
if($listchild['child_id'][$n]->num_rows > 0) {
$i = 1;
while($storeMore = $listchild['child_id'][$n]->fetch_assoc()) {
$listchild['product_id'][$i] = $mysqli->query("SELECT pid, pname, pimage, pprice, pstock from product where pid='".$mysqli->real_escape_string($storeMore[$i])."'");
if($listchild['child_id'][$n]['product_id'][$i]->num_rows > 0) {
$me = 1;
while($smeLast = $storeMore[$i]->fetch_assoc()) {
$listchild['child_id'][$n]['product_id'][$i]['pid'] = $smeLast['pid'];
$listchild['child_id'][$n]['product_id'][$i]['pid'] = $smeLast['pname'];
$listchild['child_id'][$n]['product_id'][$i]['pid'] = $smeLast['pimage'];
$listchild['child_id'][$n]['product_id'][$i]['pid'] = $smeLast['pprice'];
$listchild['child_id'][$n]['product_id'][$i]['pid'] = $smeLast['pstock'];
$me++;
}
} else {
echo '<meta http-equiv="refresh" content="0; url=index.php?error=Something+Went+Wrong+We+are+Fixing+it" />';
}
$listchild['product_id'][$i]->free();
$i++;
}
}
$listchild['child_id'][$n]->free();
$n++;
}
} else {
echo '<meta http-equiv="refresh" content="0; url=index.php" />';
}
$fetchChild->free();
Kindly help in minimizing nested while and query in my code.
Thanks
If you want, you can put everything into one SQL query using JOIN statement.
SELECT `category`.*, `product`.* FROM `product`
LEFT JOIN `category_product` ON `category_product`.`product_id` = `product`.`pid`
LEFT JOIN `category_child` ON `category_child`.`child_id` = `category_product`.`child_id`
LEFT JOIN `category` ON `category`.`cid` = `category_child`.`child_id`
WHERE `category_child`.`parent_id`='".$mysqli->real_escape_string($pid)."'
But I don't think it's the best solution.
PS. By the way, there is no LIMIT of 4 products per child category in your code, so I haven't put it either.
You can reduce all your queries to one query with JOINS. Using joins will allow you to return results from one table (e.g. product) basing on the conditions provided in another table or tables (e.g. category_product, category_child).
Read more about joins somewhere at Stack Overflow or browse some other resources. For example http://www.codinghorror.com/blog/2007/10/a-visual-explanation-of-sql-joins.html .
You should never use SQL query in a loop! It is very slow, you can overload the sql server etc...
Your database structure is bad. If you want to store hiearchical tree there are better options:
Tree 1 level:
1A2
Tree 2 level:
1A6
2B3 4C5
Tree 3 level:
1A12
2B7 8C9 10D11
3E4 5F6
You have a left and a right value by each node, and you can have the parent id too. You can check whether a node is leaf or branch if you check the difference of the right-left. By leaves it is one. You can check whether a leaf is under a branch if its left is bigger than the left of the branch and its right is smaller than the right of the branch. etc... This structure is described on several sites, for example here: nested set model
After you transformed your model to nested set, you can use a simple sql to ask for your products. Something like this:
SELECT
p.*
FROM
categories c, categories b, products p, product_categories pc
WHERE
b.category_id = ?
AND
c_category_id <> b.category_id
AND
c.category_left BETWEEN b.category_left AND b.category_right
AND
c.category_right - c.category_left = 1
AND
c.category_id = pc.category_id
AND
p.product_id = pc.product_id
This is good for beginning, your code will contain group by, limit, etc... because you want to ask only a single product per category... (or you can simply use order by rand() and limit 4 ...)
You should use prepared statements instead of manual escaping... http://php.net/manual/en/mysqli.prepare.php
i have a table with 3 columns id,name and parent_id representing categories. The root categories(categories with no parents) have parent_id 0. All other categories have parent_id as the id of their immediate parent. There is no limit on the depth of categories i mean that a category can be 3,4 or even 10 levels down from a root category. What i need now is a PHP multi-dimensional array that contains all the root categories at the first level and then their immediate sub-categories at the next level,each sub-category under its parent category,and their sub-categories under them 1 level down. So its a tree like structure. there can be many levels in the tree
I dont need the exact code but need an idea. one such idea is get all the root categories with a select query and then fire a select query for each root query to get its subcategoies and so on recursively but that would be too much select queries.
Or if i know that my table is going to contain say 300 rows at the maximum, how can i do something like
$categories=GetResultAsArray(select * from categories);
and now manipulate the $categories array in memory to get the desired tree.
You're right, using the solution with a "parentid" column is simple but it makes you write recursive queries. There are several other designs for storing hierarchical data in a database that let you do queries more efficiently.
See:
What is the most efficient/elegant way to parse a flat table into a tree?
My other answers to SO questions on hierarchical-data
My presentation Models for Hierarchical Data with SQL and PHP
My book SQL Antipatterns Volume 1: Avoiding the Pitfalls of Database Programming
Sql Antipatterns Strike Back
It's possible duplicate (http://stackoverflow.com/questions/8431463/more-efficient-hierarchy-system/8431551) but here is a snippet to query an adjacent tree (parent_id,id,title):
$q = mysql_query("SELECT id, parent_id, name FROM categories");
while ($r = mysql_fetch_row($q)) {
$names[$r[0]] = $r[2];
$children[$r[0]][] = $r[1];
}
function render_select($root=0, $level=-1) {
global $names, $children;
if ($root != 0)
echo '<option>' . strrep(' ', $level) . $names[$root] . '</option>';
foreach ($children[$root] as $child)
render_select($child, $level+1);
}
echo '<select>';
render_select();
echo '</select>';
You can be inspired by this solution that retrieves the breadcrumb trail of a url and its parents (infinite depth of hierarchy levels).
$q = "SELECT T2.*
FROM (
SELECT
#r AS parent_id,
(SELECT #r := `parent`
FROM `route` WHERE id = parent_id)
AS `parent`, #l := #l + 1 AS `depth`
FROM
(SELECT #r := $route_id, #l := 0) vars, `route` T3
WHERE #r <> 0) T1
JOIN `route` T2
ON T1.parent_id = T2.id
ORDER BY T1.`depth` DESC";
if($res = $db->query($q)) {
if($res = $res->fetchAll(PDO::FETCH_ASSOC)) {
if($size = sizeof($res) > 0) {
// push results in breadcrumb items array
$breadcrumb['items'] = $res;
$breadcrumb['levels'] = $size;
// retrieve only titles
$titles = array_column($res, 'title');
// construct html result seperated by '>' or '/'
$breadcrumb['html']['gt'] = implode(' > ', $titles);
$breadcrumb['html']['sl'] = implode(' / ', $titles);
}
}
}
Entire get_breadcrumb() function available here : https://stackoverflow.com/a/63578607/2282880