SQL query to group records under separate fields - mysql

I'm working with MySQL, and I have the following schema:
id school round score win loss tie
2 My School Name 1 10 1 0 0
3 My School Name 2 20 0 1 0
4 My School Name 3 30 1 0 0
5 My School Name 4 40 1 0 0
6 My School Name 5 50 1 0 0
7 My School Name 6 60 0 0 1
And I need the following output, grouped by school name
School Round1 Round2 Round3 Round4 Round5 Round6 wins losses ties
My School Name 10 20 30 40 50 60 4 1 1
So far I feel like I can use GROUP BY School and SUM(win) as wins to get most of the functionality out of it. The hard part, though, is to get those Round_ fields.
Does anyone know how to do this? Thanks in advance, any help would be much appreciated!
Edit: to clarify, I know I have 10 rounds exactly.

We can use a SELECT statement with a GROUP BY school to create a record for each school. The ties, wins, and losses columns are readily calculated with the SUM aggregate function, as you noted. To target a specific round, we can use some clever math (to avoid verbose conditional statements like the one CodeByMoonlight suggested):
If we want to target round R, we note that "round-R" is 0 only when round == R, otherwise it isn't 0. When we take the NOT of "round-R", 0 gets inverted to 1, while everything else gets set to 0. Now, if we multiply !(round-R) by the score of that round, it will give us 0 when the round is not R (as 0*score = 0) and it will give us "score" when the round is R (as 1*score = score). Next, when we take the SUM of this value over the columns, we add score when round=R and 0 otherwise, effectively giving us just the round R score.
Putting that all together gives:
SELECT school AS `School`,
SUM(!(round-1)*score) AS `Round1`,
SUM(!(round-2)*score) AS `Round2`,
SUM(!(round-3)*score) AS `Round3`,
SUM(!(round-4)*score) AS `Round4`,
SUM(!(round-5)*score) AS `Round5`,
SUM(!(round-6)*score) AS `Round6`,
SUM(!(round-7)*score) AS `Round7`,
SUM(!(round-8)*score) AS `Round8`,
SUM(!(round-9)*score) AS `Round9`,
SUM(!(round-10)*score) AS `Round10`,
SUM(win) AS `wins`,
SUM(loss) AS `losses`,
SUM(tie) AS `ties`
FROM `RoundScores` GROUP BY `school`
where RoundScores is the table in question.
EDIT:
If we do not want to manually add 10, we can use prepared statements :
# Store all the conditionals in a string:
# I was not able to to have round loop from 1 to 10, so I iterated over
# all distinct values of 'round' present in the table.
SET #s = "";
SELECT `round`, (#s := CONCAT( #s , "SUM(!(round-",round, ")*score) AS `Round",round, "`," )) FROM `RoundScores` GROUP BY `round`;
# Combine the conditionals from before with the rest of the statement needed.
SET #qry = CONCAT("SELECT school AS `School`,",#s,"SUM(win) AS `wins`,SUM(loss) AS `losses` FROM `RoundScores` GROUP BY `school`");
# Prepare and execute the statement.
PREPARE stmt1 FROM #qry;
EXECUTE stmt1;

TRY WITH UNION ( not tested)
SELECT SUM(win) AS wins, SUM(loss) AS losses, SUM(tie) AS ties
FROM table
GROUP BY (school)
UNION
SELECT score AS round1 FROM table WHERE round=1
UNION
SELECT score AS round2 FROM table WHERE round=2
.... AND so on..

SELECT School, Sum(Case
When Round = 1 Then Score
Else 0
End) AS Round1, Sum(Case
When Round = 2 Then Score
Else 0
End) AS Round2, Sum(Case
When Round = 3 Then Score
Else 0
End) AS Round3, Sum(Case
When Round = 4 Then Score
Else 0
End) AS Round4, Sum(Case
When Round = 5 Then Score
Else 0
End) AS Round5, Sum(Case
When Round = 6 Then Score
Else 0
End) AS Round6, Sum(Case
When Round = 7 Then Score
Else 0
End) AS Round7, Sum(Case
When Round = 8 Then Score
Else 0
End) AS Round8, Sum(Case
When Round = 9 Then Score
Else 0
End) AS Round9, Sum(Case
When Round = 10 Then Score
Else 0
End) AS Round10, Sum(Wins) AS Wins, Sum(Losses) AS Losses, Sum(Ties) AS Ties
FROM MyTable
GROUP BY School
Should work :)

Related

Set SQL ordering by highest earnings/losses

