php - Check same column twice in mysqli query - mysql

I have 3 tables in my php based system.
Those tables are product, category, product_categories.
Product
pid | product_name | price
1 | Nike T-Shirt | 23
Category
cid | category_name
1 | Men
2 | Women
Product_categories
pcid | cid | pid
1 | 1 | 1
2 | 2 | 1
That means, 1 product may be in both multiple categories.
Now I am developing the product search section with filter.
If a user select both categories, all the products in selected categories should display.
Example : If a user select both Men & Women, Nike T-Shirt should be displayed.
The query I used:
select p.*
from products p
left join product_categories pc on pc.pid=p.pid
WHERE pc.cid ='1' AND pc.cid = '2'
But it not returning correct products.
Where is the error?

You want conditions that span over multiple rows, which suggests aggregation. You can join the tables, group by product, and use the having clause for filtering:
select p.id, p.product_name, p.price
from product p
inner join product_category pc on pc.pid = p.pid
inner join category c on c.cid = pc.cid
where c.category_name in ('Men', 'Women')
group by p.pid, p.product_name, p.price
having max(c.category_name = 'Men') = 1 and max(c.category_name = 'Women') = 1
If you can filter by category id rather than by category name, then you need one less join:
select p.id, p.product_name, p.price
from product p
inner join product_category pc on pc.pid = p.pid
where pc.cid in (1, 2)
group by p.pid, p.product_name, p.price
having max(pc.cid= 1) = 1 and max(pc.cid = 2) = 1

Here are few alternatives:
-- All products that are at lest in one of the desired categories
select * from product where product.pid = any (select product_category.pid from product_category where cid = 1 or cid = 2);
-- Products that are in both categories
select * from product where product.pid = any (
select product_category.pid from product_category where cid = 1 or cid = 2 group by pid having count(cid) = 2
);
-- Products with additional information "in how many desired categories they are"
-- You can order by it and/or filter on it
with product_category_matches as (
select pid, count(*) as category_count from product_category where cid in (1, 2) group by pid
)
select *, product_category_matches.category_count from
product inner join product_category_matches on product.pid = product_category_matches.pid
where product_category_matches.category_count > 0 -- Product must be at least in one desired category
order by product_category_matches.category_count desc

Related

MySQL limit on child

I saw some articles but I can't implement it on my code,
I Have 3 tables
Category
ID
name
1
winter
2
summer
Product
ID
name
1
bikini
2
scarf
Category_product
id_category
id_product
1
1
2
2
Basically like that, with much more products and more records on category_product as well, so what I want is to select 10 categories with their products but limited by 10 as well...
So I have something like
---Category 1
Product 1
Product 2
Product 3
---Category 2
Product 1
Product 2
Product 3
10 category max and 10 products max
I tried this but only get one product
SELECT c.* FROM category c
INNER JOIN category_product cp ON cp.id_category = c.id
INNER JOIN
(SELECT id_category, MAX(id_product) test
FROM category_product
GROUP BY id_category) cp2 ON cp.id_category = cp2.id_category
AND cp.id_product = cp2.test
so what I want is to select 10 categories with their products but limited by 10 as well
You can use row_number() and limit:
select c.*, cp.product_id
from (select c.*
from categories c
limit 10 -- you might want order by to get particular categories
) c join
(select cp.*,
row_number() over (partition by cp.category_id order by cp.product_id) as seqnum
from category_product cp
) cp
on c.category_id = c.id
where seqnum <= 10;
You can join in products if you want additional information about the products.

Mysql group by with multiple columns with inner joins

