MySQL MAX value with 2 conditions - mysql

I'm trying to update SQL table with results of students contest. For that contest I've 2 tables: participants (name, class, school, etc.) and results (participant_id, results and subject). Now I need to give each participant a status - 1st place, 2nd place or 3d place based on their results.
The tricky part is the rules of that contest. Each participant must get this status in their respective school and class. So I must have winners in each school and each class. It's quite simple to update table with 2nd and 3d place as they counted as result >= 50 and result < 50. But the 1st place must be the highest score in this school and in this class.
I tried a lot of combinations with MAX() statement but with no success. No matter what I try I can't make it show maximum value based on 2 conditions (max in school, max in class). I need something like =MAXIFS() in Excel, but don't know how to do it in SQL.
UPD. Examples
Table participants
| id | name | class | school |
------------------------------
| 01 | John | 10 | 312 |
| 02 | Jack | 10 | 312 |
| 03 | Mary | 11 | 144 |
| 04 | Dany | 11 | 312 |
| 05 | Mark | 7 | 144 |
Table results
| id | participant_id | subject | score |
-----------------------------------------
| 01 | 03 | 4 | 55 |
| 02 | 01 | 4 | 75 |
| 03 | 04 | 4 | 60 |
| 04 | 05 | 4 | 45 |
| 05 | 02 | 4 | 90 |
And the result I need must look like this:
| id | participant_id | subject | score | status |
-----------------------------------------------------
| 01 | 03 | 4 | 55 | 1st place |
| 02 | 01 | 4 | 75 | 2nd place |
| 03 | 04 | 4 | 60 | 3rd place |
| 04 | 05 | 4 | 45 | 3rd place |
| 05 | 02 | 4 | 90 | 1st place |
Basically, 1st place is MAX in each school and class, 2nd is less than MAX and >= 50 and 3rd is just less than 50.
Can't show everything I tried because it's all variations of the same query. Something like that:
SELECT
id, participant_id, subject, score,
(CASE
WHEN score >= 50 THEN '2nd place'
WHEN score < 50 THEN '3rd place'
WHEN score = MAX(score) THEN '1st place'
END) AS 'status'
FROM results
INNER JOIN participants ON results.participant_id = participants.id
GROUP BY participant_id
I've also tried with subqueries, but it's obviously not helping - different subqueries even give different number of rows or resulting 'status' doesn't match the score and other information.

The next query can be used as solution:
SELECT
participants.*,
results.*,
(CASE
WHEN score = max_score THEN '1st place'
WHEN score >= 50 THEN '2nd place'
WHEN score < 50 THEN '3rd place'
END) AS 'status'
FROM participants
JOIN results ON results.participant_id = participants.id
JOIN (
SELECT
class, school, max(score) max_score
FROM results
INNER JOIN participants ON results.participant_id = participants.id
GROUP BY class, school
) max_results ON
max_results.class = participants.class AND
max_results.school = participants.school;
And here the fiddle: SQLize.online

Related

MySQL - Select query with first rows where a field value > 10 without ordering by this field

I would like to make a SELECT in my player table where, in the result, the first rows are the players who played more than 10 games. What is preventing me from just doing an ORDER BY total_games is that I would like to ORDER by signin_year and then by player_type for the following result.
+--------------+-------------+--------------+--------------+
| username | signin_year | player_type | total_games |
+--------------+-------------+--------------+--------------+
| John Doe | 2019 | 10 | 62 |
| Jon Skeet | 2019 | 20 | 2479 |
| Shmebulock | 2020 | 10 | 82 |
| Norman | 2020 | 20 | 154 |
| Bob | 2019 | 10 | 3 |
| Alice | 2020 | 10 | 9 |
| Community | 2020 | 20 | -1 |
+--------------+-------------+--------------+--------------+
I tried a bunch of WHEREs and ORDER BYs but I can't seens to make it work, is this even possible or will I need to use a subquery ?
Try using ORDER BY along with a CASE expression:
SELECT *
FROM player
ORDER BY
CASE WHEN total_games > 10 THEN 0 ELSE 1 END,
signin_year,
player_type;
This places players having played more than 10 total games first, followed by all other players. Within these two blocks, the data is sorted ascending by signin year followed by ascending player type.

