Managing Hierarchical Data in MySQL using The Adjacency List Model - php

I would like to retrieve all categories with parent in order to create a breadcrumb path. for that reason I create the following schema:
CREATE TABLE category(
category_id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
parent INT DEFAULT NULL
);
INSERT INTO category VALUES(1,'ELECTRONICS',NULL),(2,'TELEVISIONS',1),(3,'TUBE',2),
(4,'LCD',2),(5,'PLASMA',2),(6,'PORTABLE ELECTRONICS',1),(7,'MP3 PLAYERS',6),(8,'FLASH',7),
(9,'CD PLAYERS',6),(10,'2 WAY RADIOS',6);
SELECT * FROM category ORDER BY category_id;
+-------------+----------------------+--------+
| category_id | name | parent |
+-------------+----------------------+--------+
| 1 | ELECTRONICS | NULL |
| 2 | TELEVISIONS | 1 |
| 3 | TUBE | 2 |
| 4 | LCD | 2 |
| 5 | PLASMA | 2 |
| 6 | PORTABLE ELECTRONICS | 1 |
| 7 | MP3 PLAYERS | 6 |
| 8 | FLASH | 7 |
| 9 | CD PLAYERS | 6 |
| 10 | 2 WAY RADIOS | 6 |
+-------------+----------------------+--------+
10 rows in set (0.00 sec)
With the example I follow I can retrieve the information using the following SQL:
SELECT t1.name AS lev1, t2.name as lev2, t3.name as lev3, t4.name as lev4
FROM category AS t1
LEFT JOIN category AS t2 ON t2.parent = t1.category_id
LEFT JOIN category AS t3 ON t3.parent = t2.category_id
LEFT JOIN category AS t4 ON t4.parent = t3.category_id
WHERE t1.name = 'ELECTRONICS';
The problem I found is before being able to see the full path of a category we have to know the level at which it resides.
my question, is there a way to retrieve the information as the following example but not defining the exact amount of levels?
+-------------+----------------------+-------------+-------+
| lev1 | lev2 | lev3 | lev4 |
+-------------+----------------------+-------------+-------+
| ELECTRONICS | PORTABLE ELECTRONICS | MP3 PLAYERS | FLASH |
my idea is to retrieve lev1 or lev2 etc taking into consideration the depth level.
UPDATE:
EXAMPLE OUTPUT
ELECTRONICS
ELECTRONICS / TELEVISIONS
ELECTRONICS / TELEVISIONS / TUBE
ELECTRONICS / TELEVISIONS / LCD
ETC
ETC

There is a workaround.
You can add collateral PATH column. The column should have chain of ids from the elemetn to parent. Thus for the root the column empty. All the children of root has _
+-------------+----------------------+--------+--------+
| category_id | name | parent | path |
+-------------+----------------------+--------+--------+
| 1 | ELECTRONICS | NULL | |
| 2 | TELEVISIONS | 1 |1_ |
| 3 | TUBE | 2 |1_2_ |
| 4 | LCD | 2 |1_2_ |
| 5 | PLASMA | 2 |1_2_ |
| 6 | PORTABLE ELECTRONICS | 1 |1_ |
| 7 | MP3 PLAYERS | 6 |1_6_ |
| 8 | FLASH | 7 |1_6_7_ |
| 9 | CD PLAYERS | 6 |1_6_ |
| 10 | 2 WAY RADIOS | 6 |1_6_ |
+-------------+----------------------+--------+--------+
On insert a new node you just copy parent node path and add '_'
So to retrieve all children of a node you just use
SELECT *
FROM THE_TABLE
WHERE PATH LIKE '<parent node path>%'
There is a restriction of the field size and amount of levels though

Related

mysql optimisation number of ads in a category

