Sub categories Hierarchy - php

I designed a SQL structure to represent categories and their subcategories.
I have 3 tables:
articles
articles_categories
categories
Articles table:
id,title,content
Categories table:
id, title, parent_id
articles_categories:
id,article_id,category_id
No problem with SQL, but now - lets say i'm on article id 5
article id 5 has - 3 categories, that 2 of them has parents, and the main has '0' as parent.
How do I fetch them all efficiently? (lets say for - breadcrumbs).
thanks!

Unless the depth of the category hierarchy is fixed, you cannot do this in MySQL with your current model (adjacency list). You'd have to traverse the hierarchy using several SQL statements in a loop.
If the category hierarchy is fairly static, you can "precompute" the tree by using:
Path enumeration
Nested sets
Closure table
All of the above, trades write performance for read performance.
Google or search SO for any of the above and you will find examples of how to implement it.
Quite often, I find that storing the data in a adjacency list (because of best matches the data model) and caching a copy of the tree in the application is good enough, but that depends on your requirements of course :)

This should do the job:
select * from articles_categories
left join categories on categories.id = articles_categories.category_id
where article_id=1;
+------+------------+-------------+------+--------+-----------+
| id | article_id | category_id | id | title | parent_id |
+------+------------+-------------+------+--------+-----------+
| NULL | 1 | 1 | 1 | first | 0 |
| NULL | 1 | 2 | 2 | second | 1 |
| NULL | 1 | 3 | 3 | third | 2 |
+------+------------+-------------+------+--------+-----------+
Additionally, I would remove the "id" column from associative table articles_categories.

Related

Getting more results from left jointed query

I have 2 tables named collections and posts. I need to display the collection name and 3 posts from each collection collected by users. Some of the collection has less than 3 posts and some have no posts at all. Also I need to count the posts (Not total number of posts but the posts produce by the query)
MySQL Tables
Collections
| collection_id | collection_name | uid |
| 1 | My collection 01 | 1 |
| 2 | My collection 02 | 1 |
| 3 | My collection 03 | 1 |
| 4 | My collection 04 | 2 |
| 5 | My collection 05 | 2 |
| 6 | My collection 06 | 1 |
Posts
| posts_id | post_title | cid |
| 1 | post title 1 | 1 |
| 2 | post title 2 | 1 |
| 3 | post title 3 | 1 |
| 4 | post title 4 | 3 |
| 5 | post title 5 | 2 |
| 6 | post title 6 | 3 |
cid is the collection id. So what I want to and uid is the user id. I want the results to be display
3 posts from My collection 01
post title 1
post title 2
post title 3
1 posts from My collection 02
post title 5
2 posts from My collection 02
post title 4
post title 6
Just made the example according to the dummy data I added in the table above.
I tried with left join with no luck
SELECT * FROM collections LEFT JOIN posts ON posts.cid= collections. collection_id WHERE posts ON posts.cid= collections. collection_id AND collections. uid=1 ORDER BY collections. collection_id DESC LIMIT 0, 16
With this query I can get the collection name and 1 post.
But if i run two queries it will work (1 inside the other)
SELECT * FROM collections WHERE uid=1 ORDER BY collection_id DESC LIMIT 0, 16
Then I get the collection id and run another query inside while loop of above query
SELECT * FROM posts WHERE cid=$collection_id ORDER BY post_id DESC LIMIT 0, 3
I really love to do it with a single query. Your help is greatly appreciated.
There is no easy way to do that. Maybe with very complex query, but it will be difficult to maintain and may be even less efficient than doing that with several simpler queries.
The solution described by you costs 1 + (number of categories) queries, not two, of course. You could union them easily, and then you would have two queries and less trips do database, but similar load for db (comparing to your solution).
Even if you would assume, that there is a way to fetch everything with single query, then db has to do almost the same work (fetch 3 newest posts from every category). So having 2 queries vs 1 hypothetical is not a big penalty in terms of performance. Moreover, I can imagine that DB engine could have some issues with finding most optimal execution plan, especially if you would add there functions etc.
And the last solution. There is a way for fetching up to 3 posts from each category, but that require modifying schema and some application-side work. You can add a boolean column "most_recent" and have always 3 posts per cat. with true and false for the rest. You would have to keep updating it every time when you are adding/deleting posts. That is achievable as well with db triggers. Then your problem is trivial to resolve, but only because you have done some precomputation.

