Mysql joining multiple tables with hierarchical data - mysql

I am building a database for an inventory management application and I have to maintain products with multiple hierarchies of category. I have the following database model :
I want to retrieve the products, description, units of measure and the categories, for which I have used the following query :
SELECT P.ID
,P.wrin
,P.description AS productDescription
,CT.pdtCat AS productCategory
,CONCAT(UN.description,' - ',UN2.description,' - ',UN3.description) AS unitDecomposition
FROM product P
-- JOIN to product_category table
JOIN (
SELECT PC3.ID as catID
,CONCAT(PC1.category,' - ',PC2.category,' - ',PC3.category) as pdtCat
FROM product_category AS PC1
LEFT JOIN product_category AS PC2 ON PC2.parentid = PC1.id
LEFT JOIN product_category AS PC3 ON PC3.parentid = PC2.id
WHERE PC1.parentid IS NULL
) CT ON CT.catID = P.categoryId
JOIN unit UN ON P.primaryUOM = UN.ID
JOIN unit UN2 ON P.secondaryUOM = UN2.ID
JOIN unit UN3 ON P.tertiaryUOM = UN3.ID
The output from this query is :
My query is giving the intended results but seems to go sideways if my product doesn't have 3 levels of categories. The product doesn't appear in the results. Please help :)

In your sub-select you are joining from the "top" category to the bottom category and select the id of the bottom one. If there is no level 3 category, there are no results.
Try something like this:
SELECT P.ID
,P.wrin
,P.description AS productDescription
,CT.pdtCat AS productCategory
,CONCAT(UN.description,' - ',UN2.description,' - ',UN3.description) AS unitDecomposition
FROM product P
-- JOIN to product_category table
JOIN (
SELECT PC3.ID as catID
,CONCAT(PC1.category,' - ',COALESCE(PC2.category, ''),' - ',COALESCE(PC3.category, '')) as pdtCat
FROM product_category AS PC3
LEFT JOIN product_category AS PC2 ON PC3.parentid = PC2.id
LEFT JOIN product_category AS PC1 ON PC2.parentid= PC1.id
) CT ON CT.catID = P.categoryId
JOIN unit UN ON P.primaryUOM = UN.ID
JOIN unit UN2 ON P.secondaryUOM = UN2.ID
JOIN unit UN3 ON P.tertiaryUOM = UN3.ID
EDIT:
My query will produce some ' - - category' results for the product category.
Due to the fact that my strength is in MSSQL, that's an exercise for you to make it look more pretty ;)

Related

Filtering on a LEFT JOINED column

Is there a more efficient way to filter on a joined table as in the following example? Or is this a fine approach? This query returns the desired results, but I am an amateur at MySQL.
I have indexes on products.id, product_details.product_id and product_details.value
SELECT p.id
FROM products p
LEFT
JOIN product_details d
ON d.product_id = p.id
WHERE d.value = 1
OR p.id = 4
Simplified structure as follows:
products table
product_id (PRIMARY KEY) | name
--------------------------------
1 | Shirt
2 | Shoes
3 | Dress
4 | A product with no corresponding details row
product_details table
product_id (PRIMARY KEY) | value
---------------------------------
1 | 1
2 | 23
3 | 32
This is your query:
SELECT products.id
FROM products LEFT JOIN
product_details
ON product_details.product_id = products.id
WHERE product_details.value = 1 OR products.id = 4;
This is not a bad practice. I do think the query is easier to follow using EXISTS:
SELECT p.id
FROM products p
WHERE p.id = 4 OR
EXISTS (SELECT 1
FROM product_details pd
WHERE pd.product_id = p.id AND pd.value = 1
);
In addition EXISTS makes it clear that you don't want to return duplicates if there are duplicate matching rows in product_details.
If performance is you main consideration, then EXISTS is probably your best choice, with an index on product_details(product_id, value).
Couple of notes:
As a rule of thumb, a UNION ALL statement performs better than an OR operator. Also, this helps clear up the query.
Using both an implicit JOIN and a predicate in the WHERE clause on the same table can get you into trouble - especially if you're using a LEFT OUTER JOIN (the predicate in the WHERE clause has precedence over the LEFT OUTER JOIN).
Seems like you always want to pull back any records that has a products.id = 4, and also any products that have a product_details.value = 1. This seems like two separate queries to me, and splitting it would probably make it easier to maintain in the future.
SELECT
p.id
FROM
products p
WHERE
p.id = 4
UNION ALL
SELECT
p.id
FROM
product_details pd
JOIN
products p
ON
p.id = pd.product_id
WHERE
pd.value = 1
Source: https://bertwagner.com/posts/or-vs-union-all-is-one-better-for-performance/

