Selecting minimum within JOIN - mysql

I have two tables (listed only fields important for the question):
t_groups
INT groupId PRIMARY
VARCHAR(255) grname
t_goods
INT goodId PRIMARY
INT groupId
INT price
VARCHAR(255) name
Now I need a query, which selects group names and name of the cheapest good in each group. Tried doing it this way:
SELECT gr.groupId, grname, g.name
FROM t_groups AS gr
LEFT JOIN (SELECT * FROM t_goods ORDER BY PRICE ASC LIMIT 1) AS g
ON g.groupId = gr.groupId
but it doesn't work — returns NULLs in g.name field. It could be easily explained:
SELECT within JOIN statement selects cheapest good first, and then tries to "filter it" by groupId. Obviously, it'll only work for the group cheapest good belongs to.
How do I solve the task?

Why your query does not work
SELECT gr.groupId, grname, g.name
FROM t_groups AS gr
LEFT JOIN (SELECT * FROM t_goods ORDER BY PRICE ASC LIMIT 1) AS g
ON g.groupId = gr.groupId
The inner query selects the absolutely cheapest good (irrespective of group) in your database. Therefore, when you LEFT JOIN the groups to this result set, only the group which actually includes the universally cheapest good has a matching row (that group should get the g.name column filled properly). However, due to the way LEFT JOIN works all other groups will get NULL as the value of all columns in g.
The correct solution
First, you need to select the cheapest price in each group. This is easy:
SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY (groupId)
However the cheapest price is not useful without the associated goodId. The problem is that it's not meaningful to write something like:
/* does not make sense, although MySql has historically allowed it */
SELECT goodId, groupId, MIN(price) AS minPrice FROM t_goods GROUP BY (groupId)
The reason is that you cannot select a non-grouped column (i.e. goodId) unless you wrap it in an aggregate function (such as MIN): we don't know which goodId you want from among those that share the same groupId.
The correct, portable way to get the goodId of the cheapest goods in each group is
SELECT goodId, temp.groupId, temp.minPrice
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) temp
JOIN t_goods ON temp.groupId = t_goods.groupId AND temp.minPrice = t_goods.price)
The above query first finds out the cheapest price per group, and then joins to the goods table again to find the goodIds of the goods having that price inside that group.
Important: if multiple goods have an equal cheapest price in a group, this query will return all of them. If you only want one result per group you have to specify the tiebreaker, for example:
SELECT MIN(goodId), temp.groupId, MIN(temp.minPrice)
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) temp
JOIN t_goods ON temp.groupId = t_goods.groupId AND temp.minPrice = t_goods.price)
GROUP BY temp.groupId
With this query in hand, you can then find the name and price of the single cheapest good in each group (lowest goodId will be used as tiebreaker):
SELECT groupId, grname, gd.name, t3.minPrice
FROM t_groups AS gr
LEFT JOIN (SELECT MIN(goodId) AS goodId, t1.groupId, MIN(t1.minPrice) AS minPrice
FROM (SELECT groupId, MIN(price) AS minPrice FROM t_goods GROUP BY groupId) t1
JOIN t_goods ON t1.groupId = t_goods.groupId AND t1.minPrice = t_goods.price
) t2
) t3 ON gr.groupId = t3.groupId
LEFT JOIN t_goods gd ON t3.goodId = gd.goodId
This final query performs two joins at its "outer" level:
joins groups with the "goodId and cheapest price for each group" table to get the goodId and cheapest price
then joins with the goods table to get the name of the good with this goodId
It will produce only one good per group, even if multiple goods are tied for cheapest.

Here's how you could do it:
select
t_groups.grname as `name of group`,
t_goods.name as `name of good`
from (
select
groupId,
min(price) as min_price
from t_goods
group by groupId
) as mins
inner join t_goods
on mins.groupId = t_goods.groupId and mins.min_price = t_goods.price
inner join t_groups
on mins.groupId = t_groups.groupId
How this works:
mins subquery gets the minimum price for each groupId
joining mins to t_goods pulls all of the goods out that have the minimum price in their group. Note that this could return multiple goods in a single group, if there are multiple goods with the minimum price
that's then joined to t_groups to get the group name
Your query was presumably returning NULLs because it was left joining to a subquery with only one row.

Related

Apparently my Subquery returns more than 1 row

