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.
Related
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.
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
I'm currently trying to implement a search engine function, trying to return information from 3 tables efficiently. The usage is numeric searches, free text won't be possible and as such I'm not trying to optimise for this scenario.
The tables being used are as follows:
Companies hasMany Products
Products hasMany Prices
The problem is as follows:
I want to return the single cheapest priced product for each company that meets any specified criteria (this could be criteria against the product or price)
The solution I have is as follows:
SELECT * FROM (
SELECT Company.id, Company.name AS CompanyName, Product.name, Product.quantity, Price.price
FROM Product
LEFT JOIN Price ON Product.id=Price.product_id
LEFT JOIN Company ON Product.company_id=Company.id
/* EXAMPLE CONDITIONS */
WHERE Price.price > 10 AND Product.quantity > 4
ORDER BY Price.price
) AS tmp_table
GROUP BY tmp_table.id
ORDER BY tmp_table.price;
Question: Is this method of a sub query with joins the most effective way to achieve this solution?
The execution times are ranging anywhere from 1ms to 140ms with 3 companies, each with 3 products, that each have 3 prices so if this were to go into the hundreds it could get messy.
I've created an SQL Fiddle at http://sqlfiddle.com/#!2/c194b7/1/0
This query relies on a "feature" of MySQL that is specifically documented not to work. That is, you are assuming that the extra columns in the outer group by come from the first row, and yet the documentation clearly states:
MySQL extends the use of GROUP BY so that the select list can refer to
nonaggregated columns not named in the GROUP BY clause. This means
that the preceding query is legal in MySQL. 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.
I would not recommend the approach you are using.
Because you are using group by, you can do this with the group_concat()/substring_index() method:
SELECT c.id, c.name AS CompanyName, Product.name,
substring_index(group_concat(p.name order by pr.price, p.product_id), ',', 1) as price,
substring_index(group_concat(p.quantity order by pr.price, p.product_id), ',', 1) as price,
MIN(pr.price) as price
FROM Product p LEFT JOIN
Price pr
ON p.id = pr.product_id LEFT JOIN
Company c
ON p.company_id = c.id
WHERE pr.price > 10 AND p.quantity > 4
GROUP BY c.id, c.name
ORDER BY pr.price;
Here is the first statement:
SELECT * FROM (
SELECT E.EMP_NAME,O.NAME AS DEPTNAME
FROM M_EMPLOYEE E
LEFT JOIN M_USERS U ON U.USER_ID=E.USER_ID
LEFT JOIN M_ORGANIZATION O ON U.DEPT_ID=O.ORG_ID
GROUP BY E.USER_ID)T WHERE T.EMP_NAME='lm';
Here is the result:
EMP_NAME | DEPTNAME
lm | null
Here is the second statement:
SELECT * FROM (
SELECT E.EMP_NAME,O.NAME AS DEPTNAME
FROM M_EMPLOYEE E
LEFT JOIN M_USERS U ON U.USER_ID=E.USER_ID
LEFT JOIN M_ORGANIZATION O ON U.DEPT_ID=O.ORG_ID
GROUP BY U.USER_ID )T WHERE T.EMP_NAME='lm';
Here is the result:
EMP_NAME | DEPTNAME
lm | qitan
Question: Why the two results are different?
From MySQL Extensions to GROUP BY
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.
In your case, you are gouping by U.USER_ID and selecting un-aggregated columns E.EMP_NAME,O.NAME
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