Group by in mysql [some stuff to foo the bot] - mysql

I have this query:
SELECT *,
COUNT(*) AS SeasonP,
SUM(Goals) AS SeasonG,
SUM(Disposals) AS SeasonD,
SUM(Kicks) AS SeasonK,
SUM(Handballs) AS SeasonHB,
SUM(Marks) AS SeasonM,
SUM(Behinds) AS SeasonB,
SUM(Tackles) AS SeasonT,
SUM(HitOuts) AS SeasonHO,
SUM(I50) AS SeasonI50,
((SELECT COUNT(*) FROM `PlayerDetails` WHERE PlayerID = $PlayerID AND WL LIKE '%W%' )) AS W,
((SELECT COUNT(*) FROM `PlayerDetails` WHERE PlayerID = $PlayerID AND WL LIKE '%L%' )) AS L,
((SELECT COUNT(*) FROM `PlayerDetails` WHERE PlayerID = $PlayerID AND WL='D' )) AS D
FROM `PlayerDetails` WHERE PlayerID = $PlayerID GROUP BY Season, Team, League
Which almost gives me the correct output - It correctly totals games, goals, kicks, marks etc per season. But what I'm trying to do is display the games won, lost and drawn for each season and for each team. So for eg. 2015 should read Won=12, Lost 5 so just adding the wins and losses for that season but it's displaying the total won/lost for all 32 games his played rather than breaking it down per season.
I'm sure its an easy fix to my query but I'm a bit stuck.

Instead of your sub-queries populating W,L,D, use mysql IF() function as described below,
SELECT *, COUNT(*) AS `SeasonP`, SUM(`Goals`) AS `SeasonG`, SUM(`Disposals`) AS `SeasonD`,
SUM(`Kicks`) AS `SeasonK`, SUM(`Handballs`) AS `SeasonHB`, SUM(`Marks`) AS `SeasonM`,
SUM(`Behinds`) AS `SeasonB`, SUM(`Tackles`) AS `SeasonT`, SUM(`HitOuts`) AS `SeasonHO`,
SUM(`I50`) AS `SeasonI50`,
SUM(IF(`WL` LIKE '%W%', 1, 0)) AS `W`,
SUM(IF(`WL` LIKE '%L%', 1, 0)) AS `L`,
SUM(IF(`WL` = 'D', 1, 0)) AS `D`,
FROM `PlayerDetails` WHERE `PlayerID` = $PlayerID GROUP BY `Season`, `Team`, `League`

The root of all evil is this one:
((SELECT COUNT(*) FROM `PlayerDetails` WHERE PlayerID = $PlayerID AND WL='...' ))
First of all, you should avoid a SELECT as a value at all, it causes severe performance issues, since it has to be executed for every row in your result - and this may get biiiiig.
In your case, there's no filter condition in the subquery - you always ask "how many games were won EVER", that's why you always get the total value. Your group-by gets skipped this way.
Since you already work with the wanted table "PlayerDetails", try to replace it with this:
sum(if(WL='W',1,0)) AS W,
sum(if(WL='D',1,0)) AS D,
sum(if(WL='L',1,0)) AS L
Think of it as a counter. For each row it checks the condition and - depending on the result - adds 1 or 0.

Related

MySQL - Column to Row

In the original problem, we have a table that stores the date and win/loss information for each game played by a team.
the matches table
We can use the following SQL statements to get information about the number of games won and lost for each day.
SELECT match_date AS match_date,
SUM(IF(result = 'win',1,0)) AS win,
SUM(IF(result = 'lose',1,0)) AS lose
FROM matches
GROUP BY date;
We store the query results in the matches_2 table.the matches_2 table
My question is, how can we get the matches table based on the matches_2 table with a query?
In the simpler case, we can achieve the task of 'column to row' using union/union all. But that doesn't seem to work in this problem.
All relevant sql code can be found in the following fiddle:
https://dbfiddle.uk/rM-4Y_YN
You can use recursive CTE for this:
WITH RECURSIVE wins (mdate, w) AS
(
SELECT match_date as mdate, 1
FROM matches
WHERE win>0
UNION ALL
SELECT match_date, w + 1 FROM matches
JOIN wins on match_date=mdate
WHERE w < win
),
losses (mdate, l) AS
(
SELECT match_date as mdate, 1
FROM matches
WHERE lose>0
UNION ALL
SELECT match_date, l + 1 FROM matches
JOIN losses on match_date=mdate
WHERE l < lose
)
SELECT mdate as match_date, 'lose' FROM losses
UNION ALL
SELECT mdate as match_date, 'win' FROM wins
See a db-fiddle

