I have a tree structured table, one of my table is Categories:
category_id | parent_id (in foreign key it is category_id) | title
And now I want to have a query and give it a category_id and it search whole table and go inside each children and get their children to the end point and select all.
I can do this by PHP, but I want to see how can I do it by SQL, this is my PHP code:
function getChild($id){
static $category_ids = [];
$category_ids[] = $id;
$list=$this->_model->select('categories','*', 'category_parent_id = '.$id);
foreach($list as $category) {
$category_ids[] = $category['category_id'];
if($category['last_child'] == 0){
$this->getChild($category['category_id']);
}
}
$category_ids = array_unique($category_ids);
return $category_ids;
}
//$list=$this->_model->select('categories','*', 'parent_id = '.$id);
//The above code is one of my functions (methods) and it select from table category * by parent_id
How to write a SQL query for it?
Which one is faster (PHP or that SQL query)
Related
using codeigniter query i am unable to get appropriate results
i need to get results of a table this way
$catid=$_POST['category'];
$new_id = select catid from categories_table where catid='$catid' and parent='$catid';
so it will add also include results of other cats have $cadid as parent
Select * from articles_table where catid = '$new_id';
i am trying in codeigniter like this
$p=$this->input->post('category');
$this->db->select('catid');
$this->db->where('parent',$p);
$this->db->where('catid',$p);
$query = $this->db->get('categories_table');
if($query->num_rows()>0)
{
foreach($query->result() as $row)
$new_id[]=$row->catid;
}
$this->db->where('catid',$new_id);
$this->db->where('status',1);
$this->db->order_by('time_added', 'desc');
$res = $this->db->get('articles_table');
$query_res= $res->result();
it gives the error Message: Trying to get property of non-object
cats table
catid -- parent -- name
1 -- 0 -- first cat
2 -- 1 -- cat child of first
Article table
id -- catid - content
1 -- 2 -- first article
2 -- 1 -- second article
if i query where cat id = 1 it should return results from catid 2 too as 2 is child of one
I would try using the model something like this:
$p = $this->input->post('category');
$query = $this->db
->select('catid')
->where('parent',$p)
->or_where('catid',$p)
->get('categories_table');
$data = array();
if($query->num_rows())
{
foreach($query->result() as $row)
{
$res = $this->db
->where('catid',$row->catid)
->where('status',1)
->order_by('time_added', 'desc')
->get('articles_table');
foreach ($res->result() as $article_data)
{
$data[] = $article_data;
}
}
}
return $data;
The code first checks for categories where $p is EITHER equal to catid OR parent in the categories_table. Then checks for articles that has the same catid as the category and adds all articles to an array, $data.
I am trying to get direct parent (if exists also not grand parents) of each child element in order, so far I have tried this function:
$child_id = 15;
$query = "SELECT * FROM family WHERE parent_id < $child_id ORDER By parent_id DESC LIMIT 0,7";
$res = mysql_query($query);
while($obj = mysql_fetch_assoc($res))
{
$results[] = $obj;
}
$fetchedResults = fetchParentsById($results, $child_id);
function fetchParentsById($results, $id, $parentfound = false, $parents= array())
{
foreach($results as $row)
{
if((!$parentfound && $row['parent_id'] == $id) || $row['child_id'] == $id && $row['parent_id'] > 0)
{
$rowdata = array();
foreach($row as $k => $v)
{
$rowdata[$k] = $v;
}
$parents[] = $rowdata;
if($row['child_id'] == $id)
{
$parents= array_merge($parents, fetchParentsById($results, $row['parent_id'], true));
}
}
}
return $parents;
}
Now, I get all the parents but I want to get parents till the desired level from bottom up, e.g. I have 15 records, if I give child_id=15 and level = 4, then it should get me (1) parent of 15 (2) parent of 14 (3) parent of 13 (4) parent of 12 and should not proceed.
Iow, I just have to limit the number to fetch the parents to the desired level.
Any better solution, or where am I mistaking?
Table Structure :
id | parent_id | child_id
First of all, i was thinking your table structure is wacky. Until i realized that its a cross-table and id is not what parent_id or child_id refer to.
Assuming there is only one parent per child, i would advise structuring your table simpler:
| id | parent_id |
And use NULL for parent_id to indicate an id has no parent. The query would simply be
SELECT * FROM family WHERE parent_id = NULL
Or if the data has fixed id's that may or may not appear in the data, use a query like
SELECT * FROM family WHERE parent_id <> (SELECT id FROM family GROUP BY id)
to find any parent_id that is not listed in the table as id. But that would assume the data is incomplete.. which it should never be unless you dont have to trust your data.
I'd take a look at Closure Sets.
Read from slide 48 on for a few ways of working with Parent / Child data.
http://www.slideshare.net/billkarwin/sql-antipatterns-strike-back
You'll need to restructure your data - whether it's worth it (or not) depends on the volume of queries you expect.
I have a shopping cart im making. And I'm stumped at finding out how I can load products within multi-level categories that are children of the category im looking at.
For example:
-Cars
Subarus (Legacy, Outback) - Holden (Commodore, Calais) - Toyota (Corolla, Camry)
If i'm looking in the cars category, i can select the child category but I cant view the actual products that are in those sub-categories. Is there any way I can do this? even if you can have unlimited levels of categories like "ROOT > Cars > Sedan > Subaru's..."?
Each product has a category ID which it relates to a category. Each category has its unique ID and a 'parent' id which has the ID of the category that it's a child of.
I think what you'll need to do is build up a list of category IDs to then construct an IN clause for your sql. Let's say you have the following category structure:
Cars (id:1)
Toyota (id:2, parent:1)
Mini (id:3, parent:2)
Corolla (id:4, parent:3)
Holden (id:5, parent:1)
Sports (id:6, parent:5)
HSV (id:7, parent:6)
To get all descendants of a category, you'll need to loop through the parent/child structure with something like this:
/**
* I'm only writing pseudocode here btw; I haven't tested it.
* Obviously you'll need to fire the SQL commands...
*/
function getKids ($id, $found = array())
{
array_push ($found, $id);
$nbrKids = "select count(id) from category where parent_id = $id";
if ($nbrKids > 0) {
$newKids = "select id from category where parent_id = $id";
foreach ($newKids as $kid) {
return getKids ($kid, $found);
}
}
else {
return $found;
}
}
Then call getKids() like this, where $id is your category id:
$ids = getKids($id);
$ids is an array of all the categories you are interested in. You could then use join() to construct an SQL string:
$sql = "select * from cars where category_id in (" . join (",", $ids) . ")";
For correctness, you should check that $ids has at least 1 member first, otherwise your SQL query will be invalid.
[edit: actually in the above code, $ids will always have at least one member: the initial id. However, the code doesn't verify the initial id is a valid category_id. ]
Please see the data tables and query below ..
Items
Id, Name
1, Item 1
2, Item 2
Categories
Id, Name, Parent ID
1, Furniture , 0
2, Tables, 1
3, Beds, 1
4, Dining Table, 2
5, Bar Table, 2
4, Electronics, 0
5, Home, 4
6, Outdoors, 4
7, Table lamp, 4
ItemCategory
ItemId, CategoryId
1, 2 .. Row1
2, 4 .. Row 2
2, 5 .. Row 3
ItemCategory table stores which items belongs to which category. An item can belong to top level and or sub category. there are about 3 level deep categories, that is, Tob level, sub level, and sub sub level.
Users select all of the categories they want to view and submit and I can query the database by using a sample query below..
SELECT * FROM items i INNER JOIN ItemCategory ic ON
ic.itemId = i.itemId AND ic.itemId IN ('comma separated category ids')
This works fine.
My question is that Is it possible to view all the items under a top level category even though it has not been directly assigned to the item. For example, if users select Furniture above, then it lists all the items belonging to its sub categories (even though the ItemCategory doesn't contain any record for it)??
I'm open to making necessary amendements to the data table or queries, please suggest a solution. Thank you.
Watcher has given a good answer, but I'd alter my approach somewhat to the following, so you have a structured recursive 2-dimensional array with categories as keys and items as values. This makes it very easy to print back to the user when responding to their search requirements.
Here is my approach, which I have tested:
$items = getItemsByCategory($topCategory);
//To print contents
print_r($items);
function getItemsByCategory($sid = 0) {
$list = array();
$sql = "SELECT Id, Name FROM Categories WHERE ParentId = $sid";
$rs = mysql_query($sql);
while ($obj = mysql_fetch_object($rs)) {
//echo $obj->id .", ".$parent." >> ".$obj->name."<br/>";
$list[$obj->name] = getItems($obj->id);
if (hasChildren($obj->id)) {
array_push($list[$obj->name],getItemsByCategory($obj->id));
}
}
return $list;
}
function getItems($cid) {
$list = array();
$sql = "SELECT i.Id, i.Name FROM Items p INNER JOIN ItemCategory ic ON i.id = ic.ItemId WHERE ic.CategoryId = $cid";
$rs = mysql_query($sql);
while ($obj = mysql_fetch_object($rs)) {
$list[] = array($obj->id, $obj->name);
}
return $list;
}
function hasChildren($pid) {
$sql = "SELECT * FROM Categories WHERE ParentId = $pid";
$rs = mysql_query($sql);
if (mysql_num_rows($rs) > 0) {
return true;
} else {
return false;
}
}
Hope this helps.
With recursion, anything is possible:
function fetchItemsByCat($cat, &$results) {
$itemsInCat = query("SELECT Items.Id FROM Items INNER JOIN ItemCategory ON ItemCategory.ItemId = Items.Id WHERE CategoryId = ?", array($cat));
while($row = *_fetch_array($itemsInCat))
array_push($results, $row['Id']);
$subCategories = query("SELECT Id FROM Categories WHERE Parent = ?", array( $cat ));
while($row = *_fetch_array($subCategories))
$results = fetchItemsByCat($row['Id'], $results);
return $results;
}
$startCat = 1; // Furniture
$itemsInCat = fetchItemsByCat($startCat, array());
The function is somewhat pseudo-code. Replace *_fetch_array with whatever Database extension you are using. The query function is however you are querying your database.
Also, this is untested, so you should test for unexpected results due to using an array reference, although I think it's good to go.
After calling the function, $itemsInCat will be an array of integer ids of all of the items/subitems that exist in the given start category. If you wanted to get fancy, you can instead return an array of arrays with each 2nd level array element having an item id as well as that item's assigned category id, item name, etc.
If you use MySQL, you're out of luck short of indexing your tree using typical techniques, which usually means pre-calculating and storing the paths, or using nested sets:
http://en.wikipedia.org/wiki/Nested_set_model
If you can switch to PostgreSQL, you can alternatively use a recursive query:
http://www.postgresql.org/docs/9.0/static/queries-with.html
Evidently, you can also recursively query from your app, but it's a lot less efficient.
I'm having trouble figuring out how to query every item in a certain category and only list the newest 10 items by date. Here is my table layout:
download_categories
category_id (int) primary key
title (var_char)
parent_id (int)</pre></code>
downloads
id (int) primary key
title (var_char)
category_id (int)
date (date)</pre></code>
I need to query every file in a main category that let's, say, have 100 items and 5 child categories and only spit out the last 10 added. I have functions right now that just add up all the files so I can get a count by category, but I can't seem to modify the code to only display a certain amount of items based on the date.
I modified the code I am using to count all the files in a category and its children to list the files. The main problem is when I add a LIMIT clause to my SQL, it just limits the results listed from that category, not the overall query.
The code puts all the download categories in an array then loops through each one querying the downloads with that category_id.
Is my best bet is to put the results in an array and then sort it from there?
function list_cat_files($parent, $start) {
global $menu_array;
if($start == 1) {
unset($GLOBALS["total"]);
$query = mysql_query("SELECT * FROM download_categories");
while ( $row = mysql_fetch_assoc($query) ) {
$menu_array[$row['category_id']] =
array(
'name' => $row['title'],
'parent' => $row['parent_id'],
'category_id' => $row['category_id']);
}
}
foreach($menu_array as $key => $value) {
global $total;
if ($value['parent'] == $parent) {
$category_id = $value['category_id'];
$query1 = mysql_query("SELECT lid AS id, title, date, category_id FROM downloads WHERE category_id='$category_id'");
while($item = mysql_fetch_array($query1)) {
$total .= "--- ($item[lid]) : $item[title] <BR><BR>";
}
list_cat_files($key, 0);
}
}
return $total;
}
Would something like this help? Assuming $categories is an array of the category ids you want.
$query = FALSE;
foreach ($categories as $category){
$query .= $query ? ' UNION ': '';
$query .= "SELECT * from `downloads`
WHERE category_id=$category order by date limit 10\n";
}
// run query here
If it helps, when I deal with complicated category structures, here's how I approach it. I do as many queries as I want to make sorting it out easy for me as the developer. Screw performance. Then I cache the output as needed and use the cached file for public consumption. Categories don't change that often, so those resource hungry queries are only run on occasion when an admin is changing things around, and only by 1 person at a time, rather than everyone who is visiting.
select * from downloads where category_id=X order by date limit 10
Repeat for each category. Unless you want to implement a nested set/modified preorder tree traversal (MPTT): http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/