I have a personal ads website .
First problem: I have 2 tables, category and ads.
Category table (id is the primary key)
| id | type | subtype | pos |
+-----+-------------+-----------+--------+
| 1 | sell | | 1 |
| 2 | jobs | | 2 |
| 3 | dating | | 3 |
| 4 | dating | boys | 1 |
| 5 | dating | girls | 2 |
| 6 | sell | cars | 1 |
| 7 | jobs | teacher | 1 |
Ads table (id is the primary key) - this table has a lot of ads
| id | title | type | subtype|
+-----+-------------+-----------+--------+
| 1 | some text | sell | cars |
| 2 | some text | dating | girls |
| 3 | some text | dating | boys |
I want to read all category types and get the total number of ads for that type, and I need to get the subtypes too.
The output on my page will be something
Sell (1 ads)
Cars
Jobs (0 ads)
Teacher
Dating (2 ads)
Boys
Girls
What I am currently doing is making 3 queries using php and mysql, the first of which returns the categories type is a select where subtype="" , the second counts existing ads in those categories and the third gives me subtype for each type, I want to optimize mysql, reduce the number of queries and be faster even if it means changing the tables and adding more indexes
Select query:
select *
from category
where subtype = ''
order by pos desc
Count numbers of ads on a type:
select type
from ads
where type = '$type_name'
Select subtype:
select *
from category
where type = '$type_name'
and subtype !=''
order by pos
You can use this
SELECT c.*,a.c_ads
FROM
category c LEFT JOIN ( SELECT COUNt(type) c_ads,type FROM ads GROUP BY type) a
ON c.type = a.type
ORDER BY c.type,c.pos DESC
id | type | subtype | pos | c_ads
-: | :----- | :------ | --: | ----:
3 | dating | | 3 | 2
5 | dating | girls | 2 | 2
4 | dating | boys | 1 | 2
2 | jobs | | 2 | null
5 | jobs | teacher | 1 | null
1 | sell | | 1 | 1
5 | sell | cars | 1 | 1
see fiddle
When you get the data in php check row by row, if the type changes and then you can can use the column c_ads to Display ($row{'c_ads'] ads) and row['type'] to display the text of the category
Where row['type'] doesn't change you find the subcategories to row['type'] in row['subtype ']

mysql Query to avoid duplicate results

I have multiple tables as follows:
TABLE 1: Product
+-----+----------+--------+---------------------+
| id | biz_id | name | message |
+-----+----------+--------+---------------------+
| 1 | 1 | test1 | One tow three |
| 2 | 1 | test1 | One tow three |
| 3 | 1 | test1 | One tow three |
| 4 | 2 | test2 | hello world |
| 5 | 2 | test2 | hello world |
+-----+----------+--------+---------------------+
TABLE 2: Images
+-----+----------+--------------+-------------------+
| id | biz_id | product_id | path |
+-----+----------+--------------+-------------------+
| 1 | 1 | 1 | img/qwert1.jpg |
| 2 | 1 | 2 | img/qwert2.jpg |
| 3 | 1 | 3 | img/qwert3.jpg |
| 4 | 2 | 4 | img/qwery4.jpg |
| 5 | 2 | 5 | img/qwert5.jpg |
+-----+----------+--------------+-------------------+
How can I avoid duplicate in mysql while joining multiple tables?
My Query is Join both tables such that I want to avoid duplicate product(Get Distint product by name) and get all images associated with that product(Eg. Product>name - test1 has images qwert1.jpg, qwert2.jpg, qwert2.jpg )
SELECT p.name, GROUP_CONCAT(i.path)
FROM product AS p
INNER JOIN images AS i ON p.id = i.product_id
GROUP BY p.name;
Please note that this is not standard SQL, but most DBMS offer this functionality (although the keyword may be a bit different).
simple join should do all things
select p.name as Name, i.message as IMG from Images
left join Product p
on i.biz_id=p.biz_id
group by p.name;
use left join with GROUP_CONCAT or group by
select p.name name,GROUP_CONCAT(i.path) img
from Images i left join Product p
on i.biz_id=p.biz_id
group by i.product_id

How can I create this mysql "SELECT" query?

