I am not sure if this is possible in mySQL. Here are my tables:-
Categories table:
I think the cleanest way to achieve this would be to use the nested set model. It's a bit complicated to implement, but powerful to use. MySQL has a tutorial named Managing Hierarchical Data in MySQL. One of the big SQL gurus Joe Celko wrote about the same thing here. If you need even more information have a look at Troel's links on storing hierarchical data.
In my case I would stay away from using a RDBMS to store this kind of data and use a graph database instead, as the data in this case actually is a directed graph.
How big is the table Categories? You may need to cache this on the application level and construct the appropriate query: ... where id in (2, 3, 6, 7)
Also, it's best if you fetch categories by id which is their unique ID, indexed and fast as opposed to finding by name.
Bear with me, because I have never done something like this.
BEGIN
SET cat = "5";
SET temp = "";
WHILE STRCMP(temp, cat) != 0 DO
SET temp = cat;
SET cat = SELECT CONCAT_WS(GROUP_CONCAT(id), cat) FROM Categories GROUP BY (parent_id) HAVING FIND_IN_SET(parent_id, cat);
END LOOP;
END;
SELECT * FROM products WHERE FIND_IN_SET(category_id, cat)
I can almost guarantee the above won't work, but you can see what I'm trying to do. I got this far and I just decided to not finish the end of the query (select the top N from each category), sorry. :P
One way is to maintain a table that contains the ancestor to descendant relationships. You can query this particular table and get the list of all dependents.
What I've done in previous projects where I've needed to do the same thing, I added two new columns.
And then I added a trigger to the table that houses the category information to do the following (all three updates are in the same trigger)...
-- Reset all branches
UPDATE t_org_branches
SET nvc_breadcrumb = NULL,
i_depth = NULL
-- Update the root branches first
UPDATE t_org_branches
SET nvc_breadcrumb = '/',
i_depth = 0
WHERE guid_branch_parent_id IS NULL
-- Update the child branches on a loop
WHILE EXISTS (SELECT * FROM t_branches WHERE i_depth IS NULL)
UPDATE tobA
SET tobA.i_depth = tobB.i_depth + 1,
tobA.nvc_breadcrumb = tobB.nvc_breadcrumb + Ltrim(tobA.guid_branch_parent_id) + '/'
FROM t_org_branches AS tobA
INNER JOIN t_org_branches AS tobB ON (tobA.guid_branch_parent_id = tobB.guid_branch_id)
WHERE tobB.i_depth >= 0
AND tobB.nvc_breadcrumb IS NOT NULL
AND tobA.i_depth IS NULL
And then just do a join with your products table on the category ID and do a "LIKE '%/[CATEGORYID]/%' ". Keep in mind that this was done in MS SQL, but it should be easy enough to translate into a MySQL version.
It might just be compatible enough for a cut and paste (after table and column name change).
Expansion of explanation...
t_categories (as it stands now)...
Cat Parent CategoryName
1 NULL MyStore
2 1 Electronics
3 1 Clothing
4 1 Books
5 2 Televisions
6 2 Stereos
7 5 Plasma
8 5 LCD
t_categories (after modification)...
Cat Parent CategoryName Depth Breadcrumb
1 NULL MyStore NULL NULL
2 1 Electronics NULL NULL
3 1 Clothing NULL NULL
4 1 Books NULL NULL
5 2 Televisions NULL NULL
6 2 Stereos NULL NULL
7 5 Plasma NULL NULL
8 5 LCD NULL NULL
t_categories (after use of the script I gave)
Cat Parent CategoryName Depth Breadcrumb
1 NULL MyStore 0 /
2 1 Electronics 1 /1/
3 1 Clothing 1 /1/
4 1 Books 1 /1/
5 2 Televisions 2 /1/2/
6 2 Stereos 2 /1/2/
7 5 LCD 3 /1/2/5/
8 7 Samsung 4 /1/2/5/7/
t_products (as you have it now, no modifications)...
ID Cat Name
1 8 Samsung LNT5271F
2 7 LCD TV mount, up to 36"
3 7 LCD TV mount, up to 52"
4 5 HDMI Cable, 6ft
Join categories and products (where categories is C, products is P)
C.Cat Parent CategoryName Depth Breadcrumb ID p.Cat Name
1 NULL MyStore 0 / NULL NULL NULL
2 1 Electronics 1 /1/ NULL NULL NULL
3 1 Clothing 1 /1/ NULL NULL NULL
4 1 Books 1 /1/ NULL NULL NULL
5 2 Televisions 2 /1/2/ 4 5 HDMI Cable, 6ft
6 2 Stereos 2 /1/2/ NULL NULL NULL
7 5 LCD 3 /1/2/5/ 2 7 LCD TV mount, up to 36"
7 5 LCD 3 /1/2/5/ 3 7 LCD TV mount, up to 52"
8 7 Samsung 4 /1/2/5/7/ 1 8 Samsung LNT5271F
Now assuming that the products table was more complete so that there is stuff in each category and no NULLs, you could do a "Breadcrumb LIKE '%/5/%'" to get the last three items of the last table I provided. Notice that it includes the direct items and children of the category (like the Samsung tv). If you want ONLY the specific category items, just do a "c.cat = 5".
Add a column to the Categories table that will contain the complete comma-delimited tree for each group. Using your example, sub-category Educational would have this as the tree '1,2', where 1 = Toys, 2 = Educational (it includes itself). The next nested level of categories would keep adding to the tree.
To get all products in a group, you use MySQL's FIND_IN_SET function, like so
SELECT p.ID
FROM Products p INNER JOIN Categories c ON p.category_ID = c.ID
WHERE FIND_IN_SET(your_category_id, c.tree)
I wouldn't use this method for big tables, as I don't think this query can use an index.