I would like to use two kinds of ordering.
From the highest earnings
From the highest losses.
My database looks like this
id
saldo
saldo_type
7
75,67
1
10
7
1
3
5,45
1
11
12,3
0
4
6,45
0
saldo_type:
1 = earnings
0 = losses
Expected result/output for sort by highest earnings:
id
saldo
saldo_type
7
75,67
1
10
7
1
3
5,45
1
4
6,45
0
11
12,3
0
and by highest losses:
id
saldo
saldo_type
11
12,3
0
4
6,45
0
3
5,45
1
10
7
1
7
75,67
1
So far I am stuck with such a code
SELECT id, saldo, saldo_type FROM `investitions`
ORDER BY
(CASE WHEN saldo_type = "1" THEN saldo END) DESC,
(CASE WHEN saldo_type = "0" THEN saldo END) ASC
Expected result/output:
sort by highest earnings
sort by highest losses
One way would be to convert the losses to negative values then you will achieve your goal. In your case, try the following:
SELECT id, saldo, saldo_type FROM `investitions`
ORDER BY (CASE WHEN saldo_type = "1" THEN saldo else -saldo END) desc
Cast saldo to decimal
SELECT id, saldo, saldo_type FROM `investitions`
ORDER BY saldo_type desc,cast(replace(saldo,',','.') as decimal(4,2)) desc -- sort by highest earnings
Adjust the precision and scale according to your data.
Thank you very much. I was able to build a working query using a piece of each answer.
Query looks like this:
SELECT id, saldo, saldo_type FROM `investitions` ORDER BY (CASE WHEN saldo_type = "1" THEN cast(replace(saldo,',','.') as decimal(12,2)) else -cast(replace(saldo,',','.') as decimal(12,2)) END) desc

SQL for Game Scores & Prediction System

I am building a Hockey Sports score and prediction system using PHP/MySQL. Below are the system design.
I have a GAMES table where two team numbers and their score in the game is present.The columns from this table are as below.
ID ---- TEAM1 ---- SCORE1 ---- TEAM2 ---- SCORE2
1 70 1 73 2
2 74 0 70 1
3 74 0 73 0
I also have a PICKS table where the details related to user's game predictions are present. Users can guess which team will win in a game and that data is stored in this table. The columns from this table are as below. Each user can guess only once for each game.
ID ---- GAME ---- USER ---- TEAM ---- POINT
1 1 1 70 1
2 2 1 70 1
3 3 1 73 1
3 1 2 70 1
Based on the above available data, I am trying to build up the result where each user (column USER) should be awarded the points(column POINT) for each correct guess. The guess can be validated based on the scores from GAMES table. The final output should be like as below.
USER ---- POINTS ---- CORRECT GUESS COUNT ---- WRONG GUESS COUNT
1 1 1 2
2 0 0 1
The columns "CORRECT GUESS COUNT" and "WRONG GUESS COUNT" represent the total number of correct guess and wrong guess done by the user.
I have created a SQL Fiddle for the above tables with some sample data.
http://sqlfiddle.com/#!2/8d469/4/0
EDIT:
Some more inforamtion are below. It's possible that a game can be a
draw.
In that case the score will be 0 for each team. When a game is
draw, users get no points.
SELECT p.user,
SUM(IF(g.id IS NOT NULL, p.point, 0)) As points,
SUM(IF(g.id IS NOT NULL, 1, 0)) Correct,
SUM(IF(g.id IS NULL, 1, 0)) Wrong
FROM Games g
RIGHT JOIN Picks p ON g.id = p.game AND
p.team = IF(g.score1 > g.score2 , g.team1, IF(g.score1 < g.score2, g.team2, NULL))
GROUP BY p.user;
SQL Fiddle (with your data)
You'll have to forgive me, if there is a more MySQL way to do it than this (background is Oracle/SQL Server):
SELECT
p.user
,sum(CASE
WHEN p.team = g.winner THEN point ELSE 0 END) points
,sum(CASE
WHEN p.team = g.winner THEN 1 ELSE 0 END) good_guess
,sum(CASE
WHEN p.team <> g.winner THEN 1 ELSE 0 END) bad_guess
FROM
picks p
INNER JOIN (
SELECT
id game_id
,CASE
WHEN score1 > score2 THEN team1
WHEN score2 > score1 THEN team2
ELSE -1 --no team_id as negative
END winner
FROM
games
) g
ON
g.game_id = p.game
GROUP BY
p.user

Convert table of individual events-distances into a summary table of events

