SELECT MIN (MAX) after SELECTING MIN value in MySQL - mysql

I have two tables: items and prices (one-to-many)
Each item has a default price, but this price can be overriden in this second table (under some circumstances).
Firstly I had a problem of fetching all the items and pre-calculate the MINIMUM PRICE - MIN between default and its overriding current price (if any ?).
You can see it here: http://sqlfiddle.com/#!2/f31d5/25
Luckily, it was solved, thanks to stackoverflow (you can see it here: Rails select subquery (without finder_sql, if possible)), but now I have similar kind of a problem!
Once I have all the items selected, can I determine the absolute MIN (or MAX) price AMONG the newly calculated field (min_price) ?
Of course I've tried something like this: http://sqlfiddle.com/#!2/f31d5/26
But, it did not work. Is there any smart SQL-true way to get these values ?
For this particular scenario in SQLFiddle it should return 5 (MIN), or 500 (MAX)
Of course I could have selected it straight-away from pricing table like this:
SELECT MIN(price) FROM prices;
But, I cannot rely on it, since item's default price might be lower and this way I cannot check it (I think, SELECT MIN/MAX does not work with JOIN or GROUP BY).
And also one thing to be aware of - I'm writing this for my search system and this is just a small part of it, so, "WHERE"-clause is pretty important there as well, because NOT all items are actually involved.
Is there any SMART way (SQL-way) I can solve this problem?
P.S Temporarily I've solved the problem simply by making a query that orders by min_price (ASC/DESC) and applying LIMIT 1 and then gathering the needed value from the item record itself, but I'm very unhappy with this solution, so I've decided to ask about it here.
Thanks
P.P.S Totally forgot to mention, that there is no way I can avoid this SQL-query, because I have a pagination system that actually appends LIMIT X, OFFSET Y. So, I need to select the value GLOBALLY, not just for a particular page!

You don't need the second call to min(). I think this does what you want:
SELECT LEAST(IFNULL(MIN(prices.price),
items.default_price),
default_price) as min_price
FROM items LEFT OUTER JOIN
prices
ON prices.item_id = items.id
GROUP BY items.id;
If you want the lowest price across all items, you can remove the group by clause. However, I think a subquery is clearer about the intention:
select min(min_price)
from (SELECT LEAST(IFNULL(MIN(prices.price),
items.default_price),
default_price) as min_price
FROM items LEFT OUTER JOIN
prices
ON prices.item_id = items.id
GROUP BY items.id
) p;

Related

SQL: Correct Way to Grab First Row of a Group

I'm migrating a site to Google's Cloud SQL service, which has an odd default with ONLY_FULL_GROUP_BY, which means a common pattern that I use suddenly breaks down.
Consider the following:
SELECT `p`.`id`, `p`.`name`, `s`.`name` AS `latest_purchase`, `s`.`price` AS `latest_purchase_price`
FROM `Person` p
JOIN `Sales` s ON `s`.`person` = `p`.`ID`
GROUP BY `p`.`id`
ORDER BY `p`.`time` DESC
What I want is to get a list of results where each row is a unique person, with columns indicating the name and price of their most recent purchase.
The partial grouping behaviour I'm used to in MySQL is great for this, because it groups only on the person ID, and since my results are ordered the first row of each group is the one that I want, so I get the results that I expect.
But this isn't allowed with the SQL mode ONLY_FULL_GROUP_BY which requires that all selected items are either in the GROUP BY clause, or use aggregate functions to select a single result.
Neither of these works in the above example, because adding everything to the GROUP BY means I would get multiple results per person, while using aggregate functions could give an inaccurate result, as the sale price isn't necessarily the highest or lowest in the group (I could potentially end up with a sale name and price, neither of which is the latest).
Fortunately SQL mode is one of the settings Google SQL allows a user to change, so I've just done that for the time being (edit the instance and go to set flags).
However if I were to use another system in future where I can't group as I wish, then what is the "correct" way to do this when partial grouping isn't allowed?
I realise there are some similar questions on StackOverflow already, but none that I've found quite captures my problem (as they involve much simpler examples where aggregate functions can be used).
What you want is filtering not aggregation:
SELECT `p`.`id`, `p`.`name`, `s`.`name` AS `latest_purchase`, `s`.`price` AS `latest_purchase_price`
FROM `Person` p JOIN
`Sales` s
ON `s`.`person` = `p`.`ID`
WHERE s.time = (SELECT MAX(s2.time) FROM sales s2 WHERE s2.person = s.person)
ORDER BY `p`.`time` DESC

MySql group average and group last value in one query

I have table named Amounts with columns RowId, CounterId and Amount. It is easy to group by CounterId and get counters average value, but if I want also to get last value of Amount in group to know is it bigger or smaller than average, I’m in trouble? How to get that as just including Amount in query gives me first value of Amount in group what is useless. Maybe it is easy to do, but I have not found answer for my problem with just one table. I found, how to find only last Amount in group by help with RowId, but how to obtain them - average and last value - to one result, is mystery for me now… Thanks ahead.
Thanks to Ram Bath I built what I needed and result is here:
SELECT Kliendid.Id AS Id,
Kliendid.Nimi AS Klient,
MIN(X.Tarbimine) AS Piseim,
AVG(X.Tarbimine) AS Keskmine,
MAX(X.Tarbimine) AS Suureim,
COUNT(X.Tarbimine) AS Kuid,
(
Select Tarbimine
from Naidud A
where A.Id=MAX(X.Id)
) as Viimane
FROM Naidud X
INNER JOIN Kliendid ON Kliendid.ID=X.Klient
INNER JOIN Mooturid ON Mooturid.ID=X.Mootur
WHERE X.Tarbimine>0
AND X.Aeg>'2015-12-31'
AND Mooturid.Kasutusel=1
GROUP BY X.Mootur
HAVING Kuid>5
AND (Viimane=Piseim OR Viimane=Suureim)
As you see, my question was simplified as I use Estonian for table and column names and there would be much harder to help if I had shared code from the beginning... Thanks again for all of you.
Here I am assuming you RowId is Unique or is the Primary Key.
SELECT RowId,AVG(AmountId) as Average_Amount,(Select Amount from Amounts a where a.RowId=MAX(X.RowId)) as LastAmount from Amounts X Group by X.CounterId;

