Get minimum price with left join / result reduced - mysql

I am coding an online shop. A product has a default price, but since it might have different attributes (color, size, ...), those different attributes might result in different prices as well. At the moment I am trying to produce a mysql query which helps me to find the minimum and maximum possible price of a product. Using LEFT JOIN currently reduces the query's result to only one row and I don't know why.
SELECT
products.id AS id,
categories.name AS category_name,
MIN(product_attributes.price) AS min_price, MAX(product_attributes.price) AS max_price,
products.*
FROM products
LEFT JOIN categories ON category_id=categories.id
LEFT JOIN product_attributes ON products.id=product_attributes.product_id
Is this even the correct approach? I do not know that much about mysql, I just try and try and I am happy if it works. Thanks for help anyway.

You are missing a GROUP BY clause in your current query, but I would recommend using a subquery to get the result:
SELECT
p.id AS id,
c.name AS category_name,
pa.min_price,
pa.max_price,
p.*
FROM products p
LEFT JOIN categories c
ON p.category_id = c.id
LEFT JOIN
(
select MIN(product_attributes.price) min_price,
MAX(product_attributes.price) max_price,
product_id
from product_attributes
group by product_id
) pa
ON p.id=pa.product_id
The main reason why I would suggest using a subquery is because MySQL MySQL uses a EXTENSION TO GROUP BY which allows the behavior of not enforcing the FULL GROUP BY.
This extension in MySQL can cause unexpected values to be returned in the columns in the SELECT list that are not in the GROUP BY clause or in an aggregate function.
From the MySQL Docs:
MySQL extends the use of GROUP BY so that the select list can refer to nonaggregated columns not named in the GROUP BY clause. ... You can use this feature to get better performance by avoiding unnecessary column sorting and grouping. However, this is useful primarily when all values in each nonaggregated column not named in the GROUP BY are the same for each group. The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate. Furthermore, the selection of values from each group cannot be influenced by adding an ORDER BY clause. Sorting of the result set occurs after values have been chosen, and ORDER BY does not affect which values the server chooses.

you need to have GROUP BY clause,
SELECT
products.id AS id,
categories.name AS category_name,
MIN(product_attributes.price) AS min_price,
MAX(product_attributes.price) AS max_price.
products.*
FROM products
LEFT JOIN categories ON category_id=categories.id
LEFT JOIN product_attributes ON products.id=product_attributes.product_id
GROUP BY products.id, categories.name
but be careful when using GROUP BY in mysql, as the statement is perfectly valid if ONLY_FULL_GROUP_BY is disabled by default.
The correct way of doing the query is by using a subquery which separately gets minimum price for each product.
SELECT a.id AS id,
b.name AS category_name,
c.minPrice,
c.maxPrice,
a.*
FROM products a
LEFT JOIN categories b
ON a.category_id = b.id
LEFT JOIN product_attributes c
(
SELECT product_id,
MIN(product_attributes.price) minPrice,
MAX(product_attributes.price) maxPrice
FROM product_attributes
GROUP BY product_id
) d ON a.id = c.product_id

Related

SQL query to get products in multiple order states

We have a products, orders, order_items, order_status schema as shown below.
The orders tables status field, takes the values; ORDERED, RECEIVING, RECEIVED and CANCELED, as defined in the order_status table.
The UI, that interacts with this data do have a 'product view', with rows for each product. As a user select a product, another view below (In Orders), lists the orders that the selected product was present in.
The user should be able to filter the products view, based on product order status. In particular, a button, saying 'On Order', should filter the product view to only show product records that have a record in the In Orders view, with a status of 'ORDERED'.
The following query returns multiple product rows, originating from the fact that one product exists in multiple orders.
SELECT products.*,
orders.`id` AS orderID,
orders.`status`
FROM products
LEFT JOIN order_items
ON products.`id` = order_items.`product_id`
JOIN orders
ON orders.`id` = order_items.`order_id`;
We want the above result set to "coalesce" on order status. That is, the result set should have only one product record for each distinct order status. We could then filter the product view on the 'status' field.
The image below shows what result set that we would like, based on the above result. Red means it should not be part of the result.
As observed from the image above;
The product with ID 18 is repeated 3 times with the same status. We only want one of those rows.
The product with ID 19 is present in 3 rows, two with the same status. Keep one of the two rows with the same status, and the row with status = 1.
The product with ID 20 is repeated twice with the same status, keep one.
How to achieve that?
Use GROUP BY to collapse multiple rows into one. Use MIN(o.id) to get a well-defined order ID within each group.
SELECT p.*, MIN(o.id) AS orderID, o.status
FROM products AS p
JOIN order_items AS oi ON oi.product_id = p.id
JOIN orders AS o ON o.id = oi.order_id
GROUP BY p.id, o.status
It doesn't make sense to use LEFT JOIN in this case. You never want to group by a column that comes from a LEFT JOIN table, since all the rows with no match will be grouped together. And if you're filtering on order status, you obviously only want products that are in an order.
If you are running MySQL 8.0, you can use row_number() for filtering. I guess the logic you want is:
select *
from (
select
p.*,
o.id as orderid,
o.status ,
row_number() over(partition by p.id, o.status order by o.id) rn
from products p
inner join order_items oi on p.id = oi.product_id
inner join orders o on o.id = oi.order_id
) t
where rn = 1
I don't think that mixing inner join and left join make sense here. Either use two inner joins (as in the above query), or two left joins if you want to retain products without any order.

Can't GROUP BY with first unique item in MYSQL

