I have three tables which we'll pretend are called products, coupons, and discounts. I have a query that attempts to pull a list of products, and runs a subquery to find ANY coupons which have a valid discount.
For example, this shows what I'm attempting:
SELECT products.id, products.name,
(
SELECT MAX(discounts.amount) FROM discounts
WHERE discounts.coupon_id = coupons.id
LIMIT 1
) as discount
FROM products
LEFT JOIN coupons ON products.id = coupons.product_id
GROUP BY products.id
My problem is that my GROUP BY is necessary for lots of other reasons. But if there are multiple coupons for each product, the "discount" gets combined in weird ways when the grouping occurs.
Let's say for a single product there are three coupons - two without any discount and one with a discount of 33%. When the group by occurs, I want to choose the highest value but by default, MySQL returns the value as 0.
Using MAX is the subquery obviously only returns the maximim value of discounts for each individual coupon. I just need to tell GROUP BY to use the max value.
I could easily use GROUP_CONCAT to return a string of all of them, but I also need to use that value in a calculation in some HAVING conditions.
Any suggestions?
I'm don't think you want or need the subquery. What does this return for you?
SELECT products.id, products.name, MAX(discounts.amount) AS discount
FROM products
LEFT JOIN coupons ON products.id = coupons.product_id
LEFT JOIN discounts ON coupons.id = discounts.coupon_id
GROUP BY products.id
You group by productid only, while the subselect actually return three values (one for each coupon of the product). I would actually expect this query to return an error. You should either move the discount one level up, or move the coupons to the subselect. An example of the first (subqueries are typically slower in MySQL):
select
p.productid,
max(d.discount) as discount
from
product p
left join coupon c on c.productid = p.productid
left join discount d on d.couponid = c.couponid
group by
p.productid
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 am working on product data, part of which has the below structure (let's call it product_serials):
The table is a collection of product serial numbers. The snapped field determines whether a specific product has been purchased or not via it's serial number. Am trying to query the table to get a count of both all serials and also all unpurchased serials of the same product_id, using a single SQL query. So far using COUNT(ps1.id) AND COUNT(ps2.id) ... WHERE ps2.snapped = FALSE does not seem to work, it still counts the same values for both all serials and unpurchased serials, and even exaggerates the count, so am definitely doing something wrong.
What could I be missing?
My SQL query as requested:
SELECT pd.id AS product_id, pd.description,
COUNT(pds.id) AS total, COUNT(pds2.id) AS available
FROM products pd
LEFT JOIN product_serials pds ON pds.product_id = pd.id
LEFT JOIN product_serials pds2 ON pds2.product_id = pd.id
WHERE pds2.snapped = FALSE
GROUP BY pd.id
ORDER BY pd.date_added DESC
Here you join tables (even multiplying them) and then apply a WHERE condition to both.
I suggest something like the following:
SELECT product_id, count(serial), count(unpurchased)
FROM (SELECT product_id, serial,
CASE WHEN snapped THEN NULL ELSE 1 END AS unpurchased)
GROUP BY product_id
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:
I need to calculate difference not sum, is there any way to do this. Here is desired example if sub aggreagate function exists.
SELECT p.*,SUB(p.orders) AS diff FROM products AS p GROUP BY p.id HAVING p.orders<0;
This is just example, not real table, just for example. The idea is to compare if first value is bigger than next values. Real example is if query returns two rows compare two values (orders count) of this tow rows in one query.
I would be grateful for any help.
What you want is only applicable when there are always 2 rows with the same p.id and different orders. If you have more than 2 rows which you would subtract from which?
So in the special case that you always have 2 rows per p.id then what you need is not a group function but join table products with itself like:
Select p1.id, (p1.orders - p2.orders) as diff
from products p1 inner join products p2 on p1.id = p2.id
where p1.orderid <> p2.orderid
This will only work if you always have 2 different orders per product.
I have 3 tables: products, orders and orderLines(order_id, product_id).
I have an sql query to figure out which seems nearly impossible to do in only one query.
Is there a way to have in only one query:
All the products but showning a specific order's products first;
which means that: for an order A: show product1, product2.. present in orderA's orderLines first, than the following products (not ordered) are shown next.
PS:
I know it's possible to achieve this with a union of two queries, but it would be better to have it done in only one query.
You can put a subquery in the order by clause. In this case, an exists subquery is what you need:
select p.*
from products p
order by (exists (select 1
from orderlines ol
where p.productid = ol.productid and o.orderid = ORDERA
)
) desc;