Join 3 tables with MySQL

I have the following tables:
Products
prod_id | prod_name | prod_price
Supermarket
supermarket_id | name | address
supermarket_product
supermarket_id | product_id
How can I join those tables to get a table that shows all the information from Products and Supermarket tables in 1 single big table? I am basically trying to have a table only to hold which product belongs to which supermarket based on it.
I tried the following, but it doesn't work:
SELECT prod_name, prod_price, supermarkets.name
FROM products
INNER JOIN supermarkets
ON supermarket_product.supermarket_id = supermarket.supermarket_id;
You need to join products and supermarkets through bridge table supermarket_product:
select p.*, s.*
from products p
inner join supermarket_product sp on sp.product_id = p.prod_id
inner join supermarket s on s.supermarket_id = sp.supermarket_id
You need to also include supermarket_products in the query. You should also use table aliases to simplify your code:
SELECT p.prod_name, p.prod_price, s.name
FROM products p
INNER JOIN supermarket_product sp ON sp.product_id = p.prod_id
INNER JOIN supermarket s on s.supermarket_id = sp.supermarket_id

Pull data from 3 tables

I have 3 tables as follows :
Table 1: Product
id_product [Primary Key],added_time.
Table 2: Category
id_category [Primary Key],Category_name.
Table 3: product_category
id_category,id_product [Both Foreign Keys]
I want to pull Data as
Category_name,No Of Products in this Category,Last time when product was added to Category(Latest product added_time).
You could use this SQL:
SELECT Category.Category_name,
Count(DISTINCT Product.id_product) AS num_products,
Max(Product.added_time) last_added_time
FROM Category
LEFT JOIN product_category
ON product_category.id_category = Category.id_category
LEFT JOIN Product
ON Product.id_product = product_category.id_product
GROUP BY Category.Category_name;
Note that by using LEFT JOIN you will be certain to list all categories even those for which no products exist. If you don't want those, replace both LEFT keywords with INNER.
Note also that in standard SQL you need to GROUP BY any columns you mention in the SELECT list, unless they are aggregated, like with MAX or COUNT.
SELECT C.`Category_name`,
(SUM(IF(P.`id_product`IS NULL,0,1))) AS No_of_Products,
MAX(P.`added_time`) AS Latest_time
FROM
Category C
LEFT JOIN
product_category P_C ON C.`id_category` = P_C.`id_category`
LEFT JOIN
Product P ON P.`id_product` = P_C.`id_product`
GROUP BY C.`id_category`
Hope this helps.

Select info from a table referencing info from another table

