Let's say I have a categories table that stores categories. It is implemented in a nested set style(with left and right values).
category_id lft rgt
1 1 6
2 2 5
3 3 4
So category 1 is a parent of category 2. category 2 is a parent of category 3. So its essentially one path from root to leaf.
The category fields of category 1 should be inherited by category 2 which in turn would be inherited by category 3
Now what is the best way to store the fields for a specific category? My solution was to make another table which has the category id foreign key and the fieldname.
category_id fieldname
1 field1
1 field2
2 field3
3 field4
My problem with this approach is that when getting the fields of category 3, I need to get its parent, its parent's parent and so on until I get to the root node so that I can inherit their fields. It's not really a bad solution but I wonder if this would work when the category table is very large.
So the problem is basically an optimization problem. Is this an optimal solution?
You can do this using the schema that you have, but joining the two tables together. The beauty of the left/right nest structure is that in one query you can pull out lots of information about the whole hierarchy.
In your instance, you want to pull out all the category IDs with a 'lft' equal to or less than the 'lft' value for your given level of hierarchy, and join the results against the category ID fields in your fields table.
The query is something like:-
select table2.fieldname from table2 left join table1 on table1.category_id = table2.category_id where table1.lft <= [lft value for given level of hierarchy]
If you only have the category ID to go on then you can also extract the lft value using a subselect or joining the table back on itself.
i simply save such data in a table which has following schema:
CategoryID
ParentCategoryID
Path
so you could have
1
0
1\
then
2
1
1\2
then
3
2
1\2\3
you can then fire a simple query to either get the immediate parentid or get all the hierarchy right from the root to leaf using the path field
different but simple approach which has been working for me since last 4+ years without any issues :-)
Related
I have a table (models) that I want to loop through. It have a column called category, linked to another table (categories).
id name parent
1 Audio NULL
2 Video NULL
3 Monitor 2
4 Monitor 1
5 Light NULL
6 HD 3
7 4K 4
with the sql below I get the name of the level but I would like to get the whole path to be able to sort based on the full category path. For example id 7 now gives me "4K" but I would like to get "Video / Monitor / 4K", id 4-> "Audio / Monitor". As you see there can be sub categories with the same name.
SELECT
m.id AS Id,
c.name AS Category,
m.model AS Model
FROM models m
LEFT JOIN categories c ON m.category=c.id
GROUP BY m.id
ORDER BY Category
For the moment I have a php loop that starts with the id in the models table, then checks if the parent is NULL, makes a new sql and so on until parent is NULL. There must be a better way to do it?
UPDATE: The server is running MySQL 5.6 and it can not be updated for now
UPDATE 2: I saw the suggested duplicate before but in my case I know the id of the child and I want to find all parents.
A table has column as category_NAME/ID where we can pass either id and create another table for list of that category or directly add the category name.
Which one is fater
CASE 1:
TABLE1
ID | CATEGORY_NAME
CASE 2
here to fetch list we have make one JOIN statement
TABLE1
ID | CATEGORY_ID
CATEGORY_TABLE
ID | CATEGORY NAME
What is the difference between
CASE 1:
TABLE1
ID | CATEGORY_NAME
CASE 2
here to fetch list we have make one JOIN statement
TABLE1
ID | CATEGORY_ID
CATEGORY_TABLE
ID | CATEGORY NAME
I suppose what you mean by first case is Item_id.
In that case it depends on code maintainability and storage. As if category name is changed, you have to update the record in your large table,that will be heavy query.
Also as you are storing category_id instead of its name,it will help in saving storage space.As you are not replicating category name for each record but just refering to category_id.
But clearly it depends on the size of category and size of items.
If TABLE1 size is rather small (let's say less than 10k rows at max), indexed VARCHAR CATEGORY_NAME should work OK, otherwise better to use a separate table for storing categories.
Personally, I would go with option 2. Performance improvement here will not be so dramatically, but storing dictionary field inside another table is a bad practice.
Let's say we have a table with these records of tags:
Category ID
apples 1
orange 2
And then we have another table with a row
Data catID
... 1
With this setup we can retrieve this row only in apples page, what is the proper way to assign both apples & orange to that row? Would I need to change catID field from integer to varchar and just add the second id so the value will be 1,2 and then edit the query to something like:
select * from table where catID LIKE '%1%'
select * from table where catID LIKE '%2%'
instead of
select * from table where catID='1'
select * from table where catID='2'
I'm not sure if this is the proper way? Could someone tell how you do it? Basically, I don't want to duplicate the whole row, just to add another id to it.
As others have already suggested, many-to-many relationship is represented in the physical model by a junction table. I'll do the leg work and illustrate that for you:
The CATEGORY_ITEM is the junction table. It has a composite PK consisting of FKs migrated from the other two tables. Example data...
CATEGORY:
CATEGORY_ID CATEGORY
----------- --------
1 Apple
2 Orange
ITEM:
ITEM_ID NAME
------- ----
1 Foo
2 Bar
CATEGORY_ITEM:
CATEGORY_ID ITEM_ID
----------- -------
1 1
2 1
1 2
The above means: "Foo is both Apple and Orange, Bar is only Apple".
The PK ensures any given combination of category and item cannot exist more than once. The category is either connected to the item of isn't - it cannot be connected multiple times.
Since you primarily want to search for items of given category, the order of fields in the PK is {CATEGORY_ID, ITEM_ID} so the underlying index can satisfy that query. The exact explanation why is beyond this scope - if you are interested I warmly recommend reading Use The Index, Luke!.
And since InnoDB uses clustering, this will also store items belonging to the same category physically close together, which may be rather beneficial for I/O of the query above.
(If you wanted to query for categories of the given item, you'd need to flip the order of fields in the index.)
Have you realized that two ids indexing one row is a typical application of bidirectional relationship management in a real project? We need a smarter solution in DB rather than the two rows/junction table solution. In MongoDB, you could make "low_id:hight_id" as field "_id" and field "uids_low_high", and indexing the "uids_low_high" for "$in:[$id]" search.
I have a categories table with id, parent and name fields. The parent field allows a category to be a subcategory of another category.
Example categories table where there are two main categories (WIDGETS and THINGAMABOBS), and WIDGETS have 3 subcategories:
id 1, parent null, name "WIDGETS"
id 2, parent 1, name "GADGETS"
id 3, parent 1, name "DOOHICKEYS"
id 4, parent 1, name "GIZMOS"
id 5, parent null, name "THINGAMABOBS
I have a products table with category field
Example products record where product is linked to the "GIZMOS" category:
id 1, category 4, name Contraption 5000
I want to be able to supply a category name in a SELECT statement and get back all products that are in that category. But not only do I want to find the above record on "GIZMOS", but I also want to be able to find it by the name "WIDGET", because MEDIUM WIDGET is a child of WIDGET. This should work across an unlimted number of levels (ie: sub-sub-sub categories)
To make this even more complicated, I want to be able to assign a product to more than one category. Perhaps they would be separated by commas? i.e.: If I wanted the Contraption 5000 to exist in the Doohickeys and Thingamabobs categories, I would put 3,5 in the category field.
Is what I'm asking possible with a single select statement?
I'm going to start at the end of your question:
To make this even more complicated, I want to be able to assign a product to more than one category. Perhaps they would be separated by commas? i.e.: If I wanted the Contraption 5000 to exist in the Doohickeys and Thingamabobs categories, I would put 3,5 in the category field.
By doing this you are creating a many-to-many relationship, in which case you'll need a third table called products_categories that holds two columns: product_id and category_id; you'd remove the category column from your products table.
If you wanted a product with id=1 to belong to categories 3 and 5, you'd create two rows in products_categories:
product_id | category_id
------------------------
1 | 3
1 | 5
Now to the first part of your question...
What you'd need to do is create a recursive query, which I know can be done in SQL Server but honestly I'm not sure can be done in MySQL. If it can be, I'm sure someone else will come up with an appropriate answer for you.
Do this in your application code! It will be much simpler and easier to maintain.
See also this similar post (actually there are many posts on this topic)
I'am using a simple newsletter-script where different categories for one user are possible. But I want to get the different categories in one row like 1,2,3
The tables:
newsletter_emails
id email category
1 test#test.com 1
2 test#test.com 2
newsletter_categories
id name
1 firstcategory
2 secondcategory
But what Iam looking for is like this:
newsletter_emails
user_id email category
1 test#test.com 1,2
2 person#person.com 1
what's the best solution for this?
PS: The User can select his own Categorys at the profile page. (maybe with Mysql Update?)
SQL and the relational data model aren't exactly made for this kind of thing. You can do either of the following:
use a simple SELECT query on the first table, then in your consuming code, iterate over the result, fetching the corresponding rows from the second table and combining them into a string (how you'd do this exactly depends on the language you're using)
use a JOIN on both tables, iterate over the result set and accumulate values from table 2 as long as the ID from table 1 remains the same. This is harder to code than the first solution, and the result set you're pulling from the DB is larger, but you'll get away with just one query.
use DBMS-specific extensions to the SQL standard (e.g. GROUP_CONCAT) to achieve this. You'll get exactly what you asked for, but your SQL queries won't be as portable.
This is a many-to-many relationship case. Instead of having comma separated category ids make an associative table between newsletter_emails and newsletter_categories like user_category having the following schema:
user_id category
1 1
1 2
2 1
This way you won't have to do string processing if a user unsubscribes from a category. You will just have to remove the row from the user_category table.
Try this (completely untested):
SELECT id AS user_id, email, GROUP_CONCAT(category) AS category FROM newsletter_emails GROUP BY email ORDER BY user_id ASC;