I have two mysql tables named 'categories' and 'products'.
These are data of that tables.
mysql> select * from categories;
+-------------+--------+----------------------+-------------+
| category_id | parent | name | description |
+-------------+--------+----------------------+-------------+
| 1 | NULL | Products | NULL |
| 2 | 1 | Computers | NULL |
| 3 | 2 | Laptops | NULL |
| 4 | 2 | Desktop Computers | NULL |
| 5 | 2 | Tab PCs | NULL |
| 6 | 2 | CRT Monitors | NULL |
| 7 | 2 | LCD Monitors | NULL |
| 8 | 2 | LED Monitors | NULL |
| 9 | 1 | Mobile Phones | NULL |
| 10 | 9 | LG Phone | NULL |
| 11 | 9 | Anroid Phone | NULL |
| 12 | 9 | Windows Mobile | NULL |
| 13 | 9 | iPad | NULL |
| 14 | 9 | Samsung Galaxy | NULL |
| 15 | 1 | Digital Cameras | NULL |
| 16 | 1 | Printers and Toners | NULL |
| 22 | 1 | Computer Accessaries | NULL |
| 23 | 22 | USB Cables | NULL |
| 24 | 22 | Network Cables | NULL |
+-------------+--------+----------------------+-------------+
24 rows in set (0.00 sec)
mysql> select product_id, category_id from products;
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 24 |
| 2 | 6 |
| 3 | 6 |
| 4 | 6 |
+------------+-------------+
4 rows in set (0.05 sec)
Now I need to create a select query to get products to each category. I have already have category_id.
This is how I tried it:
SELECT * FROM products
WHERE category_id = 6
ORDER BY added_date DESC
My question is when I creating this select query I need all the products if one category have its subcategories. That mean, if category_id is 2, then I need to get all the products including its sub categories.
Can anybody tell me how I create this select query?
Thank you.
SELECT *
FROM products
WHERE category_id = 2 OR
category_id IN (SELECT category_id
FROM categories
WHERE parent = 2)
ORDER BY added_date DESC
try this.
select a.*,b.* from categories a inner join products b on a.category_id = b.category_id where a.category_id='$Category_id' or a.parent_id='$category_id'
You can do it using a sub-query to get category_id values whose parent is 2:
SELECT *
FROM products
WHERE category_id = 2 OR
category_id IN (SELECT category_id
FROM categories
WHERE parent = 2)
ORDER BY added_date DESC
All of the above answers will work but only for one level deep of categories. If you need sub-sub-categories then I'd suggest using something called MPTT. This will require 2 new fields in your categories database table though (typically called left and right).

Basic SQL query design issue

I'm trying to create a query that would basically be the equivelant of this (does not work).
SELECT * FROM `categories` AS C AND
SELECT * FROM `items` AS I AND
SELECT COUNT(I.id) AS items AND
SELECT SUM(I.price) AS price;
I am not using SQLServer, and I'm using PDO through PHP for database connectivity.
Here's the tables.
Category
+----+------------+
| id | Category |
+----+------------+
| 1 | First_Cat |
| 2 | Second_Cat |
+----+------------+
items
+----+----------+------+-------+
| id | category | name | price |
+----+----------+------+-------+
| 1 | 1 | Foo | 1.99 |
| 2 | 1 | Bar | 2.00 |
| 3 | 2 | ooF | 0.99 |
| 4 | 2 | raB | 1.99 |
+----+----------+------+-------+
Based on these tables I would be expecting these query results:
+----+------------+-------+-------+--+
| id | category | items | price | |
+----+------------+-------+-------+--+
| 1 | First_Cat | 2 | 3.99 | |
| 2 | Second_Cat | 2 | 2.98 | |
+----+------------+-------+-------+--+
Any help?
The query that you have posted in the comment you are not joining the category table and the item table. That makes me think that you could do something like this?:
SELECT
categories.id,
categories.Category,
COUNT(*) AS items,
SUM(items.price) as price
FROM
`categories`
JOIN items
ON categories.id = items.category
GROUP BY
categories.id,
categories.Category

Matching column names in DB using JOIN NULL values

