Efficient implementation of "category" tree? (MySQL) - php

If I have a category tree where there is an undetermined level of subcategories, is it possible to get the entire tree of categories in one query (without any loops)?
This is a possible scenario:
category_id parent_id Name
0 null Home
1 null Auto
2 0 Living Room
3 2 Couches
4 2 TV
5 4 Home Theatre
6 5 Cables
If you notice, Cables lineage is such that:
Home > Living Room > TV > Home Theatre > Cables
Right now, I get the parent category of the current category and keep looping until I get to the parent category that has a null parent category. I know this part has been asked before. However, I'm wondering is there a better way to structure this table so that I don't have to run X amount of queries (or subqueries)? I considered for instance adding a 4th column called parents or something that would have the entire lineage in a comma separated list (such that in this case parents would be: 5,4,2,0. However this seemed tacky and inefficient (and easy to break). Is there a better mechanism available?

Related

ExpressionEngine 3 - parent/child category set up using 2 separate category groups

I have an ExpressionEngine 3 site. I have 2 separate Category groups:
Course Type (ID = 3)
Course Category (ID = 5)
The hierarchy I want is Course Type > Course Category. For example:
E-Learning (Course Type)
Health and safety (Course Category)
Risk management (Course Category)
etc...
Classroom courses (Course Type)
Hazards (Course Category)
Budgets (Course Category)
etc...
I have a channel called training_courses which has various channel entries assigned to both Course Type and Course Category.
My template has tabs for the Course Type, e.g.
| E-Learning | Classroom courses | ...
What I want to do is show the entries in training_courses that are categorised under both the appropriate Course Type ->Course Category headings. And if there are no entries hide the Course Category heading.
I'm not sure how to achieve this as it seems to rely on multiple queries to exp:channel:categories
I have this...
{exp:channel:categories style="linear" channel="training_courses" show_empty="no"}
... but the problem with that query is that it doesn't differentiate between Course Type and Course Category so it shows a full list, e.g.
E-Learning
Health and safety
Risk management
Classroom courses
Hazards
Budgets
How do I distinguish between these 2 types of category and then pull the channel entries that are under them? Should this just be set up as 1 category with parent/child hierarchy?
The answer to this seems to be that you need to use one category with a parent/child set up.
If anyone has any advice on how to accomplish it with 2 separate ones let me know.
EE is extraordinarily awful when it comes to outputting and filtering categories. From their own documentation
If you specify a child category be shown, you must also include its parent category to be shown.
Well, that's great. But what if you want to show the child categories and not the parent ones? slow clap

Pagination with unlimited nesting

I have a mySQL table which stores categories and subcategories nested to unlimited level. The table structure is :
cat_id, parent_id, cat_name
1 0 Ingredients
2 1 Veg
3 1 Non-veg
4 3 Egg
5 2 Potatoes
I want to show the complete list of these items in a PHP page in a tree view. So first the items that have parent id "0" will be shown and if they have child items the child elements will be shown with recursion to unlimited level. The output will be something like the following but in table format :
id title
1 Ingredients
2 Veg
5 Potatoes
3 Non-veg
4 Egg
I am able to achieve this with the help of multiple queries. First I get all the records with parent id = 0 and then I loop through all the items and inside the loop I check if there are child records present show them (recursively).
But now I need to add pagination and show only 10 records per page.
Is there a way to achieve pagination and dynamically show 10 records per page including the child and child-child records ?
How will I calculate the total number of pages and get the records ? I will also have to add filters later in these type of tables.
You mean, only 10 records of the root items, the ones that have cat_id of 0? For your first query, just do SELECT * FROM cats WHERE parent_id = 0 LIMIT 10. Then your subsequent queries will only find the descendants of that first 10.
For total number of pages (sets of 10), do SELECT COUNT(*) / 10 AS number_of_pages FROM cats WHERE parent_id = 0.
there is a fast way to get all results in an html table, and then apply jquery pagination like :
$(document).ready(function() {
$('#mytable').DataTable();
} );
site source : https://www.datatables.net/examples/basic_init/zero_configuration.html
by this the client will wait only the first time, and after this, click next is directly a client event not necessary to disturb the server.
also you php and html will be clean with a lot of functions like :
sorting, filters, search, ...
demo in jsfiddle : http://jsfiddle.net/0wvtpzc8/

Getting several levels of child items from the DB?

My terminology is somewhat lacking, so the title for my question is undoubtedly kind of lame, but I will explain what I mean below.
I have a MySQL table that looks something like the following:
categories:
category_id | parent_id
0 0
1 0
2 1
3 1
4 3
Now, what I want to do is output the category structure like this:
category structure:
0
1 -> 2
3 -> 4
In addition to needing to be able to display the category structure, if a category is selected then I want to find all of the articles in that category and in the subcategories (articles would be another table where each article would have a parent_category_id liking it to the category it's in).
The only way I can think of doing this is:
Get all categories with parent_id equal to the id of the category being viewed
Loop through all of the results and repeat step one
Just keep doing that until all of the results have been checked
Is there a better way to do this?
one way to do it in an efficient way is using nested sets.
it is a little bit tricky, and its a bit more complicated to update.
It works like that:
every node has 2 id's, and a level. all child nodes ids ar between the nodes ids.
example:
category_id | parent_id | low_id | high_id
0 0 1 2
1 0 3 10
2 1 4 5
3 1 6 9
4 3 7 8
now you can say "give me all child nodes of category 1":
select *
from categories
where low_id between 3 /* (low_id node1) */ and 10 /* (high_id node 1) */
but if you have to insert an node, you need an algorithm to move the other nodes in right position.
also it is good to store the level of the nodes, so you don't have to look for the id/parent_id relationship, you only have to sort by low_id and use the level as indicator.
there is a doctrine2 plugin to handle nested sets if you use php as programming language.
edit: i think this is a good point to start: http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/

How to get a multidemensional array from the database?

I know the title may not be exactly what this is about but bear with me.
I don't know how another title for this.
Well look this is my situation.
I'm building a little cms system (for myself and to learn from it). I want the pages inside the CMS to be listed and ordered by categories.
It will look something like this:
Webpages
- Home
-- homepage(this is the web page itself)
- News
-- Latest news
-- Archive
this system would mean I will have sub-categories.
In the database I have made a table:
| ID | Parent_ID | Name | Lable | Order|
1 1 Webpages webpages 1
2 1 Home home 1
3 1 News news 2
As you can see here the main category is the Webpages category and Home and News are sub-categories of it.
And those 2 categories are ordered so the Home category is first then the News second.
The problem I'm facing is this:
If i want to get all the sub-categories means i need to start with the main category Webpages and with that ID i can get the sub-categories of the main category.
I think you can see how deep this can go en that would mean (I think) that there will eventually be many query's that will be run for each sub-category.
So my question is:
Is there a way to get all the sub-categories at once in the correct order in one ore 2 query's.
if you have an answer, please let me know.
thnks
When writing the query you will need to use the ORDER BY clause. For this specific example you will need to ORDER BY Parent_ID, Order. This will return sorted by Parent_ID and then sub sorted by Order.
You can then use mysql_fetch_assoc() for parse the result set into an associative array.
Since you are going to have multiple levels of hierarchy you will have to loop through the result set once store it in a associative array consisting of the hierarchy structure.
This array can then be used to display the appropriate navigation hierarchy on the page.

How to tie products to categories?

The first time I tried to do this, I created a field in the category table called query. That contained strings like:
brand = "Burberry" AND type != "Watch"
Which I then inserted into the WHERE clause of a query to find a category's products.
That probably wasn't the best design.
My second attempt was to use a tagging system. I would create a tag table with tags like Burberry and Watch. I had a table tying the tags to the products (HABTM). I also had a table tying the tags to the categories.
The table tying tags to categories had an extra field called include which if it was a 1 then all products selected must also have that tag. Or if it was a 0 then all products selected must NOT have that tag.
This seemed to be a better design then my original, but it required some pretty complex joins.
Now I need to approach this problem once again.
One difference is I am now using the CakePHP (1.3) framework.
Before I try reinventing the wheel again. I was wondering if there are any known patterns/solutions I could use?
Probably you've already done that somehow by now, but here are my 2cents:
I'd drop Categories<->Tags, because I feel that you're unnecessarily duplicating data with it.
I.e. tables should be just categories, categories_products, products, products_tags and tags.
This way:
you wouldn't have to bother about changing category tags when products are added or removed from category
your searches would become more uniform (since there's only one tagging table)
and your tags still would be no more than 3 JOINS away - which is quite comfortable :)
From what I can understand you should have 5 tables:
Categories
Products
Tags
Categories_Tags
Products_Tags
UPDATE: When the user defines what should be selected, the HABTM tables are updated so that the tags/categories link to the products they should be linked to only.
So the query will look something like:
SELECT * FROM products WHERE ID in (SELECT product_id from tag list to include) AND ID NOT IN (select product_id FROM tag list to NOT include)
Maybe I'm missing what you're trying to accomplish here, but this sounds like you're making it more complicated than it needs to be.
Create three tables: Product, Category, and ProductCategory. Product and Category each have an id. Then ProductCategory includes ProductId / CategoryId pairs.
Like:
Product
ProductId Name
1 Lamp
2 Carpet
3 Drill
4 Power cord
5 3/8" bolt
Category
CategoryId Name
1 Electrical
2 Home decor
3 Hardware
ProductCategory
ProductId CategoryId
1 1
1 2
2 2
3 1
3 3
4 1
5 3
Then if you want, e.g., to know all the "Hardware" items:
select product.*
from category
join productcategory using (categoryid)
join product using (productid)
where category.name='Hardware'

Categories