Get Only First Parent of each child - php

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.

Related

Make hierarchy from table with PHP

I have a table in database and in this table i have added 3 columns that is i, name,parent_id.please see below.
ID | name | parent_id
1 name1 0
2 name2 1
3 name3 1
Now i want to fetch this id from database. I have created a method in PHP and fetch data one by one from database. Please see below
function getData($id)
{
$array = array();
$db = JFactory::getDBO();
$sql = "SELECT * from table_name when id = ".$id;
$db>setQuery($sql);
$fetchAllDatas = $db->getObjectList();
foreach ($fetchAllDatas as $fetchAllData)
{
if($fetchAllData->parent_id > 0)
{
$array[$fetchAllData->parent_id] = $fetchAllData->name;
$this->getData($fetchAllData->parent_id);
}
else
{
$array[$fetchAllData->parent_id] = $fetchAllData->name;
}
}
return $array;
}
Now if i call this method with id 3 like
$this->getData(3); // has a parent
It will return like that
Array(
[0]=>name1
)
But i want like below
Array(
[1]=>name3,
[0]=>name1
)
I know i have redefine array if we have parent but how i manage it.
i have used array_push php function but its not work with my condition.
foreach ($fetchAllDatas as $fetchAllData)
{
$array[$fetchAllData->parent_id] = $fetchAllData->name;
if($fetchAllData->parent_id > 0)
array_push($array,$this->getData($fetchAllData->parent_id));
}
return $array;
1)Because you do $array[$fetchAllData->parent_id] = $fetchAllData->name; in the if and in the else, you do this in both cases so put out of if..else.
2) Try to push the result of your second call in the original array to get what you want.
you have unique ID in your table, so if you call your query it will always return only one result. Maybe you wanted to write query this way:
$sql = "SELECT * from table_name when parent_id = ".$id;
if you want to get the result with given ID and his parent, you should add this after calling $this->fetchSharedFolder(...);
$array = array_merge($array, $this->getData($fetchAllData->parent_id));

Getting all children in tree structure table - MySql

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)

Get parents recursive in right order for deletion from MySQL table

I have an table structure like this
mysql> SELECT id, name, parent_id FROM categories;
+-------+------------+-----------+
| id | name | parent_id |
+-------+------------+-----------+
| 15790 | Test | 0 |
| 15791 | Test2 | 0 |
| 16079 | Subtest | 15790 |
| 16080 | Subtest 2 | 15790 |
| 16081 | Subsubtest | 16079 |
+-------+------------+-----------+
Now I want to look up the parent for every children and sibling and give it back in the right order for deletion.
So my output in this case would be:
Array
(
16081,
16080,
16079,
15791,
15790
)
I can't delete just by reversing the parent ids, because this should be solid walking back the tree.
Also I am not able/allowed to change the structure of the table. So building kind of an index is necessary.
Assuming you don't have access to TRUNCATE, SET (so you could do SET FOREIGN_KEY_CHECKS=0;), ALTER, etc. etc., and absolutely must use a script:
Since the question is tagged with php, this should do the trick:
function reversetree($src_arr, $currentid = 0)
{
$cats = array();
foreach($src_arr as $id => $parent)
{
if($parent == $currentid)
{
$cats[] = $id;
$cats = array_merge($cats, reversetree($src_arr, $id));
}
}
return !$currentid ? array_reverse($cats) : $cats;
}
$rs = array();
foreach($pdo->query('SELECT id, parent_id FROM categories') as $row)
$rs[$row['id']] = $row['parent_id'];
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
$pdo->beginTransaction();
foreach(reversetree($rs) as $v)
$stmt->execute(array($v));
$pdo->commit();
I don't understand why you need the IDs in a particular order. You can delete them with a transaction and they will all be deleted simultaneously.
DELETE FROM categories WHERE ID IN (15790,15791,16079,16080,16081);
You could add FOREIGN KEY constraint with CASCADE on DELETE.
The foreign key will point to the same table on the parent id field.
When you delete the parent, all the children (no matter what level) are removed automatically.
<?php
// housekeeping
$pdo = new PDO($dsn, $user, $password);
$select = $pdo->prepare(
"SELECT parent.id AS parent_id, child.id AS child_id
FROM categories AS parent
JOIN categories AS child ON parent.id = child.parent_id
WHERE parent.id = ?"
);
$delete = $pdo->prepare('DELETE FROM categories WHERE id = ?');
// deletes $node_id, deletes its children first if required
function delete_node($node_id){
$select->execute( array($node_id) );
$children = $select->fetchAll(PDO::FETCH_NUM);
if (count($children) !== 0) { // if 0, then the category does not exist, or it has no child
foreach ($children as $child) { // call delete_node() recursively on each child
delete_node ($child[1]);
}
}
$delete->execute( array($node_id) ); // then delete this node (or do nothing if $node_id does not exist)
}
// to delete one category and all its sub-categories
delete_node(15790);
// to delete all contents
$allTopLevel = $pdo->exec('SELECT id FROM categories WHERE parent_id = 0')->fetchAll(PDO::FETCH_NUM);
foreach ($allTopLevel as $node) {
delete_node($node[0]);
}
Not tested, not even sure if it "compiles", but you get the idea. Make sure to lock the table (or start a transaction) before calling delete_node().
Sorry, i can't help much, because SQL is not my thing. But perhaps someone could transfer java pseudo code to the solution
delete(table.getFirstRow().get(id));
delete(id_a){
for(data_set : table)
if(data_set.get(parent_id) == id_a)delete(data_set.get(id));
}
table.remove(id_a);
}
edit: no iteration about elements? So something like this?
delete(list){
if(list.size() == 0)return;
idList = list.getAll(id);
plist = table.getAllWhichEquals(parent_id, idList);
delete(plist);
table.remove(idList);
}
ah, forget it, i'm deleting not all at the same time, was just a try ^^