I am trying to create an "eBay" style category menu where when the user selects an item from a multiselect field it will add a new multiselect with any sub categories and if the sub category contains sub categories add a new multi select with the sub sub categories.
I have this mostly working but I am having trouble with the database side of things. I have a main table called categories that contains two columns CatID and Category. There are three rows in the categories table "Motorcycles", "Powersports" and "Parts & Accessories"
When a user selects Motorcycles it should create a new multi select and bring up Motorcycle Brands. If on the other hand the user selects Powersports it should create a new multi dropdown and list Powersport vehicle types "Dirtbike", "PWC", "Snowmobile" etc. Which upon selecting one of those will bring up a Powersports->Vehicle Type->Brand multiselect.
The problem is I don't know how to reference all of this information properly in the database.
For example:
Main Categories(categories)
+-------+---------------------+
| CatID | Category |
+-------+---------------------+
| 1 | Motorcycles |
| 3 | Parts & Accessories |
| 2 | Powersports |
+-------+---------------------+
Motorcycles Sub Category: (motorcycle_brands)
+-------+-------+-------------------------+
| CatID | SubID | SubName |
+-------+-------+-------------------------+
| 1 | 1 | American Classic Motors |
| 1 | 2 | American Ironhorse |
| 1 | 3 | Aprilia |
| 1 | 4 | Benelli |
| 1 | 5 | Big Dog |
+-------+-------+-------------------------+
Power Sports Sub Category: (powersport_categories)
+-------+-------+--------------------------------+----+
| CatID | SubID | SubName | ID |
+-------+-------+--------------------------------+----+
| 2 | 1 | ATVs | 1 |
| 2 | 2 | Dune Buggies / Sand Rails | 2 |
| 2 | 3 | Go Karts: High-Performance | 3 |
| 2 | 4 | Personal Watercraft | 4 |
| 2 | 5 | Powersport Vehicles Under 50cc | 5 |
+-------+-------+--------------------------------+----+
So if I run the following command:
SELECT * FROM categories C
LEFT JOIN motorcycle_brands MB ON MB.CatID = C.CatID
LEFT JOIN powersport_categories PC ON PC.CatID = C.CatID
WHERE C.CatID = 1 LIMIT 5;
I get this:
+-------+-------------+-------------------+-------+-------+-------------------------+-------+-------+---------+------+
| CatID | Category | CatDBTable | CatID | SubID | SubName | CatID | SubID | SubName | ID |
+-------+-------------+-------------------+-------+-------+-------------------------+-------+-------+---------+------+
| 1 | Motorcycles | motorcycle_brands | 1 | 1 | American Classic Motors | NULL | NULL | NULL | NULL |
| 1 | Motorcycles | motorcycle_brands | 1 | 2 | American Ironhorse | NULL | NULL | NULL | NULL |
| 1 | Motorcycles | motorcycle_brands | 1 | 3 | Aprilia | NULL | NULL | NULL | NULL |
| 1 | Motorcycles | motorcycle_brands | 1 | 4 | Benelli | NULL | NULL | NULL | NULL |
| 1 | Motorcycles | motorcycle_brands | 1 | 5 | Big Dog | NULL | NULL | NULL | NULL |
+-------+-------------+-------------------+-------+-------+-------------------------+-------+-------+---------+------+
Notice all the NULL values at the end. Is there a way to get rid of these null values? It is causing other problems in my code because when it returns this array back to my PHP script the SubID and SubName array fields are blank since it goes with the last set which are NULL. However if I run the same query as above replacing 1 with 2 then I get the values I want back since they are not over written by duplicate column names.
Maybe I am going about this all wrong I was planning on having the same SubID and SubName columns in all of my tables for sub categories but it looks like it may cause me grief. The reason I did this is because otherwise I was having to write a separate query depending on what value was selected I wanted to have basically one query that would do it all essentially for each sub option. Is there any improvements here?
1) Try something like this (you should also check my other point below):
SELECT C.CatID, C.Category, C.CatDBTable, MB.SubID AS MbSubID, MB.SubName AS MbSubName, PC.SubID AS PcSubID, PC.SubName AS PcSubName, PC.ID AS PcID FROM categories C
LEFT JOIN motorcycle_brands MB ON MB.CatID = C.CatID
LEFT JOIN powersport_categories PC ON PC.CatID = C.CatID
WHERE C.CatID = 1 LIMIT 5;
2) Your database structure might be made better and more extensible (if you needed for exemple to add a 4th degree sub) if you used the following approach :
Have only one table name categories which has the following columns :
ID
parent_id (nullable)
depth
name
you will use it as follows:
"Motorcycles" which is a main category will have: (ID => 1, parent_id => null, depth => 0, name => Motorcycles)
"American Classic Motors" which is a sub of "Motorcycles" will have: (ID => 4, parent_id => 1, depth => 1, name => American Classic Motors)
"ATVs" which is a sub of "American Classic Motors" will have: (ID => 5, parent_id => 4, depth => 2, name => ATVs)
Try this:
SELECT C.CatID,C.Category,MB.CatID as MBCatID,MB.SubID as MBSubID,MB.SubName as MBSubName,IFNULL(PC.CatID,'') as PCCatID,IFNULL(PC.SubID,'') as PcSubID,IFNULL(PC.SubName,'') as PcSubName,IFNULL(PC.ID,'') as PCID FROM categories C
LEFT JOIN motorcycle_brands MB ON MB.CatID = C.CatID
LEFT JOIN powersport_categories PC ON PC.CatID = C.CatID
WHERE C.CatID = 1 LIMIT 5;
See the result in SQL Fiddle

Categories