mySQL query for selecting children - php

I am not sure if this is possible in mySQL. Here are my tables:-
Categories table:
id
name
parent_id (which points to Categories.id)
I use the above table to map all the categories and sub-categories.
Products table:
id
name
category_id
The category_id in the Products table points to the sub-category id in which it belongs.
e.g. If I have Toys > Educational > ABC where ABC is product, Toys is Category and Educational is sub Category, then ABC will have category_id as 2.
Now the problem is that I want to use a SQL query to display all the products (in all the sub-categories and their sub-categories.. n level) for a particular category.
e.g.:
select * from categories,products where category.name = 'Toys' and ....
The above query should display the products from Educational also and all other sub categories and their subcategories.
Is this possible using a mySQL query? If not what options do I have? I would like to avoid PHP recursion.
Update: Basically I want to display the top 10 products in the main category which I will be doing by adding a hits column to products table.

What I've done in previous projects where I've needed to do the same thing, I added two new columns.
i_depth: int value of how deep the category is
nvc_breadcrumb: complete path of the category in a breadcrumb type of format
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".

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.

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.

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.

Assuming MySQL, it'll be difficult to avoid recursion in PHP.
Your question is, essentially, how to mimic Oracle's CONNECT BY PRIOR syntax in MySQL. People ask this question repeatedly but it's a feature that's never made it in to MySQL and implementing is via stored procedures probably won't work because (now) stored functions cannot be recursive.
Beware of the database kludges offered so far.
The best information so far are the three links from nawroth:
Managing Hierarchical Data in MySQL, including the Nested Set Model.
Trees in SQL - nested set model by Joel Celko
Troels' links: Relational database systems: Hierarchical data in RDBMSs

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

Related

better way to handle multiple tables relationship

Suppose i have some tables like...
countries
id name
1 Cyprus
2 India
states
id name country_id
1 a 1
2 b 2
3 c 2
cities
id name state_id
1 x 1
2 y 2
3 z 3
4 p 2
pages
id name slug status
1 ab a-b 1
2 pq p-q 0
3 abc a-b-c 1
mode_of_training
id name
1 Virtual
2 Classroom
items
id name description
1 a something
2 b something
prices
id price currency_code
1 200 USD
2 300 AUD
3 4000 INR
offers
id name discount
1 xyz-1 20%
2 abc-2 30%
3 pqr-3 10%
Creating table structure in that way is correct?? so that i can reuse them
using there ids.
For example
items_relation_table
id country_id state_id city_id page_id item_id price_id offer_id status
1 0 0 0 1 1 1 0 1
2 0 0 0 1 1 1 1 0
3 1 0 1 2 2 1 2 1
4 1 0 1 3 3 2 1 1
Now i don't need to use field values of tables
If i want to change price. i'll change price in one place only
I'm saving number of bytes.
database table size is less
But my problem is
To fetch data i need to use Joins
Or Creating View is better idea
Or There is any Better way to create table structure
Your entity-tables look fine at the first glance. But I don't understand your relation table. Looks like you try to relate everything with everything, which in some cases probably doesn't make sense (do offers really belong to countries?) in in some cases it seems redundant: When a page is linked to (many) cities, there is no need to link it to the countries too, because the countries are determined by the cities.
You should add relation-tables only between those entities that really need to be in a direct many-to-many relation. And for each of those relations you need a separate table.
e.g. for a relation between pages and cities:
cities(id, name, state_id) <--> cities_pages(city_id, page_id) <--> pages(id, name, slug, status)
And yes, you have to join tables to fetch data. Thats one of the basic ideas behind relational databases. Don't be afraid of joins, if your tables are properly indexed thats not an expensive operation at all (assuming that performance is your concern). And of course you could add some views if that makes sense for your application, but that will include the same JOINS you would just abstract them behind CREATE VIEW statements.

MYSQL /PHP Find common couples in dataset of collection/groups [duplicate]