So I want to find all items from the parts table for which the price is greater than or equal to the average price of the respective product line.
And I tried it wirh subquerys and Group by but my Subquery returns more than one row. Any Help?
select * from parts
where price >= (select distinct avg(price)
from parts group by productLine)
You have to create a connection between the parts table and the average prices query, because as you have your subquery now, it returns the average price for all productlines, which you most probably you have more than one ... And also the DISTINCT doesn't help here, unless all of your productlines have the extact same AVG(price) -- which is quite unlikely.
With newer versions of mysql you can use a common table expression
with prices(avgprice, productline) as (
select avg(price), productline
from parts
group by productline)
select pa.*
from parts pa inner join prices pr on pa.productline = pr.productline
where pa.price >= pr.avgprice
If you are on a older version of mysql, which doesn't support CTE, you can also join on the result of a subquery
select pa.*
from parts pa inner join (
select avg(price) as avgprice, productline
from parts
group by productline) pr on pa.productline = pr.productline
where pa.price >= pr.avgprice
or you can just limit your subquery on the respective productline
select *
from parts p
where price >= (
select avg(price)
from parts pa
where pa.productline = p.productline)

How do MySQL aggregate sum function with two different data tables?

SELECT
category_id,
product_size,
category_name,
SUM(product_quantity) AS total_quantity
FROM tbl_categories_quantity -- (table-1)
INNER JOIN tbl_categories USING (category_id)
GROUP BY category_id,product_size
The Above Code is working in a single table, and I want to Add the below code (2nd table) that does not work
SELECT
category_id,
product_size,
SUM(product_sell) AS total_sell
FROM tbl_product_sell -- (table-2)
GROUP BY category_id,product_size;
From first subquery retrieves category and product size wise total quantity and second one retrieves total sales based on category and product size. Then combine this two subquery with LEFT JOIN because sometimes sale may not happen. COALESCE() is used for replacing NULL value to 0 (zero). If specific category or product size wise data are required then use WHERE clause in both the subquery. As category id is unique so MAX(category_name) is used otherwise category name must be placed in GROUP BY clause. Subtract total sale from total quantity for calculating available quantity.
-- MySQL
SELECT t.category_name category
, t.product_size
, t.product_quantity
, COALESCE(p.total_sell, 0) product_sell
, (t.product_quantity - COALESCE(p.total_sell, 0)) available_in_stock
FROM (SELECT tc.category_id
, tcq.product_size
, MAX(tc.category_name) category_name
, SUM(tcq.product_quantity) product_quantity
FROM tbl_categories tc
INNER JOIN tbl_categories_quantity tcq
ON tc.category_id = tcq.category_id
GROUP BY tc.category_id
, tcq.product_size) t
LEFT JOIN (SELECT category_id
, product_size
, SUM(product_sell) total_sell
FROM tbl_stock_sell
GROUP BY category_id
, product_size) p
ON t.category_id = p.category_id
AND t.product_size = p.product_size
Please check from url https://dbfiddle.uk/?rdbms=mysql_5.7&fiddle=b8c54aa656d9dc930fcb7a93d2bc0960
N.B.: Table name or column name may vary based on your DB.

MySQL Join with sum()

I have two tables; One contains for products stats and another one contains additional stats
StatsHourly:
id
product_id (can be multiple)
amount
cost
time
StatsValues:
id
product_id (can be multiple)
value (double)
I need to join those two tables and get something like this in the result:
product_id
sum (amount)
sum (cost)
sum (value)
I'm trying to do this:
"SELECT
SUM(s.amount) as amount,
SUM(s.cost) as cost
FROM StatsHourly s
LEFT JOIN (
SELECT
COALESCE(SUM(value), 0) as value
FROM StatsValues
GROUP BY product_id
) value v ON v.product_id = s.product_id
WHERE 1
AND s.product_id = :product_id";
This doesn't work. Could someone show me the right way to do it?
You have an extra comma after as cost:
SUM(s.cost) as cost, <-- here
You also use 2 aliases for the subquery, you should remove value from there:
) value v
You do not use any output from the subquery.
Coalesce() is unnecessary in the subquery.
This works (tested):
SELECT
s.product_id as product_id,
s.amount_s as amount,
s.cost_s as cost,
v.value_v as value
FROM
(SELECT
product_id,
SUM(amount) as amount_s,
SUM(cost) as cost_s
FROM StatsHourly
GROUP BY product_id) as s
LEFT JOIN
(SELECT
product_id,
SUM(value) as value_v
FROM StatsValues
GROUP BY product_id) as v
ON v.product_id = s.product_id;
WHERE s.product_id = 'product_id';
The point is:
As you have multiple equal product_id in BOTH table you have to make two aggregated tables through subqueries that makes the product_id unique and sum all appropriate rows.
After that you can join and you select the already aggregated values.
Regards

ORDER BY based on maximum number of row appearence

