SQL: Find closest number to given value with ties - mysql

I'm trying to find the closest number(s) to a given value in SQL. I have already made my query for multiple results:
SELECT *
FROM Cars
ORDER BY ABS(price - $price)
I know I can limit the table by using LIMIT 1, by which I have one number closest to the given value. But how can I include ties? Like for example when there are three or four cars with the same price? The amount of cars which have the same price is dynamic, so I can't specify a certain LIMIT.
I also know I could use SELECT TOP 1 WITH TIES, but I can't use this query because my database driver doesn't allow it. Does anybody have another idea of how to accomplish this?
Example:
car 1 = 2000
car 2 = 3000
car 3 = 3000
car 4 = 1500
When I want the cars closest to 3000, the query should return:
car 2
car 3
But without using a static LIMIT in the query, because the amount of cars with the same price can be different every time. Thanks

If your driver supports nested queries:
SELECT *
FROM CARS
WHERE ABS(price - $price) = ( SELECT MIN(ABS(price - $price)) FROM CARS )

Related

multiply two columns from two different tables and group them together

I have two tables in MySQL which are sale and material details.
I want to calculate the profit I made by selling items which is
profit = (total) -(qty* landedcost)
Here is the structure of the two tables:
This is the query
SELECT sale.name ,sale.total-(sale.qty * materialdetails.landingcost) AS
result
FROM sale JOIN materialdetails
on sale.id = materialdetails.id
GROUP BY sale.name,result;
the result i get :
query result
I want something like this
name result
A4 5000
Computer 40000
Flash memory 1000
Headphone 22000
Mobile 35000
Any idea please?
You should sum the result and group by sale.name only, something like this:
SELECT sale.name ,sum(sale.total-(sale.qty * materialdetails.landingcost)) AS
result
FROM sale JOIN materialdetails
on sale.id = materialdetails.id
GROUP BY sale.name;
Explanation: if you group by two fields GROUP BY sale.name,result you will get one line for all records that have the same sale.name and result, so for instance
name result
Computer 10000
Computer 25000
are two different lines and they are not grouped together as one.

ORDER BY and GROUP BY those results in a single query

I am trying to query a dataset from a single table, which contains quiz answers/entries from multiple users. I want to pull out the highest scoring entry from each individual user.
My data looks like the following:
ID TP_ID quiz_id name num_questions correct incorrect percent created_at
1 10154312970149546 1 Joe 3 2 1 67 2015-09-20 22:47:10
2 10154312970149546 1 Joe 3 3 0 100 2015-09-21 20:15:20
3 125564674465289 1 Test User 3 1 2 33 2015-09-23 08:07:18
4 10153627558393996 1 Bob 3 3 0 100 2015-09-23 11:27:02
My query looks like the following:
SELECT * FROM `entries`
WHERE `TP_ID` IN('10153627558393996', '10154312970149546')
GROUP BY `TP_ID`
ORDER BY `correct` DESC
In my mind, what that should do is get the two users from the IN clause, order them by the number of correct answers and then group them together, so I should be left with the 2 highest scores from those two users.
In reality it's giving me two results, but the one from Joe gives me the lower of the two values (2), with Bob first with a score of 3. Swapping to ASC ordering keeps the scores the same but places Joe first.
So, how could I achieve what I need?
You're after the groupwise maximum, which can be obtained by joining the grouped results back to the table:
SELECT * FROM entries NATURAL JOIN (
SELECT TP_ID, MAX(correct) correct
FROM entries
WHERE TP_ID IN ('10153627558393996', '10154312970149546')
GROUP BY TP_ID
) t
Of course, if a user has multiple records with the maximal score, it will return all of them; should you only want some subset, you'll need to express the logic for determining which.
MySql is quite lax when it comes to group-by-clauses - but as a rule of thumb you should try to follow the rule that other DBMSs enforce:
In a group-by-query each column should either be part of the group-by-clause or contain a column-function.
For your query I would suggest:
SELECT `TP_ID`,`name`,max(`correct`) FROM `entries`
WHERE `TP_ID` IN('10153627558393996', '10154312970149546')
GROUP BY `TP_ID`,`name`
Since your table seems quite denormalized the group by name-par could be omitted, but it might be necessary in other cases.
ORDER BY is only used to specify in which order the results are returned but does nothing about what results are returned - so you need to apply the max()-function to get the highest number of right answers.

sum in mysql than group by