Sum values across multiple tables

I'm having trouble trying to sum a field GROUPED BY a common user ID from TWO DIFFERENT tables.
To give you a little more info... I am trying to track player performances by date (i.e.: most points scored on ALL Nov. 14's). The database is split, one table for regular season games and one table for playoffs. So, for example, a player may have played a regular season game on May 3, 2001....but a playoff game on May 3, 2005.
So, I'm trying to build a view with the sums of every player on all dates.
What I have for a single table:
SELECT PlayerId,sum(Points) as TOT_PTS
FROM RS_games
WHERE DAY(Date)=$cur_day
AND MONTH(Date)=$cur_month
GROUP BY PlayerId
...but I can't figure how I could sum the values of each player across two tables without creating a third view as a "stepping stone".
Any ideas?
If you want the results by (DAY-MONTH) you can do:
SELECT playerID,
CONCAT (DAY(DATE), '-', MONTH(DATE)) AS DAY_MONTH,
SUM(points) AS Total_Points
FROM (
SELECT playerID, DATE, points
FROM rs_games
UNION ALL
SELECT playerID, DATE, points
FROM po_games
) a
GROUP BY 1, 2
sqlfiddle demo
This way, you would end up with a result with every playerId,dd-mm, and the sum of points that were scored in that specific day across the years.
Just to lay out what I was saying:
select
ALL_players.PlayerID as PlayerID
COALESCE(reg.Points, 0) + COALESCE(po.Points, 0) as Points
from
ALL_players
left join
(select PlayerID, DATE(Date) as Date, sum(Points) as Points
from RS_games
WHERE DAY(Date)=$cur_day AND MONTH(Date)=$cur_month
group by PlayerID) as reg
on reg.PlayerID = ALL_players.PlayerID
left join
(select PlayerID, DATE(Date) as Date, sum(Points) as Points
from PO_games group by DATE(Date), PlayerID
WHERE DAY(Date)=$cur_day AND MONTH(Date)=$cur_month
group by PlayerID) as po
on po.PlayerID = ALL_players.PlayerID
EDIT: Looking again at requirements this will need either a full outer join or some adjustment... adjusting (should be working now)

Perform a mathematical function on a JOIN of 2 tables - each with a COUNT function

I'm trying to accomplish the following-
I have 2 tables for soccer teams (not created by me, this is what I have to work with):
won_matches-
columns: team_id | match_name | scored_goals
lost_matches-
columns: team_id | match_name | scored_goals
teams_names-
team_id | team_name
(I don't care about the match name or the number of scored goals)
What I need to do is COUNT how many entries each team has in the won_matches table and how many entries it has in the lost_matches table, and then divide the number of lost_matches by the number of won_matches, thus getting a lost/won matches ratio.
I then need to present this ratio for each team (or all teams) along with its team name.
I tried somethings like this, but it doesn't work as needed:
SELECT b. team_name, (SELECT COUNT(team_id)
FROM won_matches [***optional; WHERE team_id=37***]) / COUNT(a.team_id)*100 AS lost_won_ratio
FROM lost_matches a
join teams_names b on a.team_id=b.team_id
[***optional; WHERE a.team_id=37***]
Would be grateful for your suggestions.
Something like this should work.
SELECT tn.teamID, sum(won_matches.teamID ) as WON, sum(lost_matches.teamID ) as LOST,(sum(won_matches.teamID )/sum(lost_matches.teamID )) as WLratio
From teams_names AS tn LEFT JOIN won_matches ON tn.teamID = won_matches.teamID LEFT JOIN lost_matches ON tn.teamID = lost_matches.teamID
Try something like this:
select team_id, Won, count(*) as Matches, sum(scored_goals) as Goals
from
(select 1 as Won, Team_id, scored_goals from won_matches
union all
select 0 as Won, team_id, scored_goals from lost_matches) x
group by team_id, Won
I think something like that will do the trick:
select team_id,
count(won), count(lost),
count(won)/(count(won)+count(lost)) as 'Win ratio' from
(
select True as won, NULL as lost, won_matches.* from won_matches
union all
select NULL as won, True as lost, lost_matches.* from lost_matches
) as S group by team_id
http://sqlfiddle.com/#!2/6dbaf/2 (EDIT: use a join to display team name)
Please notice I don't take into account possible drawn matches since I don't know how this is stored in your DB.
EDIT Notice as well I used count(won)/(count(won)+count(lost)) as count ratio formula. That seems more logical. If you stick with count(lost)/count(win) you will have to deal with the divide by 0 case...

Average on a count() in same query

I'm currently working on an assignment which requires me to find the average on the number of resources for each module. The current table looks like this:
ResourceID ModulID
1 1
2 7
3 2
4 4
5 1
6 1
So basically, I'm trying to figure out how to get the average number of resources. The only
relevant test data here is for module 1, which has 3 different resources connected to it. But I need to display all of the results.
This is my code:
select avg(a.ress) GjSnitt, modulID
from
(select count(ressursID) as ress
from ressursertiloppgave
group by modulID) as a, ressursertiloppgave r
group by modulID;
Obviously it isn't working, but I'm currently at loss on what to change at this point. I would really appreciate any input you guys have.
This is the query you are executing, written in a slightly less obtuse syntax.
SELECT
avg(a.ress) as GjSnitt
, modulID
FROM
(SELECT COUNT(ressursID) as ress
FROM ressursertiloppgave
GROUP BY modulID) as a
CROSS JOIN ressursertiloppgave r <--- Cross join are very very rare!
GROUP BY modulID;
You are cross joining the table, making (6x6=) 36 rows in total and condensing this down to 4, but because the total count is 36, the outcome is wrong.
This is why you should never use implicit joins.
Rewrite the query to:
SELECT AVG(a.rcount) FROM
(select count(*) as rcount
FROM ressursertiloppgave r
GROUP BY r.ModulID) a
If you want the individual rowcount and the average at the bottom do:
SELECT r1.ModulID, count(*) as rcount
FROM ressursertiloppgave r1
GROUP BY r1.ModulID
UNION ALL
SELECT 'avg = ', AVG(a.rcount) FROM
(select count(*) as rcount
FROM ressursertiloppgave r2
GROUP BY r2.ModulID) a
I got the solution
SELECT AVG(counter)
FROM
(
SELECT COUNT(column to count) AS counter FROM table
) AS counter
Note that the nickname {counter} was added in SELECT COUNT and at the end of the inner SELECT

sql and getting elements around a specific one

My sql database stores the highscores for the players of a game like this:
playerName
scores
To show the scores I'm trying to create a query that returns for a playerId "joe" the positon of joe as well as one player with more and less points than joe:
(rank) (playerName) (scores)
5000 luci 2001
5001 joe 1900
5002 marc 1750
(I added the rank here just to make it more clear. It isn't stored and represents just the position in the list when sorting by the scores)
Is there a way to do soemthing like this with a query without getting half of the database as result?
You'd ideally want to be using window functions to do this, namely row_number(), lead() and lag().
These are not available in MySQL, but here are a few workarounds (also row_number(), picked up from the same author).
Using window functions:
with
players as (
select row_number() over w as rank,
playername,
scores
from players
window w (order by scores desc)
),
player as (
select rank
from players
where playername = 'joe'
)
select rank,
playername,
scores
from players
join player on players.rank between player.rank - 1 and player.rank + 1
Alternatively (slightly faster):
with
player_range as (
select scores - 100 as scores -- or whatever is large enough based on your stats
from players
where playername = 'joe'
),
players as (
select row_number() over w as rank,
players.playername,
players.scores
from players
join player_range
on player_range.scores <= players.scores
window w (order by scores desc)
),
player as (
select rank
from players
where playername = 'joe'
)
select rank,
playername,
scores
from players
join player on players.rank between player.rank - 1 and player.rank + 1
To determine joe's rank you have to query the db and order by scores.