I have 3 tables, products, productscategories and categories.
The productscategories is a many to many table with only id numbers of the other two tables.
The result without the group by looks like this:
id | Url | Category
-------------------------------------
1 | http://example.com/12 | hat
2 | http://example.com/12 | shoe
3 | http://example.com/13 | hat
4 | http://example.com/13 | jacket
5 | http://example.com/14 | hat
6 | http://example.com/14 | socks
Now I want to exclude every row with the same url if it contains any of the choosen categories, in this case jacket and shoe.
The unwanted result looks like this:
id | Url | Category
-------------------------------------
1 | http://example.com/12 | hat
3 | http://example.com/13 | hat
5 | http://example.com/14 | hat
Because url with id 13 includes jacket I don't want it there. Same goes for url with 14 which includes shoe.
This accur because I have multiple categories and multiple urls that are not aware of each other.
The sql for the above:
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE categories.slug NOT IN (
'shoe',
'jacket',
)
GROUP BY products.image_url
The wanted result:
id | Url | Category
-------------------------------------
5 | http://example.com/14 | hat
How can I make an sql query that makes url aware of the category, like above?
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
GROUP BY products.image_url
HAVING sum( categories.slug IN('shoe','jacket') )=0
categories.slug IN('shoe','jacket') - return 1 if category in set or 0 if not. sum() - return count of shoe/jacket in group. HAVING filter group with shoe/jacket in it.
Suggestion 1: WHERE NOT EXISTS
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE NOT EXISTS(
SELECT 1
FROM products p2
JOIN productscategories pc2 ON p2.id = pc2.product_id
JOIN categories c2 ON c2.id = pc2.category_id
WHERE c2.slug IN ('shoe','jacket')
AND p2.url = products.url
)
Suggestion 2: OUTER JOIN
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
LEFT OUTER JOIN products p2 ON products.url = p2.url
LEFT OUTER JOIN productscategories pc3 ON p2.id = pc2.product_id
LEFT OUTER JOIN categories c2 ON c2.id = pc2.category_id AND c2.slug IN ('shoe','jacket')
WHERE c2.id IS NULL
Try this:
SELECT * FROM (
SELECT * FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
) AS A
LEFT JOIN
(
SELECT Url FROM products
JOIN productscategories ON products.id = productscategories.product_id
JOIN categories ON categories.id = productscategories.category_id
WHERE Categories.slug IN ('jacket', 'shoe')
GROUP BY url
) B ON B.url = A.url
WHERE B.url IS NULL

Write query without join and using subquery

I want to rewrite the join query with sub-query like IN(), ANY() operator.
Data set:
*categories*
categoryID name
5 ROD
7 CEMENT
*products*
productID categoryID name
7 5 BSRM 10mm
9 5 KSRM 5mm
10 5 Julius
11 7
12 5 BSRM 25mm
*sale_products*
saleID productID
118 9
119 9
120 9
121 9
122 12
123 12
124 12
This is my query to read saleID, product name and category name for which already have sold.
My query is:
SELECT sale_products.saleID, products.name, categories.name
FROM categories
INNER JOIN (products, sale_products)
ON categories.categoryID = products.categoryID
AND products.productID = sale_products.productID
Now I want to the result set without join and with the sub-query methodology.
I try in this way:
SELECT categories.name
FROM categories
WHERE categories.categoryID IN
(SELECT products.categoryID
FROM products
WHERE products.productID in
(SELECT sale_products.productID FROM sale_products))
this query only give me the category name but I need also saleID, product name.
Your original query looks queer. Why do you cross join products and sale_products?
It should better be:
select sp.saleid, p.name as product, c.name as category
from sale_products sp
join products p on p.productid = sp.productid
join categories c on c.categoryid = p.categoryid;
This is the straight-forward way to show the data. You can use subqueries instead, but I see no sense in it:
select
sp.saleid
(
select p.name
from products p
where p.productid = sp.productid
) as product,
(
select c.name
from categories c
where c.categoryid = p.categoryid
) as category
from sale_products sp;
Here is yet another query with subqueries. Again without any benefit over the simple query using direct joins on the tables.
select sp.saleid, p.name as product, c.name as category
from sale_products sp
join (select productid, name from products) p on p.productid = sp.productid
join (select categoryid, name from categories) c on c.categoryid = p.categoryid;

Who sell with lowest price

