checking value in n-depth tree? - php

I have two entities, post and category which is a 1:n relationship.
I have a reference table with two columns, post_id,category_id
The categories table has an id column, a status column and a parent_id column
If a category is a child of another category (n-depth) then it's parent_id is not null.
If a category is online it's status is 1, otherwise it is 0.
What I need to do is find out if a post is visible.
This requires:
Foreach category joined to the post trace up it's tree to the root node (till a category has parent_id == null), if any of those categories have status 0 then that path is considered offline.
If any path is online then the post is considered visible, otherwise it is hidden.
The only way I can think of doing this (as semi-pseudo code) is:
function visible(category_ids){
categories = //select * from categories where id in(category_ids)
online = false
foreach(categories as category){
if(category.status == 0)
continue;
children = //select id from categories where parent_id = category.id
if(children)
online = visible(children)
}
return online
}
categories = //select c.id from categories c join posts_categories pc on pc.category_id = c.id where pc.post_id = post.id
post.online = visible(categories)
But that could end up being a lot of sql queries, is there a better way?

If nested sets are not an option, I know about the following:
If the data is ordered so that children of a parent always follow after it's parent, you can solve this with one database-query over all data by skipping hidden nodes in the output.
This works equally with a sorted nested set, too, the principle has been outlined in this answer however the algorithms about getting the depth do not work and I would suggest a recursive iterator that is able to remove hidden items.
Also if the data is not ordered, you can create a tree structure from the (unsorted) query of all rows like outlined in the answer to Nested array. Third level is disappearing. No recursion needed and you get a structure you can easily output then, I should have covered that for <ul>/<li> html style output in another answer, too.
Answer to How can I convert a series of parent-child relationships into a hierarchical tree?
Answer to How to obtain a nested HTML list from object's array recordset?

A classic database vs memory tradeoff. What you are doing is building a tree with leafs in it. To build the tree you need recursive loop the leafs. Coming from a database there are 2 scenarios:
Build the tree recursive with a query for each leaf. You hold 1 tree in memory. That is what you are doing.
Get a flat structure from the database, and build the tree recursive in memory. You hold a flat tree and the real tree in memory. That is your alternative way.
What is better depends on a lot of things: your hardware (disk access vs memory), the size of the tree to name two.

Related

Yii : Products with MANY_MANY categories and categories may or not has parent. Get all products

I Have the following SQL schema:
tbl_products:
- id
- id_category
tbl_categories:
- id
- id_parent
tbl_products_categories: - id_product - id_category
My question is: In my categories model, what kind of relation I have to declare to get all products from this category and from it's childs ? Can I get all products from a relation or will I have to make a method to loop for each child-category and later merge all products and return it?
Anyone have any idea ?
Nested relationships, such as among categories or menu items, are notoriously tricky. The case is a bit simpler when you have constraints, such as a maximum number of children. In that case, your Product's criteria would contain as many joins as you have possible relationships (I wouldn't use Yii's relational active record in this case; would it make it too complicated in my opinion).
Otherwise, yes, your best bet is to loop recursively through each relationship and return a merged array. NOTE: this can be incredibly performance intensive, so you'll probably want to do some sort of caching on pages where this action is performed.
Since it is many to many, each product can be associated with the current desired children and all its parents, so when you query a parent, all children products will also be parent products.

Stop recursive incestuous child parent relationship in mysql

