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";
}
Related
I have a table of products with a column (prodCat) which is an ID column number, this relates to another table I have set-up (rangecat) which links to a column (rangeCatID).
My code is as follows:
<?php
$querycat = "SELECT DISTINCT products.prodCat, rangecat.rangeCatTitle, rangecat.rangeCatSEO FROM products INNER JOIN ranges ON products.prodRangeID = ranges.rangeID INNER JOIN rangecat ON products.prodCat = rangecat.rangeCatID WHERE rangeSEO = '$seoname' AND prodState = 1";
$resultcat = $conn->query($querycat);
while($rowcat = $resultcat->fetch_object()){
if ($page[3] == $rowcat->rangeCatSEO) { $state = ' class="onstate"'; } else { $state = ''; }
echo '<li ' .$state. '>' . $rowcat->rangeCatTitle . '</li>';
}
$resultcat->free();
?>
Please see following screenshot below which show the category returned multiple times:
Please see below for products table structure:
Please see below for rangecat table structure:
Given that your query has related tables, then the "duplicates" come from the fact that there are related rows in the categories table with other rows in the other tables.
If you want to get only unique rows, you must use either select distinct or group by. I'd go with select distinct:
SELECT DISTINCT category -- Or the appropriate column name
FROM products
INNER JOIN ranges ON products.prodRangeID = ranges.rangeID
INNER JOIN rangecat ON products.prodCat = rangecat.rangeCatID
-- Add where conditions here
Once you've got the unique category, you can create a second statement to get the products for each unique category.
Hope this helps
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’ve a small piece of code I’ve tried to wrap my brain around for some hours now.
I’m trying to create a query which retrieves some elements and in the end group them by a selector.
I’ll try to demonstrate: I’m querying all the members in my organization. Each member is a member of a “department” and there can be several members in each department, but only one department for each member.
So I’ve created the query to select all members in my organization group by departments.
When I’m displaying it using a simple foreach($my_query as $q) … I want to output $q->department_name but only once for that department. Right now it says “Marketing” under all five members and then it changes to “HR” for all those members. I’ve tried some different methods such as array_unique, creating a function that checks for the string to see if it is the same and other but with no result.
I could create two foreach loops but if it is at all possible not to I would prefer that because two foreach loops would affect the performance.
Any help or suggestion would be very much appreciated.
Sinerely
- Mestika
--- Edited ---
By the way, my query looks like this:
SELECT *
FROM wp_term_taxonomy AS cat_term_taxonomy
INNER JOIN wp_terms AS cat_terms ON cat_term_taxonomy.term_id = cat_terms.term_id
INNER JOIN wp_term_relationships AS cat_term_relationships ON cat_term_taxonomy.term_taxonomy_id = cat_term_relationships.term_taxonomy_id
INNER JOIN wp_posts AS cat_posts ON cat_term_relationships.object_id = cat_posts.ID
INNER JOIN wp_postmeta AS meta ON cat_posts.ID = meta.post_id
WHERE cat_posts.post_status = 'publish'
AND meta.meta_key = 'active'
AND meta.meta_value = 'active'
AND cat_posts.post_type = 'member'
AND cat_term_taxonomy.taxonomy = 'deparment'
GROUP BY cat_terms.slug, cat_term_relationships.object_id
$prev_department = NULL;
foreach ($my_query AS $q)
{
if ($prev_department != $q->department_name)
echo '<h2>'. htmlentities($q->department_name, ENT_COMPAT, 'UTF-8') .'</h2>';
// ...
$prev_department = $q->department_name;
}
Can someone help me with listing items by category in PHP?
I'm trying to create a simple list of books by category:
JavaScript
JavaScript Patterns
Object-Oriented JavaScript
Ajax
Ajax Definitive Guide
Bulletproof Ajax
jQuery
jQuery Cookbook
Learning jQuery 1.3
I have no problems with the data structure or SQL query:
BOOK: book_id, book_title, fk_category_id
CATEGORY: category_id, category_name
SELECT category.category_name, book.book_title
FROM category LEFT OUTER JOIN book
ON category.category_id = book.fk_category_id;
My problem is that I don't know how to write the PHP script to list the books under each category header.
I know enough to get the result set into an array, but then I'm stumped on using that array to group the items as shown.
Another Stack question addresses almost the same thing, but the responses stop short of solving the PHP code: List items by category
Thanks!
Add an ORDER BY category.category_name clause to your query so that as you loop through the resulting rows, the items in each category will be grouped together. Then, each time the category is different from the previous one seen, print out the category as the heading before printing the title.
$category = null;
foreach ($rows as $row) {
if ($row['category_name'] != $category) {
$category = $row['category_name'];
print "<h1>".$category."</h1>\n";
}
print $row['book_title']."<br/>\n";
}
Order the results by category and then just iterate thru, putting a category header whenever the category name changes.
The ordering is most easily done in the SQL query. You don't even need an intermediate array.
SELECT category.category_name, book.book_title
FROM category LEFT OUTER JOIN book
ON category.category_id = book.fk_category_id
ORDER BY category.category_name
And then for the PHP
$res = mysql_query($query);
$lastCategory = '';
while ($row = mysql_fetch_assoc($res))
{
if($row['category_name'] != $lastCategory)
{
$lastCategory = $row['category_name'];
echo "<br /><strong>$lastCategory</strong>";
}
echo $row['book_title'] . ' <br />';
}
You do not need to put all your results into an array first. You can just fetch them as you go.
I am looking for a cleaner way to do this. My code works, but I know it can be better. I have three tables: one with a list of Category Groups, One with a list of categories that are linked to category groups, and one with a list of news stories that are linked to the categories.
I need to loop through all of the names of the Category Groups, followed by the names of the categories that are in the category groups, with the number of news stories in each category listed as well.
I have three tables: CategoryGroups, Categories, News.
I have a set of queries. The first queries all the rows from the CategoryGroups table:
$result = mysql_query( '
SELECT cat_group_id, cat_group_name FROM CategoryGroups
' );
The second query is inside the looped results of the first query and finds all the categories that have a news item and are linked to a specific category group:
<?php
while( $row = mysql_fetch_assoc( $result ) ){
$id = $row['cat_group_id'];
$name = $row['cat_group_name'];
echo "<h3>$name</h3>";
$sql = mysql_query("
SELECT category_id, title FROM `Categories`
WHERE cat_group_id = $id
AND category_id IN
(SELECT news.category_id FROM news)
");
while( $row = mysql_fetch_assoc($sql) ) {
$title = $row['title'];
$catid = $row['category_id'];
$numbers = mysql_query("
SELECT * FROM news
WHERE category_id =$catid"
);
$nums = mysql_num_rows($numbers);
echo "$title ($nums)<br/>\n";
}
?>
I would like to limit this to one or two queries, with efficiency in mind. I know this can be done, however I have not been successful in my attempts.
thanks.
Why not JOIN the tables?
SELECT cat_group_name, title, count(newsid)
FROM CatagoryGroups
INNER JOIN Categories ON cat_group_id
INNER JOIN News ON category_id
GROUP BY cat_group_name, title
looks like it should be close, if table news has a newsid column (it's gotta have SOME primary key, right? well, count that;-). With the obvious indexes the JOINs should be quite fast, and your PHP code can do whatever output formatting you may need from that.
I suggest you need to get a book on SQL, such as "SQL Queries for Mere Mortals."
$sql = mysql_query("
SELECT cg.cat_group_name, c.title, COUNT(n.category_id) AS NumNews
FROM `CategoryGroups` cg
JOIN `Categories` c USING (cat_group_id)
JOIN `News` n USING (category_id)
GROUP BY cg.cat_group_name, c.title");
Then loop over the result and output a new <h3> each time the cat_group_name is different from the previous row.