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/
Related
Hello people of the coding world.
I'm working on a small web development project and have an idea but am unsure whether or not it is applicable in pure MySQL or whether I will need to utilise some PHP code to make it work the way I need it to work. I'm not against using PHP, but feel that there is a better solution to splitting things.
I have three database tables. I'll call them A, B, and C. Tables B and C contain items that can have an item_parent. Any item in C is a child of at least one item in B, and any item in B is a child of at least one item in A. Each item, in all three tables, will have an item_id and item_level assigned to it. The ID is just a unique identifier, and serves no other purpose. The level is used to determine visibility to the viewer, as the viewer must have a level equal to or above the required level in order to view an item.
My query is that I want to be able to get the access level required in one query. This query would need to check all parents of items in table C and B for their access levels. If an item has multiple parents, it must check both and return the lowest of the two. If you can see at least one parent, you can see the child provided its level is not too high, but if you cannot see any parent then you cannot see the child regardless of child's level.
In order to get the multi-parenting system functional I've used the JSON data type for item_parent. It allows me to set a variable number of values without much change to the code used to assign or read those same values. item_id and item_level are of types INT and SMALLINT respectively.
Some sample data:
table_a
item_id | item_level
1 | 1
2 | 4
3 | 6
table_b
item_id | item_level | item_parent
1 | 2 | [1,2]
2 | 3 | [2]
3 | 7 | [1,3]
table_c
item_id | item_level | item_parent
1 | 0 | [3]
2 | 5 | [1]
3 | 8 | [1,2]
The query I want would need to run checks on the parents and return the lowest of those, then the highest of the returned data sets. Each item_parent refers to an item_id in the table directly above it.
The way I imagine this running (on table_c.item_id=1) is as follows:
table_c.item_id=1 parents are checked. The lowest level of all parents of an item is returned. Assuming we are checking the first item, the number returned would be 7, as we are checking table_b.item_id=3 (the only defined parent of table_c.item_id=1).
table_b.item_id=3 parents are checked, and again lowest value returned. We are only checking table_b.item_id=3 as it is a parent of table_c.item_id=1 and we are following the tree up. This item has two parents, so both are checked and the lowest level between them is returned. This would return a 1.
Now that we've done the level checking, we return the highest level of all returned results including the level of table_c.item_id=1. The numbers we are working with are 0, 1, and 7. The returned level would be 7, as it is the highest level of all those returned.
In the above situation, if your viewing level was 4 then you would be unable to see table_c.item_id=1 due to the level of table_b.item_id=3. However, if your level was 7 or above, you would be able to see it.
I've a few questions on how to make this work.
Can this be done by pure MySQL queries, or will I need to break things into multiple queries that are run separately with PHP processing the item_parent JSON data to prepare the next query? If so, what would the query be?
Is my use of JSON as the item_parent data type appropriate, or is there a data type that better for storing this information?
Apologies if I have not worded this very well. In my head, the idea is solid, but when it comes to explaining it I'm not sure what words to use or if I've conveyed my thoughts properly. Please let me know if edits are needed or if things are unclear.
Thank you in advance to those to take the time to read and work on a potential solution.
Following some reading of some comments and resources I was linked to, I determined that it would be more appropriate to rethink part of my database design as there is no pure MySQL way to do what I was hoping for with the existing structure.
Since I've rethought part of the database, this question becomes moot.
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?
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/
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.
Well , i'm going to set up my mongo db schema for a little site, what i need is to reproduce this schema into mongo db collections:
product
-> tag1
->child_tag1
->child_tag2
-> tag2
->child_tag1
->child_tag2
-> tag3
->child_tag1
->child_tag2
which is the best way to reproduce this schema with mongo ?
For sure i need to be able to change a tag or child tag without update all collection objects :)
[EDITED]
Well cause of i know my question is not so clear, i need to clarify that i'm trying converting a mysql db site onto a mongoDb site.
So i have 4 tables in mysql:
products
id(AUTO) | product_name | qty
1 biscuits 34
2 limonade 29
tags
id(AUTO) | tag_name
1 sugar
2 eggs
3 vitamine C
tags_childs
id(AUTO) | id_tag | tag_child_name
1 1 glucouse
2 2 protein
3 2 chicken
products_tags
id_product | id_tag | id_tag_child
1 1 NULL
1 1 1
1 2 3
2 3 NULL
so i use products_tags table to join over 4 tables (and this is why heating joins i'm switching to mongoDb :))
so i can reproduce this scenario with mongodb collections and objects?
thx :)
Just wanted to make sure I'm understanding what your above document shows; essentially Many Products can have Many Tags, and Tags can have children under them (Coding tag -> Php is child tag of Coding).
With that being my understanding of the above question here's how I would structure your Mongo Collections:
Products Collection:
{
"ProductName":"Sweet Jelly",
"Qty":9,
"Tags":[
ListOfTagIds
]
}
The list of TagIds would be the Unique Identifiers which mongoDB automatically assigns to each of the new fields created.. see here
From there I'd create a Tags Collection:
{
"TagName":"Coding Languages";
"ChildTags":[
$_idOfChildren
]
}
Then if your tag had a child:
{
"TagName":"PHP",
}
For this Parent / Child relationship you can read a bit more about it from this excellent mongo resource located here: Mongo Book - Page 16 starting with No Joins.
Good Luck!