Get Top 5 For Each Metric For Each Month For Each Item

I'm having trouble creating the query to break my now daily, table into a top monthly.
I have a table with the following structure (note the headers are actually all caps):
Start_Date | Month| Item | Location | ... | Quantity | Sales |
-----------|------|------|----------|-----|----------|-------|
8/6/19 | 08 | A | USE | ... | | |
8/6/19 | 08 | B | USE | ... | | |
8/6/19 | 08 | C | USW | ... | | |
8/6/19 | 08 | D | USW | ... | | |
8/5/19 | 08 | A | USE | ... | | |
8/5/19 | 08 | B | USE | ... | | |
8/5/19 | 08 | C | USW | ... | | |
8/5/19 | 08 | D | USW | ... | | |
.....
7/1/19 | 07 | D | USW | ... | | |
Every date has the metrics above, there's 4 rows per day due to the 4 different items, which I want -- comparing by item. My goal is to now break this out to a monthly table, with the goal of taking the top 5 in each category (Quantity, Sales, etc.) for that month and getting the AVG of that.
Example: Item A
8/6/19: Quantity = 500 | Sales = 100
8/5/19: Quantity = 478 | Sales = 130
8/4/19: Quantity = 366 | Sales = 113
8/3/19: Quantity = 678 | Sales = 90
8/2/19: Quantity = 594 | Sales = 92
8/1/19: Quantity = 500 | Sales = 105
Note: There's data for the other items B, C and D respectively.
My goal was to take the top 5 for each category and present that at the monthly level:
Results:
| Month| Item | Location | ... | Quantity | Sales |
|------|------|----------|-----|----------|-------|
| 08 | A | USE | ... | 550 | 108 |
| 08 | B | USE | ... | | |
| 08 | C | USW | ... | | |
| 08 | D | USW | ... | | |
| 07 | A | USE | ... | | |
| 07 | B | USE | ... | | |
| 07 | C | USW | ... | | |
| 07 | D | USW | ... | | |
Quantity = 550 was taken from 8/1 -> 8/6 Quantity adding the top 5 (1, 2, 3, 5, 6) and dividing by 5 (AVG of top 5). Then Sales was 1, 2, 4, 5, 6.
So obviously I need to query each category (Quantity, Sales, etc.) separately and then UNION the results together. I'm just struggling with how to even get the TOP 5 of a specific category.
I've done some searching on Stack and Google for how to obtain the Top5 in a query. I see some threads that suggest you can actually use TOP(5) which doesn't work for me. LIMIT 5 only limits the results to 5, and I'm unable to use LIMIT in a subquery w/ the most recent version of SQL. Ordering and using a simple statement like "number <= 5" won't work since the months are different in the later rows of the dataset... I'm able to get the AVG for all the data for a specific month by simply just SELECTing the AVG() and GROUPing by ITEM but I want to project a top 5/10 which I've been unable to figure out.
Thanks for your assistance. I could throw out some of the queries I've tried but none are even close as I've been trying to use LIMIT.
You can use window functions:
select month, item,
avg(case when seqnum_q <= 5 then quantity end) as quantity_top5,
avg(case when seqnum_s <= 5 then sales end) as sales_top5
from (select t.*,
row_number() over (partition by item, month order by quantity desc) as seqnum_q,
row_number() over (partition by item, month order by sales desc) as seqnum_s
from t
) t
group by month, item;
Note: Using month without year is discomfiting. Either year should be included in the logic, or you should be clear that the data is only for one year.

MySQL: Finding Maximum Value for a Column for each location and server