Selecting with modified preorder tree traversal when there are multiple trees

I have implemented a Modified Preorder Tree Traversal as explained here for optimalisation reasons. My table is something like this:
+----+-----------+------+-------+
| id | parent_id | left | right |
+----+-----------+------+-------+
| 1 | NULL | 1 | 4 |
| 2 | 1 | 2 | 3 |
| 3 | NULL | 1 | 4 |
| 4 | 3 | 2 | 3 |
+----+-----------+------+-------+
I.e. there exist an arbitrary number of trees in the table. If a node's parent id is null, then that automatically means the node is the very base of its tree.
The article points out that one can select all a node's descendants very simply:
SELECT *
FROM table
WHERE
left > ?
AND
right < ?
Now my problem is that the selection is obviously going to include nodes of other trees. After all, I haven't even specified a tree to begin with. Is it possible to just select the nodes from one specified tree with this model?
Don't start new tree with left = 1. Use MAX(right) + 1 for left value. This will not affects other node manipulations, and trees will be separated by left-right range.

PHP Categories Dropdownlist

I've been trying a lot of algorithms, including functions inside functions, and so on... But I haven't been able to solve this, hope you can help me.
I have a MySQL Table with categories, each category can have a parent category. Top categories are those whose parent is 0.
id | parent | name | desc
------------------------------------------------
1 | 0 | sportwear| test
2 | 0 | bikes | test
3 | 1 | shirts | more tests
4 | 1 | shoes | ....
5 | 3 | men | ....
6 | 3 | women | .....
AND SO ON....
I would like to have a SELECT element with a list like this
CATEGORIES:
-sportwear
-----shirts
--------men
--------women
-----shoes
-bikes
-morecategories
-etc
And it should have all the categories, and show them with their proper depth.
To get them All we would use something like
SELECT * FROM categories;
To get subcategories from a specific parent we would use something like
SELECT * FROM categories WHERE parent = PARENT-ID-HERE;
How can I make a PHP algorithm to build a select list like the one shown before?

How to write a MYSQL query that will return children nested under parents?

I don't know if what I'm asking is even possible, but here's my situation. I have a table structured somewhat like this:
+--------------------------------------------------+
| id | parent_id | name | category | ....
+--------------------------------------------------+
| 0 | -1 | item0 | 1 |
| 1 | 0 | item1 | 1 |
| 2 | 0 | item2 | 1 |
| 3 | 2 | item3 | 1 |
| 4 | 2 | item4 | 1 |
| 5 | -1 | item5 | 1 |
+--------------------------------------------------+
A parent_id of -1 will mean it is a "base" item with no parent. Each item will have more columns of information. I need to somehow output all items in a category nested like the following:
item0 => item1
=> item2
=> item3
=> item4
item5
I don't know if that makes sense or not, but hopefully it does!
The only way I can think of doing this is making a query to get all of the "base" items (querying for rows with parent_id = -1) then iterate through every resulting row, querying for rows that have their parent_id equal to the current row's id, then repeating the process going deeper and deeper until there aren't any more children for a base item.
Is there a better way?
Thanks!!
It is not possible in pure SQL.
SQL is intended to work with relational data not trees (hierarchical data).
You can represent tree in an SQL schema, however you won't be a able to result in a tree as you intend to do.
The only way to do is to get an usable result by making as many join as level you're storing.
Your current schema may support multiple level, however, it will be very difficult to manage more than one or two level.
You may be interested in Nested Set Model or Managing hierarchical data in mysql
There are some implementation of the Nested Set like this one to work with Doctrine 2
This is not possible in pure SQL and it is one of the aspects of the relational model that generates most criticism.
I would recommend you to read the links on this post: SQL "tree-like" query - most parent group
And also, if your application relies too much on this, I would suggest you to take a look at some non-relational databases that can represent this kind of data way better, such as MongoDB ( www.mongodb.org )
I hope i understood well your question(it's pretty late here and i've just come from a bar), if i didnt, just correct me and i'll rewrite my answer.
From the scenario given, i guess there's another parent table, isn't there?
Lets imagine it's attributes are id and name. Children table is the given one by you (w/o unnecessary attributes).
mysql> insert into parent(name) values ('petr'),('tomas'),('richard');
mysql> insert into children(name,parent_id) values('michal',1),('tomas',1),('michal');
mysql> select parent.id,parent.name,children.name from parent left join children on parent.id = children.parent_id;
+----+---------+--------+
| id | name | name |
+----+---------+--------+
| 1 | petr | michal |
| 1 | petr | tomas |
| 2 | tomas | NULL |
| 3 | richard | michal |
+----+---------+--------+
In order to do this multiple time (parent got child who got child who got child etc..) You can accomplish that by using multiple joins.
mysql> select parent.id,parent.name as Parent,children.name as Child,children2.name as Child2 from parent left join children on parent.id = children.parent_id left join children2 on children.id = children2.parent_id;
+----+---------+--------+--------+
| id | Parent | Child | Child2 |
+----+---------+--------+--------+
| 1 | petr | michal | NULL |
| 1 | petr | tomas | dan |
| 1 | petr | tomas | pavel |
| 2 | tomas | NULL | NULL |
| 3 | richard | michal | michal |
+----+---------+--------+--------+
If i either didnt answer what you asked or you need further explanation let me know ;]
Regards,
Releis

