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)
Related
I feel like I've tried every solution in here but nothing is quite right.
I have a table shows a category, subcategory, and a person's name. Each category is totalled.
Category 1
Subcategory 1
Person 1
Person 2
Subcategory 2
Person 1
TOTAL: 3
This could be repeated again underneath for Category 2 with its own total.
I want to click on that total value and pass all this information to another page. If I click the "3", it should only apply to everything this total refers to. Just Category 1. But obviously it should send both those subcategories and all those people.
if I pass the parameters from this page through to the other page it will include all categories, not just those a given total refers to. If I pass a field value through it will only send the first of each and not all. For example, sending the Subcategory field through would only send Subcategory 1 and not both Subcategory 1 and 2.
I've tried using joins on these values to pass multiple parameters through but no luck.
Is there a trick to this I'm not realising or should I do something entirely different? Thank you.
If I've understood correctly then you probably don't need to pass multiple categories.
If let's say you original query looked something like
SELECT Category, Subcategory, person, someValue FROM myTable
Then when you when you click the total value you would only pass the category (Category 1)
In your subreport, the dataset query would then be something like...
SELECT Category, Subcategory, person, someValue FROM myTable
WHERE Category =#myCategoryParameter
I know there has been a few answer to a couple of questions similar to the one am asking. But their approach didn't look convincing.
My question is how would I structure a database for a category that can have books and a subcategory which can also have books?
I have already design a database, but am not convinced by the approach. So I would really appreciate if the guys with experience gave me some advice or improvement or a completely different approach.
This how my tables look like (bare in mind this is not the actual code)
TABLE Category
ID
user_id -- Foreign key from user
name
TABLE SubCategory
ID
user_id
category_id
name
The table for the book have the same design.
There's no reason to have more than one table for "categories", whether it be a top-level category or a sub-category. They're all just "categories".
So, have a single table called "categories", with a parent_id field:
// categories table
id
name
user_id
parent_id
When you want to pull all top level categories, just run your query against the categories table with a condition that parent_id is null.
Then, when you want to pull sub categories, just run the query against the categories table with a condition that parent_id = 123 (or whatever).
Not only does this keep everything a lot cleaner, but it also allows for expansion in case you want to continue adding sub-sub-sub-sub categories...etc.
Another option is to use CakePHP's TreeBehavior.
I personally just rather use the way I suggested above, but might just be because I haven't taken the time to really understand this behavior enough.
If parent_category is null then it is a top level. If it is non-null then it is a sub category.
TABLE Category
ID
name
parent_category
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 had a question about whether or not my implementation idea is easy to work with/write queries for.
I currently have a database with multiple columns. Most of the columns are the same thing (items, but split into item 1, item 2, item 3 etc).
So I have currently in my database ID, Name, Item 1, Item 2 ..... Item 10.
I want to condense this into ID, Name, Item.
But what I want item to have is to store multiple values as different rows. I.e.
ID = One Name = Hello Item = This
That
There
Kind of like the format it looks like. Is this a good idea and how exactly would I go about doing this? I will be using no numbers in the database and all of the information will be static and will never change.
Can I do this using 1 database table (and would it be easy to match items of one ID to another ID), or would I need to create 2 tables and link them?
If so how exactly would I create 2 tables and make them relational?
Any ideas on how to implement this? Thanks!
This is a classical type of denormalized data base. Denormalization sometimes makes certain operations more efficient, but more often leads to inefficiencies. (For example, if one of your write queries was to change the name associated with an id, you would have to change many rows instead of a single one.) Denormalization should only be done for specific reasons after a fully normalized data base has been designed. In your example, a normalized data base design would be:
table_1: ID (key), Name
table_2: ID (foreign key mapped to table_1.ID), Item
You're talking about a denormalized table, which SQL databases have a difficult time dealing with. Your Item field is said to have a many-to-one relationship to the other fields. The correct things to do is to make two tables. The typical example is an album and songs. Songs have a many-to-one relationship to albums, so you could structure your ables like this:
Table Album
album_id [Primary Key]
Title
Artist
Table Song
song_id [Primary Key]
album_id [Foreign Key album.album_id]
Title
Often this example is given with a third table Artist, and you could substitute the Artist field for an artist_id field which is a Foreign Key to an Artist table's artist_id.
Of course, in reality songs, albums, and artists are more complex. One song can be on multiple albums, multiple artists can be on one album, there are multiple versions of the same song, and there are even some songs which have no album release at all.
Example:
Album
album_id Title Artist
1 White Beatles
2 Black Metallica
Song
song_id album_id Title
1 2 Enter Sandman
2 1 Back in the USSR
3 2 Sad but True
4 2 Nothing Else Matters
5 1 Helter Skelter
To query this you just do a JOIN:
SELECT * FROM Album INNER JOIN Song ON Album.album_id = Song.album_id
I don't think one table really makes sense in this case. Instead you can do:
Main Table:
ID
Name
Item Table:
ID
Item #
Item Value
Main_ID = Main Table.ID
Then when you do queries you can do a simple join
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 :-)