I have the following table:
++++++++++++++++++++++++++++++++++++++++++++++++++
| location | server | datetime | max_cpu |
++++++++++++++++++++++++++++++++++++++++++++++++++
| Chicago | 1 | 2013-05-01 00:00 | 10 |
| Chicago | 1 | 2013-05-01 01:00 | 15 |
| Chicago | 1 | 2013-05-01 02:00 | 11 |
| Chicago | 2 | 2013-05-01 00:00 | 8 |
| Chicago | 2 | 2013-05-01 01:00 | 12 |
| Chicago | 2 | 2013-05-01 02:00 | 13 |
| Atlanta | 1 | 2013-05-01 00:00 | 11 |
| Atlanta | 1 | 2013-05-01 01:00 | 12 |
| Atlanta | 1 | 2013-05-01 02:00 | 19 |
| Atlanta | 2 | 2013-05-01 00:00 | 21 |
| Atlanta | 2 | 2013-05-01 01:00 | 15 |
| Atlanta | 2 | 2013-05-01 02:00 | 17 |
I need the maximum CPU for each box in each location for a given day, e.g.
++++++++++++++++++++++++++++++++++++++++++++++++++
| location | server | datetime | max_cpu |
++++++++++++++++++++++++++++++++++++++++++++++++++
| Chicago | 1 | 2013-05-01 01:00 | 15 |
| Chicago | 2 | 2013-05-01 02:00 | 13 |
| Atlanta | 1 | 2013-05-01 02:00 | 19 |
| Atlanta | 2 | 2013-05-01 00:00 | 21 |
I know how to do this for a single criteria (e.g. just location) and tried to expand upon that (see below) but it is not giving me the output I need.
SELECT a.location, a.server, a.datetime, a.max_cpu
FROM mytable as a INNER JOIN
(
SELECT location, server, max(max_cpu) as max_cpu
FROM mytable
GROUP BY location, server
)
AS b ON
(
a.location = b.location
AND a.server = b.server
AND a.max_cpu = b.max_cpu
)
You can do this by finding the max cpu and joining back to the original table.
It seems that you want the time of the max as well as the amount (this is not clearly stated in the text, but clear in the results):
select t.*
from mytable t join
(select location, server, DATE(datetime) as thedate, MAX(max_cpu) as maxmaxcpu
from mytable t
group by location, server, DATE(datetime)
) lsd
on lsd.location = t.location and lsd.server = t.server and
lsd.thedate = DATE(t.datetime) and lsd.maxmaxcpu = t.max_cpu
This calculates the maxcpu on each day and then joins back to get the appropriate row or rows in the original data. If there is more than one record with the max, you'll get all the records. If you only want one, you can add group by location, server, day(datetime) to the query.
This better answers the "for a given day" part of the question. Since you can ignore the time, this avoids that date hacky thing, is a tad simpler, and if multiple times have the same CPU for that server, it doesn't show duplicates:
select distinct a.location, a.server, a.datetime, a.max_cpu
from
mytable a
inner join (
select location, server, max(max_cpu) as max
from mytable
where
datetime >= ? -- start of day
and datetime < ? -- start of next day
group by location, server
) b on a.location=b.location and a.server=b.server and a.max_cpu as max
where
a.datetime >= ? -- start of day
a.and datetime < ? -- start of next day
Query (works only if max_cpu is unique per location, server and Date ):
SQLFIDDLEExample
SELECT t1.*
FROM Table1 t1
WHERE t1.max_cpu = (SELECT MAX(t2.max_cpu)
FROM Table1 t2
WHERE t2.location = t1.location
AND t2.server = t1.server
AND DATE(t2.datetime) = DATE(t1.datetime))
Result:
| LOCATION | SERVER | DATETIME | MAX_CPU |
------------------------------------------------------------
| Chicago | 1 | May, 01 2013 01:00:00+0000 | 15 |
| Chicago | 2 | May, 01 2013 02:00:00+0000 | 13 |
| Atlanta | 1 | May, 01 2013 02:00:00+0000 | 19 |
| Atlanta | 2 | May, 01 2013 00:00:00+0000 | 21 |