I have a database with tables:
suppliers:
fid , name
1 | 'Andrey'
2 | 'lucas'
products:
pid , name
1 | 'X'
2 | 'Y'
prdtFrn:
pid , fid , price
---------------- supplier 'andrey'
1 | 1 | 19.00
2 | 1 | 16.00
----------------- supplier 'lucas'
1 | 2 | 14.00
2 | 2 | 18.00
Querying the products, I get all products that have registred, but I need know who sells current product of 'product table' as lowest price, I don't know how to write the query
select p.*
from (
select pid, min(price) as MinPrice
from prdtFrn
group by pid
) pm
inner join prdtFrn p on pm.pid = p.pid and pm.MinPrice = p.price
You will see multiple rows per product where two suppliers are selling a product at the same price.
As with any complex SQL, build it up, step-by-step.
SELECT s.name AS s_name, p.name AS p_name, sp.price
FROM suppliers AS s
JOIN prdtFrn AS sp ON s.fid = sp.fid
JOIN products AS p ON p.pid = sp.pid
This gives you the list of seller, product and price. Now, presumably, for each product, you want the details of the supplier name who sells the product for the smallest price:
SELECT p_name, MIN(price) AS min_price
FROM (SELECT s.name AS s_name, p.name AS p_name, sp.price
FROM suppliers AS s
JOIN prdtFrn AS sp ON s.fid = sp.fid
JOIN products AS p ON p.pid = sp.pid
) AS spp
This gives you the minimum price for each product. Now you need to join that result with the first query, to yield the answer:
SELECT spp.p_name, spp.s_name, min_price
FROM (SELECT s.name AS s_name, p.name AS p_name, sp.price
FROM suppliers AS s
JOIN prdtFrn AS sp ON s.fid = sp.fid
JOIN products AS p ON p.pid = sp.pid
) AS spp
JOIN (SELECT p_name, MIN(price) AS min_price
FROM (SELECT s.name AS s_name, p.name AS p_name, sp.price
FROM suppliers AS s
JOIN prdtFrn AS sp ON s.fid = sp.fid
JOIN products AS p ON p.pid = sp.pid
) AS spp
) AS mp
ON mp.p_name = spp.p_name AND mp.min_price = spp.price;
Then, when it's working, you can optimize...for example, the minimum price calculation doesn't need the supplier information:
SELECT spp.p_name, spp.s_name, mp.min_price
FROM suppliers AS s
JOIN prdtFrn AS sp ON s.fid = sp.fid
JOIN (SELECT pid, MIN(price) AS min_price
FROM prdtFrn
GROUP BY pid
) AS mp
ON mp.pid = sp.pid AND mp.min_price = sp.price;
My first query went awry because I went for more information than was needed in the very first query; this is much simpler. So, the lessons here are:
Build up the query piece-by-piece.
Don't be satisfied with the first draft.

mysql count subtables and its subtables for its main table

Table 1 - Category
id | name
Table 2 - subcat
id | cid(category.id) | name
Table 3 - products
id | cid(category.id) | sid(subcat.id) | name
select a.* , count(b.id) as total
from category a left join
subcategory b on a.id=b.cid
group by a.id order by a.name
this gives the count of sub categories for each category
and I can run seperately for products so that i will get count of products for each category
what I want is the count of subcategories and the count of products for each category. How to form the query?
It should be like catename, count of (sub categories) and count of (products)
Because you may have sub-categories for a given category, but no products actually listed for such sub-category, you might not get the results you expect... This should catch both for you
select a.id,
a.name,
BySubCat.AvailableSubCategories,
ByProduct.ActualSubCats,
ByProduct.ProductCount
from
category a
left join
( select cid, count(*) as AvailableSubCategories
from subcat
group by cid ) BySubCat
on a.id = BySubCat.cid
left join
( select cid,
count( distinct sid ) as ActualSubCats,
count(*) ProductCount
from
products
group by
cid ) ByProduct
on a.id = ByProduct.cid
order by
a.name
Try this:
select c.name,count(sc.id),count(sub.pcount)
from subcat sc
inner join
(
select p.sid as subid, count(p.id) as pcount from Products p
inner join subcat sc on sc.id = p.sid
group by p.sid
) sub
on sub.subid = sc.id
inner join Category c on c.id = sc.cid
group by sc.id,sub.pcount