RoR selecting newest "distinct by column" rows

I have a table which I use to log item price change over time.
I'm trying to write a method which grabs the entire set of items (without duplicates), together with their latest prices.
That means that a row with an item_id of 2 may appear several times inside my table, and a row with an item_id of 3 may appear several times inside the table etc', but the result should only include them once, with their latest price
I'm trying to figure out a way (without using Item.find_by_sql() if possible), to return the entire set of items and their latest prices.
Currently I have the following:
SELECT * FROM
(SELECT * FROM item_logs
ORDER BY created_at DESC) inner_table
GROUP BY item_id
It does work, but it seems wrong to do it like this, I guess i'm looking for a more elegant way to do this, since current implementation requires me to use find_by_sql which is not very flexible.
not sure it's any better, but another option would be use joins:
ItemLog.joins(
'join (select item_id, max(created_at) as created_at from item_logs group by 1)
as i on i.item_id = item_logs.item_id and i.created_at = item_logs.created_at'
)
longer than your find_by_sql solution, could be a more expensive query on your database, but keeps the result as an active record relation so you can chain other methods on.

MySQL: Best way to combine multiple MAX() selects with an array?

I hope someone could give me a general direction on this problem:
The starting point is an array of ids of db records.
array ids = [45,23,14,7];
Those records have some columns, i.e.
id,price,rating
7,$5.00,5
14,$2.00,4
23,$5.00,2
45,$5.00,5
What I would need is
the items with max(price) (or something equivalent).
if there is more than one item with the same price, get the ones with max(rating) (or something equivalent).
Finally, if there is still more than one item, take the one that comes first in the array.
I'm particularly stuck with point 3. Is there a way to do that in (My)SQL, or should I do that in code?
Thank you for your reading.
Something like this should work:
SELECT * FROM table WHERE id IN (45,23,14,7) ORDER BY price DESC, rating DESC LIMIT 1
In addition to the answer by #jasonlfunk you can add an extra order clause to take into account your array as well:
SELECT * FROM table WHERE id IN (45,23,14,7) ORDER BY price DESC, rating DESC, FIELD(id,45,23,14,7) ASC LIMIT 1
...I think about your point 3 ..it must be done in code, the result of mysql not necessarily returns results in the order or the array, if applying order by price, then by rating still returning more than one item your code should be able to receive a list instead a single row, and then make the comparisson in code.

Will grouping an ordered table always return the first row? MYSQL

I'm writing a query where I group a selection of rows to find the MIN value for one of the columns.
I'd also like to return the other column values associated with the MIN row returned.
e.g
ID QTY PRODUCT TYPE
--------------------
1 2 Orange Fruit
2 4 Banana Fruit
3 3 Apple Fruit
If I GROUP this table by the column 'TYPE' and select the MIN qty, it won't return the corresponding product for the MIN row which in the case above is 'Apple'.
Adding an ORDER BY clause before grouping seems to solve the problem. However, before I go ahead and include this query in my application I'd just like to know whether this method will always return the correct value. Is this the correct approach? I've seen some examples where subqueries are used, however I have also read that this inefficient.
Thanks in advance.
Adding an ORDER BY clause before grouping seems to solve the problem. However, before I go ahead and include this query in my application I'd just like to know whether this method will always return the correct value. Is this the correct approach? I've seen some examples where subqueries are used, however I have also read that this inefficient.
No, this is not the correct approach.
I believe you are talking about a query like this:
SELECT product.*, MIN(qty)
FROM product
GROUP BY
type
ORDER BY
qty
What you are doing here is using MySQL's extension that allows you to select unaggregated/ungrouped columns in a GROUP BY query.
This is mostly used in the queries containing both a JOIN and a GROUP BY on a PRIMARY KEY, like this:
SELECT order.id, order.customer, SUM(price)
FROM order
JOIN orderline
ON orderline.order_id = order.id
GROUP BY
order.id
Here, order.customer is neither grouped nor aggregated, but since you are grouping on order.id, it is guaranteed to have the same value within each group.
In your case, all values of qty have different values within the group.
It is not guaranteed from which record within the group the engine will take the value.
You should do this:
SELECT p.*
FROM (
SELECT DISTINCT type
FROM product p
) pd
JOIN p
ON p.id =
(
SELECT pi.id
FROM product pi
WHERE pi.type = pd.type
ORDER BY
type, qty, id
LIMIT 1
)
If you create an index on product (type, qty, id), this query will work fast.
It's difficult to follow you properly without an example of the query you try.
From your comments I guess you query something like,
SELECT ID, COUNT(*) AS QTY, PRODUCT_TYPE
FROM PRODUCTS
GROUP BY PRODUCT_TYPE
ORDER BY COUNT(*) DESC;
My advice, you group by concept (in this case PRODUCT_TYPE) and you order by the times it appears count(*). The query above would do what you want.
The sub-queries are mostly for sorting or dismissing rows that are not interested.
The MIN you look is not exactly a MIN, it is an occurrence and you want to see first the one who gives less occurrences (meaning appears less times, I guess).
Cheers,