I am building a small app that shows the cheapest price for a product by area. I need the result from my database to show only one result per area, and that result must have the lowest price of all rows for that area.
I've come up with this so far, which is nearly there but mixes up the result rows.
SELECT `products`.`id`, `products`.`area_id`, `products`.`date`,
`products`.`duration`, MIN(`products`.`price`) AS `price`, `products`.`rating`,
`products`.`buy_url` FROM `products` WHERE `price` >= '0' GROUP BY `products`.`area_id` ORDER BY
`price` ASC
Although this successfully returns only one result per area, using MIN() here seems to get the lowest price for that area, but the other columns will be from a different row (i.e. the row that would have been selected in its entirety had I not used MIN() above).
So, I obviously have this wrong. I'd be really grateful for some advice on how I can select the lowest 'price', along with the rest of that row, from each distinct area.
Thanks,
Matt
select t1.* from products as t1
inner join (
select area_id,min(price) as price
from products where price > 0
group by area_id) as t2
on t1.area_id = t2.area_id and t1.price = t2.price
alter table products add index i (area_id,price);
SELECT `products`.`id`, `products`.`area_id`, `products`.`date`, `products`.`duration`, MIN(`products`.`price`) AS `price`, `products`.`rating`, `products`.`buy_url` WHERE `price` >= '0' GROUP BY `products`.`id`, `products`.`area_id`, `products`.`date`, `products`.`duration`, `products`.`rating`, `products`.`buy_url` ORDER BY `price` ASC
You'll have to group by all the columns you are selecting.
what about this
SELECT MIN(p1.price) AS minPrice,
p1.id, p1.area_id, p1.date, p1.duration,p1.rating,p1buy_url
FROM products p1
LEFT JOIN products p2
ON (p1.area_id=p2.area.id AND p1.id<p2.id)
WHERE p2.id is NULL
GROUP BY area_id
ORDER BY p1.id ASC
note: you can not order by on a field( here u mention minPrice) which is not exist in table
LEFT JOIN is faster than INNER JOIN as u can check by using EXPLAIN keyword before SELECT
Reference Question
Related
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)
Let me start in plain english first
Query: Get top 100 paying users and their current active item (just one item)
Here is a drafted query
SELECT `user_id`, SUM(p.`amount`) as `total`
FROM `users_purcahse` AS p
LEFT JOIN (SELECT `ui`.`item_id` as `item_id`, `ui`.`user_id` as `user_id`
FROM `user_items` AS `ui`
LEFT OUTER JOIN `items` AS `i` ON `ui`.`item_id` = `i`.`id`
LEFT OUTER JOIN `categories` AS `cat` ON `i`.`category_id` = `cat`.`id`
WHERE `ui`.isActive = 1
) AS `ui` ON p.`user_id` = `ui`.`user_id`
GROUP BY `user_id`, `ui`.`item_id`
ORDER BY `total` DESC
LIMIT 0, 100;
The problem with this is that the inner query is getting all users items table and then it will join it with the top 100 paying users
user items is a very large table, the query is taking too long
I simply want to attach the current active items for each user after doing the calculations
Note: a user can have so many items but only 1 active item
Note2: it's not enforced on the DB level that user_items can have one column with is_active per user
This is a job for some well-chosen subqueries.
First, let's find the user_id values of your top-paying users.
SELECT user_id, SUM(amount) total
FROM users_purcahse
ORDER BY SUM(amount) DESC
LIMIT 100
Next, let's find the item_id values for your users. If more than one item is active, we'll take the one with the smallest item_id value to get just one.
SELECT user_id, MIN(item_id) item_id
FROM user_items
WHERE isActive = 1
GROUP BY user_id
Then, in an outer query we can fetch the details of your items.
SELECT top_users.user_id, top_users.total,
active_items.item_id,
items.*, categories.*
FROM (
SELECT user_id, SUM(amount) total
FROM users_purcahse
ORDER BY SUM(amount) DESC
LIMIT 100
) top_users
LEFT JOIN (
SELECT user_id, MIN(item_id) item_id
FROM user_items
WHERE isActive = 1
GROUP BY user_id
) active_items ON top_users.user_id = active_items.user_id
LEFT JOIN items ON active_items.item_id = item.id
LEFT JOIN categories ON item.category_id = categories.id
ORDER BY top_users.total DESC, top_users.user_id
The trick here is to use GROUP BY subqueries to get the data items where you need just one value per user_id.
Once you have the resultset you need, you can use EXPLAIN to help you sort out any performance problems.
I have table products and table seen. Every time a customer views a product an entry is added for that product in seen table. I want to retrieve list of all products from products table and sort the list of products in desc order of how many times its been viewed.Note if a product hasn't been viewed even once then there will be no entry in seen table, and that product should be put at top of the list followed by products which have been viewed once,twice and so on
SELECT products.product_id ,products.product_name , anon_1.seen_count
FROM products
LEFT OUTER JOIN (
SELECT seen.product_id , count(*) AS seen_count
FROM seen
GROUP BY seen.product_id
) AS anon_1 ON anon_1.product_id = products.product_id
ORDER BY anon_1.seen_count ASC;
My above query puts the products that haven't been viewed at the bottom. How do i fix this?
I have tried both descending and ascending. Descending puts the ones that have been least viewed at the bottom and ascending puts the ones that have not been viewed at the bottom
For LEFT OUTER JOIN, you should consider NULL value for products that haven't been viewed. So you should use this:
SELECT products.product_id ,products.product_name,
COALESCE(anon_1.seen_count, 0) seen_count
FROM products
LEFT OUTER JOIN (
SELECT seen.product_id , count(*) AS seen_count
FROM seen
GROUP BY seen.product_id
) AS anon_1 ON anon_1.product_id = products.product_id
ORDER BY seen_count ASC;
ORDER BY anon_1.seen_count DESC;
it sorts them from the greatest value to smallest.
You should use
ORDER BY anon_1.seen_count ASC;
to sort from the smallest to greatest.
Try this
SELECT products.product_id ,products.product_name , CASE WHEN anon_1.seen_count IS NULL THEN 0 ELSE anon_1.seen_count END AS count
FROM products
LEFT OUTER JOIN (
SELECT seen.product_id , count(*) AS seen_count
FROM seen
GROUP BY seen.product_id
) AS anon_1 ON anon_1.product_id = products.product_id
ORDER BY count DESC;
In Mysql and SqlServer, null value is the min value. But for Oracle, null value is the max value.
So you might using MySql or SqlServer, right?
Current result would be as follow, we need to put the last one to the top, which seen_count is null.
In order to do this, I add an additional column to solve it. Does that meet with your need? Hope that could help you!
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.
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.