I am making a small lottery game for fun and to improve myself.
On the database, I have
table(id, package, value, price, purchase_code,round)
See an example. There is two package, package1 and package2.
package1 has a value of 3 and package2 has a value of 4. This means, that if I buy the package1, i got 3 ticket which is playing, giving me bigger chance to win in the current round, so it inserts 3 record into a table, containing the informations. So in this case, I have the following records in my table:
id pacakage_id value price purchase_code round
1 1 3 10 w3hjkrw 1
2 1 3 10 w3hjkrw 1
3 1 3 10 w3hjkrw 1
I would like to see overall how money the users spent , and for this, I used sum(price).
Ok, but as you can see, the three record was one purchase, so sum(price) would give me the result 30. I tried to group by purchase_code, but it is not doing what I want.
Here is the code:
$income_query = mysql_query("SELECT SUM(price) FROM lottery WHERE round = '$current_round' GROUP BY code") or die(mysql_error());
while($result = mysql_fetch_array($income_query)) {
$round_money = $result['SUM(price)']." $";
Think you will need to do a sub query to get the package id price. Possibly using distinct, although I would just use a normal aggregate function (MAX will do the job here).
Something like this:-
SELECT code, SUM(package_id_price)
FROM(
SELECT code, package_id, MAX(price) AS package_id_price
FROM lottery
WHERE round = '$current_round'
GROUP BY code, package_id
) Sub1
GROUP BY code

MySQL query for items where average price is less than X?

I'm stumped with how to do the following purely in MySQL, and I've resorted to taking my result set and manipulating it in ruby afterwards, which doesn't seem ideal.
Here's the question. With a dataset of 'items' like:
id state_id price issue_date listed
1 5 450 2011 1
1 5 455 2011 1
1 5 490 2011 1
1 5 510 2012 0
1 5 525 2012 1
...
I'm trying to get something like:
SELECT * FROM items
WHERE ([some conditions], e.g. issue_date >= 2011 and listed=1)
AND state_id = 5
GROUP BY id
HAVING AVG(price) <= 500
ORDER BY price DESC
LIMIT 25
Essentially I want to grab a "group" of items whose average price fall under a certain threshold. I know that my above example "group by" and "having" are not correct since it's just going to give the AVG(price) of that one item, which doesn't really make sense. I'm just trying to illustrate my desired result.
The important thing here is I want all of the individual items in my result set, I don't just want to see one row with the average price, total, etc.
Currently I'm just doing the above query without the HAVING AVG(price) and adding up the individual items one-by-one (in ruby) until I reach the desired average. It would be really great if I could figure out how to do this in SQL. Using subqueries or something clever like joining the table onto itself are certainly acceptable solutions if they work well! Thanks!
UPDATE: In response to Tudor's answer below, here are some clarifications. There is always going to be a target quantity in addition to the target average. And we would always sort the results by price low to high, and by date.
So if we did have 10 items that were all priced at $5 and we wanted to find 5 items with an average < $6, we'd simply return the first 5 items. We wouldn't return the first one only, and we wouldn't return the first 3 grouped with the last 2. That's essentially how my code in ruby is working right now.
I would do almost an inverse of what Jasper provided... Start your query with your criteria to explicitly limit the few items that MAY qualify instead of getting all items and running a sub-select on each entry. Could pose as a larger performance hit... could be wrong, but here's my offering..
select
i2.*
from
( SELECT i.id
FROM items i
WHERE
i.issue_date > 2011
AND i.listed = 1
AND i.state_id = 5
GROUP BY
i.id
HAVING
AVG( i.price) <= 500 ) PreQualify
JOIN items i2
on PreQualify.id = i2.id
AND i2.issue_date > 2011
AND i2.listed = 1
AND i2.state_id = 5
order by
i2.price desc
limit
25
Not sure of the order by, especially if you wanted grouping by item... In addition, I would ensure an index on (state_id, Listed, id, issue_date)
CLARIFICATION per comments
I think I AM correct on it. Don't confuse "HAVING" clause with "WHERE". WHERE says DO or DONT include based on certain conditions. HAVING means after all the where clauses and grouping is done, the result set will "POTENTIALLY" accept the answer. THEN the HAVING is checked, and if IT STILL qualifies, includes in the result set, otherwise throws it out. Try the following from the INNER query alone... Do once WITHOUT the HAVING clause, then again WITH the HAVING clause...
SELECT i.id, avg( i.price )
FROM items i
WHERE i.issue_date > 2011
AND i.listed = 1
AND i.state_id = 5
GROUP BY
i.id
HAVING
AVG( i.price) <= 500
As you get more into writing queries, try the parts individually to see what you are getting vs what you are thinking... You'll find how / why certain things work. In addition, you are now talking in your updated question about getting multiple IDs and prices at apparent low and high range... yet you are also applying a limit. If you had 20 items, and each had 10 qualifying records, your limit of 25 would show all of the first item and 5 into the second... which is NOT what I think you want... you may want 25 of each qualified "id". That would wrap this query into yet another level...
What MySQL does makes perfectly sense. What you want to do does not make sense:
if you have let's say 4 items, each with price of 5 and you put HAVING AVERAGE <= 7 what you say is that the query should return ALL the permutations, like:
{1} - since item with id 1, can be a group by itself
{1,2}
{1,3}
{1,4}
{1,2,3}
{1,2,4}
...
and so on?
Your algorithm of computing the average in ruby is also not valid, if you have items with values 5, 1, 7, 10 - and seek for an average value of less than 7, element with value 10 can be returned just in a group with element of value 1. But, by your algorithm (if I understood correctly), element with value 1 is returned in the first group.
Update
What you want is something like the Knapsack problem and your approach is using some kind of Greedy Algorithm to solve it. I don't think there are straight, easy and correct ways to implement that in SQL.
After a google search, I found this article which tries to solve the knapsack problem with AI written in SQL.
By considering your item price as a weight, having the number of items and the desired average, you could compute the maximum value that can be entered in the 'knapsack' by multiplying desired_cost with number_of_items
I'm not entirely sure from your question, but I think this is a solution to your problem:
SELECT * FROM items
WHERE (some "conditions", e.g. issue_date > 2011 and listed=1)
AND state_id = 5
AND id IN (SELECT id
FROM items
GROUP BY id
HAVING AVG(price) <= 500)
ORDER BY price DESC
LIMIT 25
note: This is off the top of my head and I haven't done complex SQL in a while, so it might be wrong. I think this or something like it should work, though.

Mysql query to count top ocurrences in 3 columns (considering it as 1 column only)

I have a table like this:
user_id hobbie1 hobbie2 hobbie3
1 ski soccer tv
1 skating tv sleep
1 tv ski sleep
1 tv read movies
(...)
And I want to get, for that user_id, the top 10 hobbies he has, ordered by occurences number. Yes, I know the database seems odd in terms of normalization, but this is a simplified example of the real thing :)
The table has several rows with the same user_id and 3 columns with its hobbies.
In that example I should be able to get:
hobbie count
tv 4
sleep 2
ski 2
(...)
So, I'm thinking that I need to convert 1 row (1 ski soccer tv) to 3 rows (1 ski | 1 soccer | 1 tv) to then do my normal sql count, etc.
My solution is:
select hobbie, count(hobbie) from (
(select hobbie1 as hobbie from hobbies)
union all
(select hobbie2 as hobbie from hobbies)
union all
(select hobbie3 as hobbie from hobbies)
) AS b
group by b.hobbie order by 2 desc limit 10
But that doesn't seem to be optimized and uses UNION to solve the problem. Any better solution?
I like this problem, but If the goal is to do in a single query, I can't think of much better solution than you have.
To me, it begs for thinking out of the box:
The unhelpful answer to "normalize" the data is, well, unhelpful. Obviously that's not an option.
How often do you need this data? How up to date does it need to be? Perhaps every night you could extract the needed data into a normalized table and run queries off of that? This is a common strategy for aggregate data that is just too hard to calculate.
There really isn't a good/nice way via one sql statement only.
Have a query that grabs all of the the hobbies and then loops over the result tabulating them. Something like (in php):
select hobbie1, hobbie2, hobbie3 from hobbies where user = ##
$tallies = array();
foreach ($result as $rs) // loops through the rows
for ($i = 1; $i <= 3; $i ++) // loops through the columns
if (array_key_exists($rs['hobbie'.$i], $tallies)
$tallies[$rs['hobbie'.$i]] += 1;
else
$tallies[$rs['hobbie'.$i]] = 1;
Then sort the array by values using asort()
asort($tallies);
They will now be smallest to largest so lets get the list of items and reverse it:
$tallies = array_reverse(array_keys($tallies));
You now have an array with the most popular hobby to the least. Hope that helps.