I am programming in PHP / MySQL / Javascript.
I have a list of parts which we want to link in a child / parent relationship with no limit on the amount of tiers.
When I am picking from a list of parts to add a child to a parent I limit the list of parts to exclude the parent itself, and any parts which are already children of that parent.
What I have discovered is that I also want to exclude the grandparents of the parent as otherwise we can get an incestuous relationship, which when I display the tree of parts will create an infinite loop.
Not only that but I cannot allow the child part to be a great grandparent of the parent or great great grandparent e.t.c.
Here is the SQL statement I use currently which I think could also be improved by using LEFT JOIN but I am not skillful enough with SQL at this point.
SELECT *
FROM sch_part_general
WHERE (sch_part_general.part_id <> $parentId)
AND (sch_part_general.part_id NOT IN
(SELECT part_id FROM sch_part_mapping WHERE parent_id = $parentId)
)
sch_part_general is a multi column table with all the parts, with part_id as the primary key.
sch_part_mapping is a two column mapping table with part_id (child) || parent_id (parent).
Could someone point me in the right direction with the SQL query? I am not keen on using a while loop to create the SQL statement as I think this will be quite inefficient but it is the only way I have considered might work so far.
MySQL doesn't have much (if any) support for hierarchical queries. If you want to stick to what is called theAdjacency List Model, all you can do is add a JOIN for each level you like to include. Needless to say this doesn't scale well.
On the other hand, if you can alter your Database Schema, I would suggest implementing the Nested Set Model.
A very good explantion of the Nested Set Model is presented in Mike Hillyer's blog
Limitations of the Adjacency List Model
Working with the adjacency list model in pure SQL can be difficult at
best. Before being able to see the full path of a category we have to
know the level at which it resides.
Nested Set Model
the concept of nested sets in SQL has been around for over a decade,
and there is a lot of additional information available in books and on
the Internet. In my opinion the most comprehensive source of
information on managing hierarchical information is a book called Joe
Celko’s Trees and Hierarchies in SQL for Smarties, written by a very
respected author in the field of advanced SQL, Joe Celko.
If you can't alter the schema, then there is no running away from looping as the answer from Lieven suggests.
if you can alter the schema, then maybe the following can also be enough for your case:
add a new column to the sch_part_mapping , lets call it "hierarchy_id". it is a value constructed to be unique int at the first time you start a totally new hierarchy (with the first grand grand grandest grandmost parent in any hierarchy - however its said in english) and is inserted to all lines belonging to a single hierarchy no matter at what level.
then, its easy to skip parents and grand parent found in the same hierarchy: to your sql above you can then add:
SELECT *
FROM sch_part_general
WHERE (sch_part_general.part_id <> $parentId)
AND (sch_part_general.part_id NOT IN
(SELECT part_id FROM sch_part_mapping WHERE parent_id = $parentId)
//addition here
and not exists (select * from sch_part_mapping where hierarchy_id= ? and parent_id = sch_part_general.part_id)
)
the question mark should be replaced with the relevant heirarchy id that you need to calculate.
EDIT: i missed that you have a variable for a specific parent ID, therefore the hierarchy_id can be calculated in the same query:
SELECT *
FROM sch_part_general
WHERE (sch_part_general.part_id <> $parentId)
AND (sch_part_general.part_id NOT IN
(SELECT part_id FROM sch_part_mapping WHERE parent_id = $parentId)
//addition here
and not exists (select * from sch_part_mapping where hierarchy_id= (select hierarchy_id from sch_part_mapping where parent_id = $parentId limit 1) and parent_id = sch_part_general.part_id)
)
With MySql/MariaDB you can use the Open Query Graph engine (http://openquery.com/graph/doc) which is a mysql plugin that lets you create a special table where you put the relationships, basically parentId and childId.
The magic is that you query this table with a special column latch depending of the value passed in the query will tell the OQGRAPH engine which command to execute. See the docs for details.
It handle not only tree (recursive 1-n relations), but graph data structures (recursive n-m relations) with weight (think for example that you want to store companies ownership, a company can have several subsidiaries and can also have several shareholders).

Setting up a nested set hierarchy

Let's say I have a bunch of businesses. And each business can have multiple categories, subcategories and sub-subcategories (three levels). Let's say I set up a table according to the nested set model for my categories.
How do I now use this table and assign categories to each business? I understand I will need another table but what node gets assigned? Is it the lowest level node?
business_id category_id
And then what's the right way to retrieve all the categories for each business?
The way this generally works is that you assign the leaf or lowest-level-node. Then when you are querying to get the full hierarchy you traverse up the tree to the root. It is generally much easier (especially in MySQL) to traverse from leaf to root then vice versa.
Here is the best link I've found that describes how to accomplish this query for a tree of dynamic size (the link you've included assumes that the tree is always 3 levels deep)

Adjacency List Model + Website Navigation

I am using the adjacency list model to find sub categories within my website. I have working PHP code to find all the categories and sub categories, but now I cannot figure out how use that to create a navigation system. Here is how the site will work, very basic:
URL string
There will be a main category, followed by levels
index.php?category=category-name&level1=sub-category&level2=another-sub-category&level3=content-item
Later I will make SEO friendly links.
URL with no sub categories
Where Level 1 is the content item
www.website.com/category/content-item/
URL with sub categories
Where Level 1, 2, 3, etc are the sub categories and the final level is the content item
www.website.com/category/sub-category/sub-category-2/content-item/
Here is the code I am using to find categories and sub categories. Currently it just outputs a list of all categories and sub categories and number's the level of each child. Not sure if this helps, it just creates a list.
function display_children($ParentCategoryID, $Level) {
// retrieve all children of parent
if ($ParentCategoryID == ''){
$Result = mysql_query('SELECT * FROM categories WHERE parent_category_id IS null');
}
else{
$Result = mysql_query('SELECT * FROM categories WHERE parent_category_id="'.$ParentCategoryID.'";');
}
// display each child
while ($Row = mysql_fetch_array($Result)) {
echo str_repeat('-',$Level)."[".$Level."]".$Row['category_name']."<br />";
display_children($Row['category_id'], $Level + 1);
}
}
See this question first for options on how to represent hierarchical data in a database.
Adjacency list is great for its simplicity, and makes changes easy, but can be awful because it leads to recursive code, such as your function above, in practice, which is a performance killer under load. The best approach, absent changing your data model is using MySQL session variables to retrieve the entire hierarchy in one query, which brings back all the data you need in one database call. Even this though leads to poor performance under load - less so than the recursive function - but still not good; and, I write from experience :).
If it was me I'd use either Nested Sets, Adjacency List in combination with some denormalizations, such as the Bridge Table and Flat Table, or just a Lineage Table. Really depends on how often the data changes and if you need those changes to be done easily. All of these options should be much, much faster, to work with rather than relying upon just the parent-child ID columns.

php / Mysql associate tree search and item query

I have a tree of categories in my database.
I also have a table of items associated with the tree by a category id.
Now, I want to list all items in a specific category and its children and their children, etc...
For now, I proceed this way:
Retrieve the id of all concerned categories.
Make a query in the items table with a WHERE clause like this: WHERE cat_id=2 OR cat_id=10 OR ...
I think this way cause the query to be very slow and very long if I have a lot of categories. A search can be in 100 categories sometimes.
Is there a better practice?
From gugl on "storing tree in relational database": http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
Adjacency List is simple, but not good in most complex cases
Nested Set is complex from 1st view (mostly during write), but it much more like standard for storing and reading trees in RDBMs.
+1 about
EXPLAIN select * from table
that will help you to see bottlenecks.
Also try instead of
column1 = 1 or column1 = 2
something like:
column1 in (1, 2)
But anyway without indexes it wouldn`t help.

Categories