Table Fields:
shop_id , product_id
I want a list of all shops having specific products(should have at least 1 product)
results should be sorted on basis of shops having maximum number of specified products
I could write sql query for 1st part, but the list is not sorted according to the shops that match maximum number of products
SELECT
shop_id,
product_id
FROM
products_table
WHERE
product_id IN (1,2,3)
ORDER BY ???
Is there a optimal solution?
Join with a subquery that gets the counts for each shop, and order by that.
SELECT a.shop_id, a.product_id
FROM products_table AS a
JOIN (SELECT shop_id, COUNT(*) AS product_count
FROM products_table
WHERE product_id in (1, 2, 3)
GROUP BY shop_id) AS b
ON a.shop_id = b.shop_id
WHERE product_id IN (1, 2, 3)
ORDER BY b.product_count DESC
A query like this will avoid repeating the list of product_ids:
with sp as (
select shop_id, product_id
from products_table
where product_id IN (1,2,3)
)
select
shop_id, product_id,
(select count(*) from sp as sp2 where sp2.shop_id = sp.shop_id) as shop_count
from sp
order by shop_count desc
But now I see you're using MySQL so it won't work out for you although it can be expanded:
select
shop_id, product_id,
(
select count(*) from products_table as p2
where product_id in (1,2,3) and p2.shop_id = p.shop_id
) as shop_count
from products_table as p
where product_id in (1,2,3)
order by shop_count desc;
It's essentially the same query but the join is implied. I'm under the impression that MySQL doesn't always handle correlated queries very efficiently. I think the flavor of Barbar's answer is the one you'll have to use unless you create a temporary table mirroring "sp" above.
As a side note, I study languages and it's interesting to me that I chose to call my computed column "shop_count" while the other Barmar went with "product_count. I focused on shop as the center of my attention though we're actually counting up products. To me "shop_count" indicated "count per shop" while Barbar might describe his as "count of products". By no means am I arguing that one approach is more valid or natural. It's just fascinating to me to see the different perspective that people can take.

SQL Listing only unique values with subquery

let me start off by saying Yes this is a homework question.
It seems so simple but I can't get it to work. I am just starting sub-queries and I'm guessing that's what my teacher wants here.
here's the question
5. Write a SELECT statement that returns the name and discount percent
of each product that has a unique discount percent. In other words,
don’t include products that have the same discount percent as another
product.
-Sort the results by the ProductName column.
Here's what I tried
SELECT DISTINCT p1.ProductName, p1.DiscountPercent
FROM Products AS p1
WHERE NOT EXISTS
(SELECT p2.ProductName, p2.DiscountPercent
FROM Products AS p2
WHERE p1.DiscountPercent <> p2.DiscountPercent)
ORDER BY ProductName;
Any Help would be highly appreciated - thanks !
When checking for uniqueness using COUNT() makes it simple, you can either use it in a HAVING clause or select it outright.
SELECT a.ProductName, a.DiscountPercent
FROM Products a
JOIN (SELECT DiscountPercent, COUNT(DiscountPercent) AS CT
FROM Products
GROUP BY DiscountPercent
)b
ON a.DiscountPercent = b.DiscountPercent
WHERE b.CT = 1
Or:
SELECT a.ProductName, a.DiscountPercent
FROM Products a
JOIN (SELECT DiscountPercent
FROM Products
GROUP BY DiscountPercent
HAVING COUNT(DiscountPercent) = 1
)b
ON a.DiscountPercent = b.DiscountPercent
Try this, it's quite similar to yours, but with not in you assure that the discount is not present in another product.
SELECT p1.ProductName, p1.DiscountPercent
FROM Products AS p1
WHERE p1.DiscountPercent NOT IN
(SELECT p2.DiscountPercent
FROM Products AS p2
WHERE p1.ProductName <> p2.ProductName)
ORDER BY ProductName
If you need to solve this with a subquery, you want to find products for which NOT EXISTS another product with an equal(=) discountPercent, not different (<>). Using a <> in that NOT EXISTS clause would return results only if all discountPercents in the table had the same value (there doesn't exist any other product with a different discount --> all discounts are the same)
And take into account that you'll need to add a condition to make sure the subquery isn't finding a match for the same row executing it (i.e. p1 is not the same row as p2)
For instance, if productName is enough to identify a product:
SELECT DISTINCT p1.ProductName, p1.DiscountPercent
FROM Products AS p1
WHERE NOT EXISTS
(SELECT p2.ProductName, p2.DiscountPercent
FROM Products AS p2
WHERE p1.DiscountPercent = p2.DiscountPercent
AND p1.ProductName <> p2.ProductName)
ORDER BY ProductName;
try this query in mysql
select ProductName, DiscountPercent
from product
where DiscountPercent in (select DiscountPercent from product
group by DiscountPercent
having count(DiscountPercent)=1)