I have a main product table with different products. Different products have different specs, so I've created separate specs tables for each product (there will be more than ten of them). What I want to do is to show individual product's specs on a product_page.php whenever the product is clicked.
My product page has columns:
id - SKU - prod_name - prod_desc....
My specs table columns
id - SKU - prod_specs....
So I want to take SKU from first table and search the rest of the table with this UNIQUE sku and wherever it is show the rest of the field from that table
What I do is
SELECT SKU FROM products AS p
INNER JOIN cpu_specs AS cs ON cs.SKU = p.SKU
INNER JOIN hdd_specs AS hs ON hs.SKU = p.SKU
WHERE p.SKU = $productSKU "
But it gives me an error.
If I do SELECT * then it fetches all the info from both tables
Try this:
SELECT p.id, p.prod_name, p.SKU, IFNULL(cs.prod_desc, hs.prod_desc)
FROM products AS p
LEFT JOIN cpu_specs AS cs ON cs.SKU = p.SKU
LEFT JOIN hdd_specs AS hs ON hs.SKU = p.SKU
WHERE p.SKU = $productSKU
I didn't find any proper SELECT query that would find just one result from the tables, but came up with a workaround
In my table products each product has a subcategory (sub_cat),which is cpu, hdd etc.
So I've named the secification tables for each product like so hdd_specs, cpu_specs and so on.
So I store the sub_cat as a variable and then SELECT everything from the table called like my variable and it works smoothly.
$query = "SELECT sub_cat FROM products WHERE SKU = $productSKU";
$select = mysql_query($query);
$result_1 = mysql_fetch_assoc($select);
$sub_cat = $result_1['sub_cat'];
$sub_cat = $sub_cat.'_specs';
$get = " SELECT * FROM $sub_cat
WHERE SKU = $productSKU
";

SQL: Get Products from a category but also must be in another set of categories

I am currently stuck in a situation. The scenario is this. I have products who may be associated with multiple categories. The data structure is shown below:
Products Table:
product_id name
1 Lemon
2 Kiwis
3 Cheese
Product to Categories Table
product_id category_id
1 1
1 2
1 3
2 1
2 3
3 2
3 4
Category Table (not required in query however adding it here to help visualize what is happening)
category_id name
1 Fruit
2 Yellow
3 Round
4 Dairy
What I'm struggling with here is that originally I want to get all products that are in the fruit category (category id 1) but I also want to check if a fruit is yellow. Keep in mind that yellow will not be the only filter, sometimes I will want to return yellow and orange fruit, however since cheese is yellow I can't return it since it isn't a fruit. However to make things a bit easier I always know that I am going to look in the fruit category as a base.
The database structure can not change as its an opencart database structure.
Here are my attempts:
SELECT GROUP_CONCAT(DISTINCT p2c2.category_id SEPARATOR ',') as categories
FROM oc_product_to_category p2c
LEFT JOIN oc_product p ON (p.product_id = p2c.product_id)
LEFT JOIN oc_product_to_category p2c2 ON (p.product_id = p2c2.product_id)
WHERE p2c.category_id IN ('1','2')
This kind of works except for that fact that it will return Cheese.
Notes: I use Group Concat because at the end of all of this my goal is to return not so much the products that match these categories but based on the filters I want to return another list of categories from the products that match this criteria. So:
Scenario:
Get Products that match category criteria
Return categories of those products.
Any assistance will be greatly appreciated.
This type of problem is called relational division.
There are two common solutions:
First solution strings together the matching categories and compares to a fixed string:
SELECT p2c.product_id
FROM oc_product_to_category p2c
GROUP BY p2c.product_id
HAVING GROUP_CONCAT(p2c.category_id SEPARATOR ',' ORDER BY p2c.category_id) = '1,2'
Second solution does a JOIN for each required value:
SELECT p.product_id
FROM oc_product p
INNER JOIN oc_product_to_category p2c1
ON (p.product_id = p2c1.product_id AND p2c1.category_id = 1)
INNER JOIN oc_product_to_category p2c2
ON (p.product_id = p2c2.product_id AND p2c2.category_id = 2)
I cover these solutions in my presentation SQL Query Patterns, Optimized. I found in my tests that the join solution is much better for performance.
#Tom's suggestion is right, here's what that would look like in a complete query:
SELECT p.product_id, GROUP_CONCAT(p2c3.category_id SEPARATOR ',') AS categories
FROM oc_product p
INNER JOIN oc_product_to_category p2c1
ON (p.product_id = p2c1.product_id AND p2c1.category_id = 1)
INNER JOIN oc_product_to_category p2c2
ON (p.product_id = p2c2.product_id AND p2c2.category_id = 2)
INNER JOIN oc_product_to_category p2c3
ON (p.product_id = p2c3.product_id)
GROUP BY p.product_id;
The DISTINCT that #Tom suggests shouldn't be necessary, because your p2c table should have a UNIQUE constraint over (product_id, category_id).