Is there a way to get nested data out of MySQL without using recursion?

So let us say that I have a menu system with all the navigation items stored in a MySQL table like so:
Table: Menu
-------------------------------------------------------
| id | title | url | parent_id |
-------------------------------------------------------
| 1 | Home | /home | 0 |
| 2 | About | /about | 0 |
| 3 | History | /about/history | 2 |
| 4 | Location | /about/location | 2 |
| 5 | Staff | /about/staff | 2 |
| 6 | Articles | /blog | 0 |
| 7 | Archive | /blog/archive | 6 |
| 8 | Tags | /blog/tags | 6 |
| 9 | Tag Name 1 | /blog/tags/tag-name-1 | 8 |
| 10 | Tag Name 2 | /blog/tags/tag-name-2 | 8 |
-------------------------------------------------------
As you can see this table is quite simple with the only complication being the self referencing column parent_id, which defines how the menu should be nested.
So this would produce the following menu:
- Home
- About
- History
- Location
- Staff
- Articles
- Archive
- Tags
- Tag Name 1
- Tag Name 2
Is there a way to get this structure from the aforementioned table without making use of a recursive function in PHP (but it could be Python, Java or any other language) that queries the database with each iteration?
Ideally this could be handled with one MySQL query. Perhaps the table structure needs to be changed to accommodate this - if so how?
You could pull all of it out in one single pull, and then work with it recursively in PHP. That way you save some of the query time, but gain a little scripting time.
I would do something like this:
Get all data, ordered by parent id
Put row into $data[$parent_id][]
define function to build menu, takes one param which is id
get $data[$id] and work with that array, building the array.
while looping through the items, check if size of $data[current-item-id] > 0
if so, call above function with 0 as param
This way, you only query the database once, but use a little more of the servers ram.
If you're fetching the whole tree and you can't or don't want to change the table structure, take a look at https://stackoverflow.com/a/8325451/4833
This can be done in sql query, take a look at this resource which explains recursion in a query
http://www.artfulsoftware.com/mysqlbook/sampler/mysqled1ch20.html.
MySQL don't have an default function to do that.
You can make an procedure with loop to get the data result you want, or create an function and use in your sql select.
Anyway you will use loop.
Example:
DROP PROCEDURE IF EXISTS famsubtree;
DELIMITER go
CREATE PROCEDURE famsubtree( root INT )
BEGIN
DROP TABLE IF EXISTS famsubtree;
CREATE TABLE famsubtree
SELECT childID, parentID, 0 AS level
FROM familytree
WHERE parentID = root;
ALTER TABLE famsubtree ADD PRIMARY KEY(childID,parentID);
REPEAT
INSERT IGNORE INTO famsubtree
SELECT f.childID, f.parentID, s.level+1
FROM familytree AS f
JOIN famsubtree AS s ON f.parentID = s.childID;
UNTIL Row_Count() = 0 END REPEAT;
E ND ;
go
DELIMITER ;
And use to query:
call famsubtree(1); -- from the root you can see forever
SELECT Concat(Space(level),parentID) AS Parent, Group_Concat(childID ORDER BY childID) AS Child
FROM famsubtree
GROUP BY parentID;

Categories