I have two MySQL tables:
product
photo
Then I do INNER JOIN
SELECT *
FROM product
INNER JOIN photo
ON product.productID = photo.productID
But I need only one raw per unique product ID
If I try:
SELECT *
FROM product
INNER JOIN photo
ON product.productID = photo.productID
ORDER BY product.productID
It returns error
Expression #38 of SELECT list is not in GROUP BY clause and contains nonaggregated column 'photo.photoID' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by
The problem is photoID column. There are few photos per one product, but I need table with only first photo per product.
Can I do it with MYSQL tools like WHERE for example?
Thanks!
When you use Aggregate function clause,you need to use GROUP BY on no-Aggregate column.
Making a subquery to find the MIN photoID. then JOIN on another subquery
You can try this.
SELECT p.*,T.* FROM
(
SELECT productID,MIN(photoID) 'photoID'
FROM photo
GROUP BY productID
) T
INNER JOIN (
SELECT DISTINCT productID,name
FROM product
) p ON p.productID = T.productID
sqlfiddle : http://sqlfiddle.com/#!9/97a87d/12

SQL average and Join

I'm trying to merge these two statements into one query to get the a list of product names(or ids) against the average of their TTFF data, and I'm stuck.
select AVG(TTFF) from TTFFdata group by product_id
select product.product_name, count(*) from product join TTFFdata on product.product_id = TTFFdata.product_id
I've looked into using a temporary table (CREATE TEMPORARY TABLE IF NOT EXISTS averages AS (select AVG(TTFF) from TTFFdata group by product_id)) but couldn't get that to work with a join.
Anyone able to help me please?
You need to understand the components. Your second query is missing a group by. This would seem to be what you want:
select p.product_name, count(t.product_id), avg(t.TTFF)
from product p left join
TTFFdata t
on p.product_id = t.product_id
group by p.product_name
It is better to do group by on product_id, product_name for two reasons. One is, you can select product id along with product name. Second reason is, If the product name is not unique then it may give wrong results(this may be a rare scenario like product name is same but it differs based on other columns like version or model). The below is the final query.
select Product.product_id,
product_name,
AVG(TTFF) as Avg_TTFF
from Product
inner join
TTFFdata
on Product.product_id = TTFFdata.product_id
group by Product.product_id,Product.product_name
TTFFdata:
product:
Output:

Select distinct returning indistinct rows

Select distinct is returning indistinct rows. Why?
I want to return distinct shops.
Here is my sql statement:
SELECT
DISTINCT s.*, p.p_id
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
Here is the result:
The product (p.p_id) needs to not be distinct, as I want to return a list of shops that have a specific product. But the Shop needs to be distinct.
What am I doing wrong?
Returned rows are distinct. Distinct is applied to all returned row, not to single column. Yes, p_id is same for two rows. But if you compare all columns, there are differences between them.
If you want distinct shops - don't include in select columns from other tables, because it can cause duplicates as in your example.
Simply don't include p.p_id within your selection.
I.e.
SELECT DISTINCT
s.*
FROM shop s
....
Well, If you will look at your entire output, you can see the p_id(the last column) is different for each row. Distinct applies to the entire record, not just one column.
You can either drop the p_id from your select, or use group by and decide which one of the p_id you want, perhaps max? :
SELECT
s.*, max(p.p_id)
FROM
shop s
INNER JOIN product_shop ps on s.s_id = ps.s_id
INNER JOIN product p ON p.p_id = ps.p_id
WHERE
s.country = 'new zealand'
GROUP BY s.id

Filter query by ID

I'm trying to retrieve all the product information, filtering by product category id. The result should be hundreds of products but only returns 5 items. Whats wrong with my query?
SELECT product.id_product,
product.reference,
product.price,
product.active,
product.quantity,
product.id_category_default,
lang.name AS product_name,
lang.description,
lang.link_rewrite AS product_link_rewrite,
category.name AS category_name,
category.link_rewrite AS category_link_rewrite,
image.id_image,
product.id_manufacturer,
discount.reduction,
manufacturer.name AS manufacturer_name
FROM ps_product product
LEFT JOIN ps_specific_price discount ON discount.id_product=product.id_product
LEFT JOIN ps_product_lang lang ON lang.id_product=product.id_product
LEFT JOIN ps_category_lang category ON category.id_category=product.id_category_default
LEFT JOIN ps_image image ON image.id_product=product.id_product
LEFT JOIN ps_manufacturer manufacturer ON manufacturer.id_manufacturer=product.id_manufacturer
WHERE product.active=1
AND product.quantity>=1
AND product.id_category_default IN
(
4,5,6,65,66,90,91,53,54,48,49,50,55,62,67,68,71,19,82,88,89,87,22,24,26,74,
76,77,28,78,79,97,98,99,93,96,35,36,38,39,100
)
GROUP BY product.id_product ASC
Perhaps something as simple as:
GROUP BY product.id_product ASC
needs to be:
ORDER BY product.id_product ASC
mySQL extends the group by clause so that any columns not in the group by may get an indeterminate value.
If ONLY_FULL_GROUP_BY is disabled, a MySQL extension to the standard SQL use of GROUP BY permits the select list, HAVING condition, or ORDER BY list to refer to nonaggregated columns even if the columns are not functionally dependent on GROUP BY columns. ... The server is free to choose any value from each group, so unless they are the same, the values chosen are indeterminate, which is probably not what you want.
Options:
change group by to order by
add all columns to the group by
don't use group by at all.