Select flat data into table format

I have a table with a schema similar to the following
id | year | month | amount ...
x | 2011 | 12 | 312
x | 2011 | 12 | 213
x | 2012 | 1 | 123
x | 2012 | 1 | 123
x | 2012 | 2 | 123
...
I want to know if it would be possible (via mysql) to output something like
year | 1 | 2 | 3 | ....
2011 | 321 | 231 | ...
2012 | 246 | 123 | ...
So doing a group by year, month, sum(amount) and output one "row" per year with the individual months as the columns.
Thanks
You would want to use a pivot table of sorts. If you are only dealing with months something like the below SQL should work for you. I only did the first three months but the rest are the same.
SELECT year, SUM(IF(month=1,amount,0)) As '1', SUM(IF(month=2,amount,0)) As '2', SUM(IF(month=3,amount,0)) As '3' FROM mytable GROUP BY year

MySQL query that needs a lot of (complex?) filtering

I have the following database table (People):
+--------+--------+--------+
| Name | Age | Time |
+--------+--------+--------+
| Tim | 30 | 10:10 |
| Jill | 31 | 10:20 |
| Peter | 31 | 10:30 |
| Peter | 33 | 10:40 |
| Jack | 32 | 10:50 |
| Susan | 35 | 10:60 |
| Susan | 35 | 11:70 |
+--------+--------+--------+
Now I want to get 5 names which are the oldest from this list:
Select * FROM People ORDER BY Age DESC LIMIT 5
This will give:
| Susan | 35 | 11:70 |
| Susan | 35 | 10:60 |
| Peter | 33 | 10:40 |
| Jack | 32 | 10:50 |
| Peter | 31 | 10:30 |
Now this is not exactly what I want. We see Susan in there twice, same as Peter. I don't want duplicate names to show up. Now we see that the 2 Susans have both the same age, so the Susan with the highest time (mm:ss) should be filter out (in this case: | Susan | 35 | 11:70 |)
Fot the 2 Peters we see that one Peter is older, so in this case the younger Peter should be filtered out (| Peter | 31 | 10:30 |)
So how would this Query look like? It should first select all, then order them by Age (oldest on top), then filter out the duplicate names where it must first look at the age (lowest gets filtered) and then at the time (highest time gets filter out) and then DESC LIMIT 5.
So the only correct result will look like this:
| Susan | 35 | 10:60 |
| Peter | 33 | 10:40 |
| Jack | 32 | 10:50 |
| Jill | 31 | 10:20 |
| Peter | 31 | 10:30 |
SELECT
Name, Age, MIN(Time) As LowestTime
FROM
People
GROUP BY
Name, Age
ORDER BY
Age DESC, Name ASC
LIMIT 5
SELECT Name,Age,Max(Time) as Time
FROM People
GROUP BY concat(Age,':',Name)
ORDER BY Age DESC,Time ASC
LIMIT 5
The GROUP BY takes care of Susan, the ORDER BY of Peter
I believe this query is what you're looking for. It filters by Name:Age, then Name:Time, and finally does a final group by Name and ORDER as necessary.
Query
SELECT `Name`, `Age`, `Time`
FROM `People`
WHERE concat(`Name`,'-',`Time`)
IN (
SELECT concat(`Name`,'-',MAX(`Time`))
FROM `People`
WHERE concat(`Name`,'-',`Age`)
IN (
SELECT concat(`Name`,'-',MIN(`Age`))
FROM `People`
GROUP BY `Name`
)
GROUP BY `Name`
)
GROUP BY `Name`
ORDER BY `Age` DESC
LIMIT 5
Note: While this is a valid query and returns the results you're looking for, it can get pretty expensive, especially with a larger table. If you plan to do this filtering on a larger table, I would suggest adding a column where you concatenate these fields manually, and set up an index against both columns. Also, I'm assuming this was just a quick example, but if you should also have an id column set up that is PRIMARY and auto_increment.