Two weeks ago I started learning SQL and it has been going pretty good so far but I have run into a situation that I can't seem to resolve. After two days of searching the web and looking at books, I am no closer to solving this issue, so time to ask for some help. Part of my problem is that being so new to SQL I'm not exactly sure what to search for.
This is using mySQL and INNODB.
After some joins and other things I have the table below with athlete information giving the type of event in which the athlete participated and the distance of that event. The possible event/distance combinations are {A10,A15,B10,B15}.
Events Table
last_name first_name event distance
Munster Eddie A 10
Brady Marsha A 10
Clampet Jethro B 15
Grumby Jonas A 15
Brady Peter A 10
Brady Marsha A 10
Brady Marsha B 15
Grant Ginger B 15
Munster Eddie B 10
Brady Marsha A 10
What I am trying to do as the final step is to transform this table into a form that shows how many times each athlete participated in each event, like the following output:
last_name first_name A10 A15 B10 B15
Munster Eddie 1 0 1 0
Brady Marsha 3 0 0 1
Clampet Jethro 0 0 0 1
Grumby Jonas 0 1 0 0
Brady Peter 1 0 0 0
Grant Ginger 0 0 0 1
I think I want to use correlated subqueries, so I have tried a number variants of this following SQL query but it returns "Operand should contain 1 column(s)", which makes sense.
SELECT last_name, first_name,
count(if(event='A',1,0) AND if(distance=10,1,0)) AS A10
FROM sample s
WHERE (SELECT last_name, first_name, event, distance
FROM sample s1
WHERE s1.last_name = s.last_name
)
ORDER BY last_name, first_name;
The steps I see I need are:
1. create a set of each name in the table, which I can do, and then
2. iterate through each name, creating a new query selecting event/distance and then
3. summing that query on event/distance combination;
4. return the result back up to #1.
I see that procedures provide some looping capabilities, is that the way to do this? Is what I want to do possible in the SQL environment? My next step is to just dump the raw table to PHP and process it there.
Any thoughts and/or solutions are greatly appreciated.
Add GROUP BY:
SELECT last_name,
first_name,
count(if(event = 'A', 1, NULL)
AND if(distance = 10, 1, NULL)) AS A10,
count(if(event = 'A', 1, NULL)
AND if(distance = 15, 1, NULL)) AS A15,
count(if(event = 'B', 1, NULL)
AND if(distance = 10, 1, NULL)) AS B10,
count(if(event = 'B', 1, NULL)
AND if(distance = 15, 1, NULL)) AS B15
FROM sample s
GROUP BY last_name,
first_name
ORDER BY last_name,
first_name;
I would suggest using the SUM + CASE syntax.
Try something like this:
SELECT LAST_NAME,
FIRST_NAME,
SUM(CASE
WHEN EVENT = 'A'
AND DISTANCE = 10 THEN 1
ELSE 0
END) AS A10,
SUM(CASE
WHEN EVENT = 'A'
AND DISTANCE = 15 THEN 1
ELSE 0
END) AS A15,
SUM(CASE
WHEN EVENT = 'B'
AND DISTANCE = 10 THEN 1
ELSE 0
END) AS B10,
SUM(CASE
WHEN EVENT = 'B'
AND DISTANCE = 15 THEN 1
ELSE 0
END) AS B15
FROM SAMPLE
GROUP BY LAST_NAME,
FIRST_NAME
You can find a working example on SQL Fiddle.
Good Luck!

Two mysql counts with two different where conditions in one query

I have a table with two columns namely teacherid and sub_group. Now, sub_group can have values ranging from 0-100 where 0 means lectures for teachers and anything greater than 0 means tutorials. I want to be able to calculate the number of lectures and tutorials grouped by teachers.
So far i have two queries like
SELECT teacherid, count(*) as lectures FROM `ttresponsibility` where sub_group = 0
group by teacherid
SELECT teacherid, count(*) as tutorials FROM `ttresponsibility` where sub_group > 0
group by teacherid
I want to combine the results of the two into one resultset, something like
teacher lectures tutorials
1 15 10
2 14 8
Please suggest...
You can use the aggregate function with a CASE expression:
select teacherid,
sum(case when sub_group = 0 then 1 else 0 end) lectures,
sum(case when sub_group > 0 then 1 else 0 end) tutorials
from `ttresponsibility`
group by teacherid;
This will give you 3 columns, the teacherId and then the total lectured and tutorials in separate columns.
This relies on COUNT ignoring NULLs (the missing ELSE in the CASE expression)
SELECT
teacherid,
count(CASE WHEN sub_group = 0 THEN 1 END) as lectures
count(CASE WHEN sub_group > 0 THEN 1 END) as tutorials
FROM
`ttresponsibility`
group by teacherid
You can use CASE statement to get what you want here.
SELECT
teacherid,
SUM(CASE WHEN sub_group = 0 THEN 1 ELSE 0 END CASE) as lectures,
SUM(CASE WHEN sub_group > 0 THEN 1 ELSE 0 END CASE) as tutorials,
FROM ttresponsibility
GROUP BY teacherid
Almost same solution, as other guys offered, but with IF function:
SELECT
teacherid,
SUM(IF(sub_group = 0, 1, 0)) as lectures,
SUM(IF(sub_group > 0, 1, 0)) as tutorials,
FROM ttresponsibility
GROUP BY teacherid
For me it's easier to read

How to optimize this in MySQL?

I have table structure as displayed in first table.
And want to fetch Both Male and Female Counts in a single query so that request will go only for one time onto the server.
This is what you need to do:
select gender,
count(case when age between 0 and 20 then 1 else null end) Age_0_20,
count(case when age between 21 and 40 then 1 else null end) Age_21_40
from yourtable
group by gender
Adjust accordingly :)
Update, with clarifications
Note that COUNT aggregate function only counts non-null values. Thus, the else values in the case must be NULL. The When value returns 1 but it could just be any non-null value.
Some people implement this by using SUM:
select gender,
sum(case when age between 0 and 20 then 1 else 0 end) Age_0_20,
sum(case when age between 21 and 40 then 1 else 0 end) Age_21_40
from yourtable
group by gender
The result is going to be absolutely the same.