How can I use recursion to retrieve parent nodes?

I have created a recursive function to get parent products of a product. I'm almost sure I'm nearly there, but need help snapping out of recursion.
The result I am looking for is something like:
Product 1 (id:1, parent:none)
Product 2 (id:2, parent:1)
--- --- Product 3 (id:3, parent:2)
--- --- Product 4 (id:4, parent:2)
--- Product 5 (id:5, parent:1)
--- --- Product 6 (id:6, parent:5)
--- --- Product 7 (id:7, parent:5)
My updated function is as follows:
function get_parents ($pid, $found = array()) {
array_push ($found, $pid);
$sql = "SELECT * FROM products WHERE child_id = '$pid'";
$result = mysql_query($sql) or die ($sql);
if(mysql_num_rows($result)){
while($row = mysql_fetch_assoc($result)){
$found[] = get_parents($row['pid'], $found);
}
}
return $found;
}
I call it using a simple:
$parents = get_parents($pid);
The problem I am having is that when I run it, it creates an infinite loop, which doesn't break.
I don't want to get done for spamming so I have saved the result of my array to a text file, which can be seen here http://vasa.co/array.txt
Any help would be seriously appreciated :-)
Hmm.. judging by your structure of your DB, it would seem that something is amiss unless I'm missing something
The statement
$sql = "SELECT * FROM products WHERE child_id = '$pid'";
Tells me that for each product, you are storing the ID of the child. Typically, in a tree based structure, it's the reverse, you store the parent ID not the child - unless you want a child node to have many parents. If that is the case, then the function could easily run into problems. Consider the following:
| ID | Child_ID |
+----+----------+
| 1 | 2 |
| 2 | 1 |
This would cause an infinite loop. If you store the parent_id, then by that nature, you are encoding the graph to be hierarchical. Since every product has A parent, then the logic can be written recursively.
The could then be written as such?
function get_parents ($pid, $found = array()) {
array_push ($found, $pid);
$sql = "SELECT * FROM products WHERE id = '$pid'";
$result = mysql_query($sql) or die ($sql);
if(mysql_num_rows($result)){
while($row = mysql_fetch_assoc($result)){
$found[] = get_parents($row['parent_id'], $found);
}
}
return $found;
}

How do I retrieve items belonging to sub categories by selecting a top level category?

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.

Categories