This question already has an answer here:
MYSQL/PHP find the most common item associate with a given item
(1 answer)
Closed 6 years ago.
I have a dataset of "collections" or let's call them groups or wishlists...
a collection is a list of items
collectionId | itemdId
---------------------------------
123 | 2345
123 | 3465
123 | 876
123 | 567
123 | 980
777 | 980
777 | 332
777 | 3465
777 | 876
777 | 678
777 | 567
you see item 876 and 980, are included in both collections (777 and 123) so they are a popular couple/pair
my users generate these collection, and I'm curious to extract two insights:
which are the most common items (this is easy)
which are the most common pairs/couple (or more than 2) of items (this is my question)
eg.
say many wish-lists contains iphones and a pink iphone covers
among other accessories but i want to extract in fact that iphone +
that pink iphone cover is a common recurring "couple"
all in all, basically i'm trying to do what Amazon does, if you see an iphone i want to suggest you a pink iphone cover because many other users have suggested/favorited that
Do I have to compare similarity between collection first? to see who many items they have in common? than rate the similarity with an index?
what is the best approach to this with mysql.
do i need PHP as well?
UPDATE:
in PHP I would probably do something loopy like in pseudo code
for total number of collection:
select all item from collection 1
select all item from collection 2
do array_interesct (c1,c2)
store the matching items
repeat...
select all item from collection 2
do array_interesct (c1,c3)
store the matching items
repeat...
...then elect all item from collection 2 and repeat all the iterations..
For two collection you can do a join
select a.itemID
from my_table a
join my_table b on a.itemID = b.ItemID
where a.collection = 123
and b.collection = 777
for all you can try with a cartesian product (for pair two table) .. for ( 3 ..3)
select a.itemID
from my_table a
cross join my_table b
where a.item = b.item
and a.collection <> b.collection

Display data from 3 tables

I have messed with this query for 3 days now and no matter what I cannot get the right result. I'm working on a small database for a friend of mine for his mechanic shop.
I got 3 tables car, jobs and invoices. Everything start with "cars" if you add a car you get car_id for it. Next step would be to add a job to this car. Of course job goes to jobs table and also records car_id. After the job is done you can create an invoice out of it which goes to invoices table and again records car_id.
cars jobs invoices
=======================================================
cID, make, model | jID,cID,job1 | iID,jID,cID,amount
1 audi a6 | 1 1 check | 1 1 1 99.99
2 bmw 750 | 1 2 oil | 2 2 2 56.97
3 saab 95 | 3 3 oil | 3 3 3 30.22
1 audi a6 | 4 1 oli | 4 4 1 22.33
Small draft what the tables look like. And the outcome what I'd like to get should look like this:
ID make model
--------------------------
1. audi a6
Job 1 Invoice 1
Job 4 Invoice 4
2. bmw 750
Job 2 Invoice 2
3. Saab 95
Job 3 Invoice 3
Basically when I click my client list I get a table rows with clients and underneath client I'd like to print links to jobs and invoices.
Quite long post but I hope you understand what I mean.
I've tried different JOINs, the latest that I tried was
SELECT * FROM cars
RIGHT JOIN jobs ON cars.car_id=jobs.car_id
RIGHT JOIN invoices ON cars.car_id=invoices.car_id
ORDER BY cars.car_id;
It kind a worked but it gave me separate rows for each job with same client info. May I'm printing the result wrong. I just don't know any more. Can anybody offer a solution for this? right SQL command html for printing.
Why don't you just make it in a single table with fields like this:
id car job invoices
===============+===============+==============+==============+
1 + fastCar + works + 99.99 +
+ + + +
+ + + +
With statement like this:
CREATE TABLE cars (id int(11) AUTO_INCREMENT, car text(50) default '', job text(50) default '', invoices int(5) default NULL);
Then you can get all car attributes using a query like:
SELECT job FROM cars WHERE car='fastCar';
and pass desired values with another query to a teble that contains
details for the job for example, where you can have job id as needed.

Mysql find a table in from another table record and find a specific record in this table

I'm a new in Mysql and I have a complicated problem:
I have a table with "Shops" name in this table there is a ShopID column. The records look like this:
Shop_001
Shop_002...
Every "shopID" refer to a new table with this name, for example there is a table with Shop_0001 name. In this table there is "partnumber" column which mean the parts which are available in this shop.
I send a specific part number to sql server and I want to check all shops in the "Shops" table and return a rows in the "Shop_xxxx" tables which has this specific partnumber.
Unfortunately I have no idea how do I get start on this. Can anybody help me give some instruction or anything on this?
you're looking for a many to many relationship. so you just need 3 tables
1 table is the list of shops
1 table is the list of products
and 1 table is the list of which shops have which products. like this
table1
id|shops
------
1 shop1
2 shop2
3 shop3
table2
id|products
------
1 prod1
2 prod2
3 prod3
4 prod4
5 prod5
table3
id|shop_id|prod_id
-------------------
1 2 3
2 2 1
3 2 2
4 1 3
5 1 4
6 1 5
7 3 2
So for every time a product is added to a shop, an entry is added in table3. This will allow you to query by shops or by products, and you will only ever need 3 tables.
google querying many to many relationships for how to get the list of products for shop1 or the list of shops that have product4 etc.

