I will preface this by saying I am still very much learning MySQL, and I am absolutely at that stage where I know just enough to be dangerous.
I have a database with data for scorekeeping for a sports league. We record wins/losses as either 1 or zero points. There is a night that has double play involved (meaning the players play twice in a single night, for 2 different formats). My data is structured like so (just a sample, I have hundreds of rows, over different formats):
ID
FID
WK
Type
HomeTeam
AwayTeam
HF1
HF2
AF1
AF2
1
44
1
PL
TM1
TM2
1
0
0
1
2
44
1
PL
TM3
TM4
0
0
1
1
3
44
2
PL
TM2
TM3
1
1
0
0
4
44
2
PL
TM4
TM1
0
1
1
0
5
44
3
PL
TM3
TM1
999
0
999
1
6
44
3
PL
Tm2
TM4
1
0
0
1
Where the 999 is used as a code number for us to know that the match hasn't yet been played, or the scoresheet hasn't been turned in to us for recordkeeping. (I use PHP to call these to a website for users to see what is going on, and am using an IF statement to convert that 999 to "TBD" on the website)
I can pull the Format 1 and Format 2 scores separately and get a listing just fine, but when I try to pull them together and get a total score, I am getting an incorrect count. I know the error lies with my WHERE Clause, but I've been banging my head trying to get it to work correctly, and I think I just need an extra set of eyes on this.
My current SQL Query is as follows:
SELECT Team,
SUM(TotalF1) AS TotalF1,
SUM(TotalF2) AS TotalF2,
SUM(TotalF1+TotalF2) AS Total
FROM ( ( SELECT HomeTeam AS Team,
HF1 AS TotalF1,
HF2 AS TotalF2
FROM tbl_teamscores
WHERE FID = 44
AND Type = 'PL'
AND HF1 != 999
AND HF2 != 999 )
UNION ALL
( SELECT AwayTeam,
AF1,
AF2
FROM tbl_teamscores
WHERE FID = 44
AND Type = 'PL'
AND AF1 != 999
AND AF2 != 999 )
) CC
GROUP BY Team
ORDER BY Total desc, Team ASC;
I am getting incorrect totals though, and I know the reason is because of those 999 designations, as the WHERE clause is skipping over ALL lines where either home or away score matches 999.
I tried separating it out to 4 separate Select Statements, and unioning them, but I just get an error when I do that. I also tried using Inner Join, but MySQL doesn't seem to like that either.
Edit to add DBFiddle with Real World Table Data and queries: https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=1d4d090b08b8280e734218ba32db6d88
An example of the problem can be observed when looking at the data for Player 10. The overall total should be 13, but I am only getting 12.
Any suggestions would be very helpful.
Thanks in advance!
You can use conditional aggregation:
SELECT Team,
SUM(CASE WHEN Total8 <> 999 THEN Total8 END) AS Total8,
SUM(CASE WHEN TotalLO <> 999 THEN TotalLO END) AS TotalLO,
SUM(CASE WHEN Total8 <> 999 THEN Total8 END) + SUM(CASE WHEN TotalLO <> 999 THEN TotalLO END) AS Total
FROM (
SELECT HomeTeam AS Team, Home8PTS AS Total8, HomeLOPTS AS TotalLO FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
UNION ALL
SELECT AwayTeam, Away8PTS, AwayLOPTS FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
) CC
GROUP BY Team
ORDER BY Team ASC;
or:
SELECT Team,
SUM(NULLIF(Total8, 999)) AS Total8,
SUM(NULLIF(TotalLO, 999)) AS TotalLO,
SUM(NULLIF(Total8, 999)) + SUM(NULLIF(TotalLO, 999)) AS Total
FROM (
SELECT HomeTeam AS Team, Home8PTS AS Total8, HomeLOPTS AS TotalLO FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
UNION ALL
SELECT AwayTeam, Away8PTS, AwayLOPTS FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
) CC
GROUP BY Team
ORDER BY Team ASC;
If you get nulls in the results then you should also use COALESCE():
SELECT Team,
COALESCE(SUM(NULLIF(Total8, 999)), 0) AS Total8,
COALESCE(SUM(NULLIF(TotalLO, 999)), 0) AS TotalLO,
COALESCE(SUM(NULLIF(Total8, 999)), 0) + COALESCE(SUM(NULLIF(TotalLO, 999)), 0) AS Total
FROM (
SELECT HomeTeam AS Team, Home8PTS AS Total8, HomeLOPTS AS TotalLO FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
UNION ALL
SELECT AwayTeam, Away8PTS, AwayLOPTS FROM tbl_teamscores WHERE FID = 44 AND Type = 'PL'
) CC
GROUP BY Team
ORDER BY Team ASC;
See the demo.
Related
Let me start explaining this with an example, I have a table with records of matches played in a soccer league, by using this table and its matches results am able to generate a standings table for the teams in this league via a mysql query.
Table [matches] (example)
--------------------------------------------------------
|id | hometeam |goalsfor|goalsagainst| awayteam |
--------------------------------------------------------
--------------------------------------------------------
| 8 | Real Madrid | 2 | 0 | Inter Milan |
--------------------------------------------------------
| 9 | Inter Milan | 3 | 3 | Real Madrid |
--------------------------------------------------------
Generated standings by query
Pos Team Pld W D L F A GD Pts
1 FC Barcelona 5 2 3 0 8 5 3 9
2 Inter Milan 6 2 2 2 11 10 1 8
3 Real Madrid 6 2 2 2 8 8 0 8
4 AC Milan 5 0 3 2 8 12 -4 3
The query:
select
team,
count(*) played,
count(case when goalsfor > goalsagainst then 1 end) wins,
count(case when goalsagainst> goalsfor then 1 end) lost,
count(case when goalsfor = goalsagainst then 1 end) draws,
sum(goalsfor) goalsfor,
sum(goalsagainst) goalsagainst,
sum(goalsfor) - sum(goalsagainst) goal_diff,
sum(
case when goalsfor > goalsagainst then 3 else 0 end
+ case when goalsfor = goalsagainst then 1 else 0 end
) score
from (
select hometeam team, goalsfor, goalsagainst from scores
union all
select awayteam, goalsagainst, goalsfor from scores
) a
group by team
order by score desc, goal_diff desc;
What I want to do is to order the standings based on Head to Head matches, so it would first order by points, then if there's a draw in points the second sorting would be to look at the two teams matches and compare who has more wins, or scored more than the other, then use that to sort the table.
By doing this as in the example Real Madrid will become ranked as 2nd and then Inter Milan as 3rd.
How can I achieve this?
I want to compare the two teams matches when they are equal in points, and use that to sort.
ORDER BY score DESC, h2h DESC; goal_diff DESC
Update: I ended going with a solution mix of sql and php, first I find equaled teams in rank, and then generate mini h2h standings for those team and update the rank based on it. I still see this doable with just sql, but with my heavy query its too complicated to implement with just sql, thats why I mixed with php in the implementation.
You need to process this in two steps. First, run the query above and store the results in a work table (call it work below). Then you need to get a tie breaker score for each team that is on the same score. Below, I join the matches table to the work table for each team, and ignore any where the work rows do not have the same score, as they are not important. Then give the team 1 if they won. Have to do it again for the other side. You might want to change this to the 3 for win, 1 for draw.
Sum these results up, join that result to the team row in work, and you have a tie break score for each row where where the score is the same.
You need to check what happens if you have many teams on the same score, and see if this is the result you want.
select w.*, b.hth
From work w
left outer join (
select team, SUM(hth) hth
from (
Select hometeam team, case when m.goalsfor > m.goalsagainst then 1 else 0 end hth
from matches m
inner join work w1 on m.hometeam = w1.team
inner join work w2 on m.awayteam = w2.team
where w1.score = w2.score
union all
Select awayteam team, case when m.goalsAgainst > m.goalsFor then 1 else 0 end hth
from matches m
inner join work w1 on m.hometeam = w1.team
inner join work w2 on m.awayteam = w2.team
where w1.score = w2.score
) a --all hth at same points
group by team
) b --summed to one row per team
on b.team = w.team
order by w.score desc, b.hth desc;
For example purposes lets say Im trying to figure out the average score for males and females from each parent.
Example data looks like this:
parentID childID sex score
------------------------------------
1 21 m 17
1 23 f 12
2 33 f 55
2 55 m 22
3 67 m 26
3 78 f 29
3 93 m 31
This is the result I want:
parentID offspring m f avg-m avg-f avg-both
----------------------------------------------------
1 2 1 1 17 12 14.5
2 2 1 1 22 55 38.5
3 3 2 1 28.5 29 28.67
With the below query I can find the average for both males and females but I'm not sure how to get the average for either male or female
SELECT parentID, COUNT( childID ) AS offspring, SUM( IF( sex = 'm', 1, 0 ) ) AS m, SUM( IF( sex = 'f', 1, 0 ) ) AS f, max(score) as avg-both
FROM sexb_1
WHERE avg-both > 11
GROUP BY parentID
I tried something like this in the query but it returns an error
AVG(IF(sex = 'm', max(score),0)) as avg-m
I tried something like this in the query but it returns an error
AVG(IF(sex = 'm', max(score),0)) as avg-m
You can't use one aggregate function within another (in this case, MAX() within AVG())—what would that even mean? Once one has discovered the MAX() of the group, over what is there to take an average?
Instead, you want to take the AVG() of score values where the sex matches your requirement; since AVG() ignores NULL values and the default for unmatched CASE expressions is NULL, one can simply do:
SELECT parentID,
COUNT(*) offspring,
SUM(sex='m') m,
SUM(sex='f') f,
AVG(CASE sex WHEN 'm' THEN score END) `avg-m`,
AVG(CASE sex WHEN 'f' THEN score END) `avg-f`,
AVG(score) `avg-both`
FROM sexb_1
GROUP BY parentID
HAVING `avg-both` > 11
See it on sqlfiddle.
Using if
SELECT parentID, COUNT( childID ) AS offspring,
SUM(iF( sex='m', 1 ,0 )) AS m,
SUM(iF( sex='f', 1 ,0 )) AS f,
AVG(if(sex='m', score, null)) as avg_m,
AVG(if(sex='f', score, null)) as avg_f,
AVG(score) as avgboth
FROM sexb_1
GROUP BY parentID
HAVING avgboth > 11
fiddle
In your query the error is due to the usage of avg-both You need to use back ticks or underscore for the alias name. Here it considers it as difference of avg and both
And also you cannot use alias names inside where clause as after the table name is picked up from the query, it is the where clause that comes next. So the database doesn't know the alias names yet.
You can try below query-
SELECT
parentID, COUNT(childID) AS `offspring`,
COUNT(IF(sex = 'm',sex ,NULL )) AS `m`, COUNT(IF(sex = 'f', sex,NULL)) AS `f`,
AVG(IF(sex = 'm',score,NULL )) AS `avg-m`, COUNT(IF(sex = 'f', score,NULL)) AS `avg-f`,
AVG(score) AS `avg-both`
FROM sexb_1
GROUP BY parentID
HAVING `avg-both` > 11;
/*-------------temp table--------------*/
CREATE TEMPORARY TABLE IF NOT EXISTS bowlpick.tempschedule AS
(SELECT * FROM bowlpick.schedule
where season = (select max(year) from bowlpick.season));
/*-------------temp table--------------*/
set #g1= (select winner from bowlpick.tempschedule where game =1);
select date,name, #g1,bowl1 Pick,comf1
,case
when bowl1 = #g1 then comf1
when bowl1 <> #g1 then -comf1
else '0' end pts
from bowlpick.picks
where year(date) = (select max(year) from bowlpick.season)
order by bowl1 desc limit 5
<--------------------Table----------------->
name #g1 Pick comf1 pts
Player 1 Boise State Washington 7 -7
Player 2 Boise State Boise State 30 -30
Player 3 Boise State Boise State 21 -21
Player 4 Boise State Boise State 27 -27
Player 5 Boise State Boise State 15 -15
Why does this not work??? The last four pts should be positive numbers, not negative.
It as if the variable (#g1) is not working in the 'CASE' part. It works correctly when the 'select' part.
If I hard-code the winner (Boise State), it works correctly. Does variables not work in case statements?
Not sure why you are using temporary tables, you could do it in a single query, see the following example for somewhere to start, one other thing I noticed was that the case was returning numbers for the two case statements and a string for the else. Not only this the case statement has 2 WHEN statements which cover all bases surely meaning '0' would have never been selected anyway.
SELECT
bowlpick.picks.date
,bowlpick.picks.name
,bowlpick.tempschedule.winner
,bowlpick.picks.bowl1 Pick
,bowlpick.picks.comf1
,CASE
WHEN bowlpick.picks.bowl1 = bowlpick.tempschedule.winner THEN bowlpick.picks.comf1
WHEN bowlpick.picks.bowl1 <> bowlpick.tempschedule.winner THEN -bowlpick.picks.comf1
ELSE 0
END pts
FROM
bowlpick.picks
LEFT JOIN bowlpick.tempschedule ON year(bowlpick.picks.date) = bowlpick.tempschedule.season
WHERE
bowlpick.tempschedule.game =1
ORDER BY
bowl1 DESC
LIMIT 5
The following is an additional bit of work removing the need for the temporary table (links and grouping would need better defining but with an incomplete schema it's the best I could do)
SELECT
p.Date
,p.Name
,s.Winner
,p.Bowl1 Pick
,p.Comf1
,CASE
WHEN p.Bowl1 = s.Winner THEN p.Comf1
WHEN p.Bowl1 <> s.Winner THEN -p.Comf1
ELSE 0
END pts
FROM
picks p
LEFT JOIN schedule s ON YEAR(p.Date) = s.Season
WHERE
s.game = 1
GROUP BY
s.Season, p.Date
HAVING
s.Season = MAX(s.Season)
ORDER BY
p.Bowl1 DESC
LIMIT 5
He's my SQL Fiddle used to do this
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
Hi I am trying to figure out a way of finding the largest winning streak for each member in my table. When the table was built, this was never in the plans to happen so is why Im seeking help on how I can achieve this.
My structure is as follows:
id player_id opponant_id won loss timestamp
If it is a persons game, the player id is their id. If they are being challenged by someone, their id is the opponant id and the won loss (1 or 0) is in relation to the player_id.
I want to find the greatest winning streak for each user.
Anyone have any ideas on how to do this with the current table structure.
regards
EDIT
here is some test data, where id 3 is the player in question:
id player_id won loss timestamp
1 6 0 1 2012-03-14 13:31:00
13 3 0 1 2012-03-15 13:10:40
17 3 0 1 2012-03-15 13:29:56
19 4 0 1 2012-03-15 13:37:36
51 3 1 0 2012-03-16 13:20:05
53 6 0 1 2012-03-16 13:32:38
81 3 0 1 2012-03-21 13:14:49
89 4 1 0 2012-03-21 14:01:28
91 5 0 1 2012-03-22 13:14:20
Give this a try. Edited to take into account loss rows
SELECT
d.player_id,
MAX(d.winStreak) AS maxWinStreak
FROM (
SELECT
#cUser := 0,
#winStreak := 0
) v, (
SELECT
player_id,
won,
timestamp,
#winStreak := IF(won=1,IF(#cUser=player_id,#winStreak+1,1),0) AS winStreak,
#cUser := player_id
FROM (
(
-- Get results where player == player_id
SELECT
player_id,
won,
timestamp
FROM matchTable
) UNION (
-- Get results where player == opponent_id (loss=1 is good)
SELECT
opponent_id,
loss,
timestamp
FROM matchtable
)
) m
ORDER BY
player_id ASC,
timestamp ASC
) d
GROUP BY d.player_id
This works by selecting all win/loses and counting the win streak as it goes through. The subquery is then grouped by player_id and the max winStreak as calculated as it looped through is output per-player.
It seemed to work nicely against my test dataset anyway :)
To do this more efficiently I would restructure, i.e.
matches (
matchID,
winningPlayerID,
timeStamp
)
players (
playerID
-- player name etc
)
matchesHasPlayers (
matchID,
playerID
)
Which would lead to an inner query of
SELECT
matches.matchID,
matchesHasPlayers.playerID,
IF(matches.winningPlayerID=matchesHasPlayers.playerID,1,0) AS won
matches.timestamp
FROM matches
INNER JOIN matchesHasPlayers
ORDER BY matches.timestamp
resulting in
SELECT
d.player_id,
MAX(d.winStreak) AS maxWinStreak
FROM (
SELECT
#cUser := 0,
#winStreak := 0
) v, (
SELECT
matchesHasPlayers.playerID,
matches.timestamp,
#winStreak := IF(matches.winningPlayerID=matchesHasPlayers.playerID,IF(#cUser=matchesHasPlayers.playerID,#winStreak+1,1),0) AS winStreak,
#cUser := matchesHasPlayers.playerID
FROM matches
INNER JOIN matchesHasPlayers
ORDER BY
matchesHasPlayers.playerID ASC,
matches.timestamp ASC
) d
GROUP BY d.player_id
SELECT * FROM
(
SELECT player_id, won, loss, timestamp
FROM games
WHERE player_id = 123
UNION
SELECT opponant_id as player_id, loss as won, won as loss, timestamp
FROM games
WHERE opponant_id = 123
)
ORDER BY timestamp
That will give you all the results for one player ordered by timestamp. Then you would need to loop those results and count winning records or else concatenate them all into a string and then use string functions to find your highest 11111 set in that string. That code will vary depending on the language you want to use, but logically those are the two choices.