MySQL Query problem (duplicate results)

Im having a problem finding duplicate results in a mysql database (a cocktail recipe website). Here the setup:
Table 1: 'cocktail'
[cid,c_name] (cid = unique cocktail id, c_name = cocktail name)
Table 2: 'ingredients':
[iid,i_name] (iid = unique ingredient id, i_name = ingredient name)
Table 3: 'cocktail_ingredients' (the linking table)
[ciid,cid,iid] (ciid = unique row identifier, cid = cocktail cid, iid = ingredient iid)
So one cocktail can have multiple rows in the 'cocktail_ingredients' table (1 to many).
Setup is fine. The problem Im having now is finding if there are duplicate cocktails in my database.
For instance if the cocktail_ingredients table had these entries:
cid | iid
1 | 56
1 | 78
1 | 101
.
.
.
9 | 56
9 | 78
9 | 101
The cocktail is the same (for theoretical purposes here anyway).
If the 'cocktail_ingredients' table had one more row ...
9 | 103
Then it wouldn't be the same, as cocktail number 9 includes an extra ingredient.
So the mysql has to do 2 checks, firstly that the ingredient count is the same, and secondly that every ingredient id (iid) is the same for corresponding cocktails (cid).
Im stumped on this one, any help much appreciated. I'm thinking I might have to head down the PHP route as well to code in something more complex, but I'm struggling there as well so thought this would be a good place to stop and ask.
Thanks a ton
Nick
You may recall from a distant math class that the definition of set equality is that both A abd B are subsets of one another (non-strict) so just create a view or procedure that checks if every thin that is in A is also in B, then check the two cocktails are both subsets of one another. This is far from a complete answer, but it may be enough to get you going ;)
it will probably be easier to do the negation - find an ingredient in A that is not in B. none exist, then A must be a strict subset of B (assuming A and B can't both be empty)
Alternatively do a count of each ingredient in A, each ingredient ion B and each ingredient in A and B then if they are equal they are equivalent cocktails
CREATE VIEW ingredient_count AS
SELECT cid, count(*) as ingredients
FROM cocktail_ingredients
GROUP BY cid
CREATE VIEW shared_ingredients AS
SELECT c1.cid cid1, c2.cid cid2, count(*) as ingredients
FROM cocktail_ingredients as c1 INNER JOIN cocktail_ingredients as c2
ON (c1.cid != c2.cid AND c1.iid = c2.iid)
GROUP BY c1.cid,c2.cid
CREATE VIEW duplicates AS
SELECT cid1,cid2
FROM (ingredient_count AS ic1 INNER JOIN shared_ingredients
ON ic1.cid=cid1) INNER JOIN ingredient_count as ic2
ON ic2.cid=cid2
WHERE ic1.ingredients=ic2.ingredients
AND shared_ingredients=ic1.ingredients
Note this may be much faster in mysql with subselects with sensible where clauses rather than views, but this is easier to understand
You can impose such checking using TRIGGER.
But, yet there is a conceptual problem.
Say, you have two cocktails {1 | 56, 78, 101} and {9 | 56, 78, 101, 103} and also assume that you have implemented the check.
Now, you are inserting data for 1:
cid | iid
----------
1 | 56
Then, add rest two ingredients...
cid | iid
----------
1 | 56
1 | 78
1 | 101
Fine, now you start adding 9:
cid | iid
----------
1 | 56
1 | 78
1 | 101
9 | 56
You have three more ingredients, so continue adding them:
cid | iid
----------
1 | 56
1 | 78
1 | 101
9 | 56
9 | 78
Two more remaining (101,103)
But alas! You cannot add 101! If you try to add 101, then 9 would become identical to 1, which your trigger will prevent you from adding.
When a cocktail is subset of another, you have to add the subset later. I hope I could make you understand this.
You should not put any restriction in database. What I would do in my web application is:
In the cocktail entry/update interface, I would take user input (and not yet insert/update in DB)
When user clicks the save button (I would add a save button), check if the new/updated cocktail becomes copy of another (May be I would write a stored procedure, but it can be found using a select query only)
If the new/updated cocktail is not duplicate of another, insert/update database. If

Categories