MySQL rating/ladder system - mysql

I have two tables:
*tbl_MATCHES*
id
contestant_1
contestant_2
winner
*tbl_CONTESTANTS*
id
name
The goal is to get each contestant's win percentage. That is, count how many matches each contestant won, and divide that by the total number of matches each contestant took part in. This is currently working, but it seems cumbersome:
SELECT all_matches.contestant_id, num_matches, num_wins,
(num_wins / num_matches * 100.0) AS win_percent FROM (
// get total number of wins for each contestant
SELECT contestants.id AS contestant_id,
COUNT(matches.winner) AS num_wins
FROM contestants
LEFT JOIN matches
ON matches.winner = contestants.id
GROUP BY matches.winner
) all_matches
// join total number of wins to total number of matches played
JOIN (
SELECT contestant_id, COUNT(contestant_id) AS num_matches FROM (
// get list of all matches played by all contestants
SELECT contestants.id AS contestant_id
FROM matches
JOIN contestants ON contestants.id = matches.contestant_1
UNION ALL
SELECT contestants.id AS contestant_id
FROM matches
JOIN contestants ON contestants.id = matches.contestant_2
) all_m
GROUP BY contestant_id
) all_matches
ON all_matches.contestant_id = all_matches.contestant_id
ORDER BY win_percent DESC
I feel like this sort of thing must have been done before, and I'm looking for some help optimizing this or a link to a better one someone has already done

I would try this approach:
SELECT
contestants.id AS contestant_id,
COUNT(*) AS num_matches,
SUM( CASE WHEN matches.winner = contestants.id THEN 1 ELSE 0 END )
AS num_wins,
SUM( CASE WHEN matches.winner = contestants.id THEN 1 ELSE 0 END )
/ COUNT(*) * 100 AS win_percent
FROM matches
JOIN contestants
ON contestants.id IN( matches.contestant_1, matches.contestant_2 )
GROUP BY contestants.id

Related

Count three posibilities in one table

I have a table with subscribers which have two fields, actived and suscribed. I need to get the total number of subscribers, the number of activated and the number of suscribed in the same consult. I've tried to do this with a double left join but i need to group by a field that is not the primary key and i get error. I have my consult now like this:
FROM (SELECT subscribers.mailing_list, subscribers.mailing_list AS suscriptores, s2.inactivos AS inactivos, s3.excluidos AS excluidos
FROM subscribers
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS inactivos FROM subscribers WHERE subscribed = false GROUP BY id) s2 ON subscribers.id = s2.id
LEFT JOIN (SELECT id, mailing_list, COUNT(*) AS excluidos FROM subscribers WHERE excluded = true GROUP BY id) s3 ON subscribers.id = s3.id
) AS subs
GROUP BY subs.mailing_list```
Instead of needing 3 queries, you need case expressions inside an aggregate function, this is known as "conditional aggregates" e.g.
SELECT
mailing_list
, COUNT( CASE WHEN subscribed = FALSE THEN 1 END ) AS inactivos
, COUNT( CASE WHEN subscribed = TRUE THEN 1 END ) AS excluidos
, COUNT( * ) AS Total
FROM subscribers
GROUP BY
mailing_list
In MySQL, you can simplify this logic to:
SELECT mailing_list,
SUM( subscribed = FALSE ) AS inactivos,
SUM( subscribed = TRUE ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;
If subscribed is in fact boolean, you can further simplify this to:
SELECT mailing_list,
SUM( NOT subscribed ) AS inactivos,
SUM( subscribed ) AS excluidos,
COUNT( * ) AS Total
FROM subscribers
GROUP BY mailing_list;

Finding top 5 results for multiple values in sql result

I have the following sql query:
SELECT v.venue_id, s.zip, COUNT( * )
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
ORDER BY COUNT( * ) DESC
Which returns the count of individual zip codes, their count, and associated venue.
How do I go about selecting the top 5 zip codes for each unique venue id?
I believe I can run a subquery that groups results by venue id with the top 5 zip counts, but I am unsure of where to start
Could be you select the result in this way ... a bit complex ..
using the having for extract the value that match the max count group by venue_id from your original query ..
SELECT v.venue_id as venue_id, s.zip as , COUNT( * ) as num
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
HAVING ( v.venue_id, COUNT( * )) in
(select venue_id, max(num)
from
(SELECT v.venue_id as venue_id, s.zip as , COUNT( * ) as num
FROM bcs_scans s
JOIN bcs_scanners sc ON s.uuid = sc.uuid
JOIN bcs_venues v ON sc.venue_id = v.venue_id
WHERE v.banlist_id = '625'
AND s.del =0
GROUP BY s.zip
ORDER BY COUNT( * ) DESC ) a t
group by venue_id)
ORDER BY COUNT( * ) limit 5

Select table two times - not working

Actually I have the following database schema:
* events
id type player match
* players
id name
* matches
id date
The event type can be "A" for assistances or "G" for goals. I need to select the two types in the same query to calculate an offensive production (assistances + goals * 2).
The final result need to be:
year player assistances goals off_production
I have tried the following query, but it's not working (it's returning wrong data):
SELECT COUNT(assist.id) AS assistances, COUNT(goals.id) AS goals, (assistances + goals * 2) AS off_prod, p.name AS player, YEAR(m.date) AS year
FROM matches AS m, players AS p, events AS assist, events AS goals
WHERE assist.match = m.id AND
assist.type ="A" AND
assist.player = p.id AND
goals.match = j.id AND
goals.type ="G" AND
goals.player = p.id
GROUP BY year, player
ORDER BY year DESC, off_prod DESC
Thanks in advance
Try this....
SELECT assistances
,goals
,(A.assistances + A.goals * 2) AS off_prod
,player
,`YEAR`
FROM (
SELECT COUNT( CASE WHEN e.`type` ='A' THEN 1 ELSE NULL END) AS assistances
,COUNT( CASE WHEN e.`type` ='G' THEN 1 ELSE NULL END) AS goals
,p.`Name` AS player
,YEAR(m.`date`) AS `year`
FROM `players` AS p
INNER JOIN `events` AS e ON p.`id` = e.`player`
INNER JOIN `matches` AS m ON e.`match` = m.`id`
GROUP BY p.`name`, YEAR(m.`date`)
) A
ORDER BY A.`year` DESC, (A.assistances + A.goals * 2) DESC
Working Sql Fiddle

How do I group SQL Server rows in Table A by date in Table B

This is simple in theory, but difficult for me to figure out.
I have two SQL Server tables:
List of purchases with purchase date and total
select total, date from purchases
list of miles traveled for a specific job and date of travel
select travelDate, miles from trips
EDIT: To keep the answer/discussion on my question, I am rephrasing the requirement.
I need to figure out the total miles driven between each purchase.
I want more accuracy than an overall average.
I can manually get this by summing all miles from trips in between each purchases date. Now, I just want to automate the process.
The grouping should be such that all trips dates greater than purchases date A and less than purchases date B are part of the purchases date A group.
Curing my denseness, I see that your request is quite reasonable by treating the problem as "replacement fuel cost"—thus using the fuel cost of the next fueling rather than the previous cost to buy the fuel actually used (which gets really complicated, really fast). Volume then doesn't matter. Try this on for size.
SELECT
T.*,
P.*, -- from previous purchase
N.*, -- from next purchase (NULL if none yet)
TripCost = N.Total * T.Miles / M.MilesThisFill
FROM
dbo.Trips T
CROSS APPLY (
SELECT TOP 1 *
FROM dbo.Purchases P
WHERE P.[Date] < T.travelDate
ORDER BY P.[Date] DESC
) P
CROSS APPLY (
SELECT TOP 1 *
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
) N
CROSS APPLY (
SELECT Sum(miles) MilesThisFill
FROM dbo.Trips T2
WHERE
T2.[Date] > P.[Date]
AND T2.[Date] < N.[Date]
) M;
Or here's a version that thinks very differently about the problem but should give the same result. Let me know which one performs better, would ya? (SET STATISTICS IO ON; SET STATISTICS TIME ON;)
WITH PSeq AS (
SELECT
Seq = Row_Number() OVER (ORDER BY [Date]),
*
FROM dbo.Purchases
), Slices AS (
SELECT
FromDate = P.[Date],
ToDate = N.[Date],
N.Total
FROM
PSeq P
INNER JOIN PSeq N
ON P.Seq + 1 = N.Seq
), TotalMiles AS (
SELECT
S.FromDate,
Sum(T.Miles) MilesThisFill
FROM
Slices S
INNER JOIN dbo.Trips T
ON T.travelDate BETWEEN S.FromDate AND S.ToDate
GROUP BY
S.FromDate
)
SELECT
T.travelDate,
S.FromDate,
S.ToDate,
TripCost = S.Total * T.Miles / M.MilesThisFill
FROM
Slices S
INNER JOIN dbo.Trips T
ON T.travelDate BETWEEN S.FromDate AND S.ToDate
INNER JOIN dbo.TotalMiles M
ON S.FromDate = L.FromDate;
I apologize in advance for any typos or errors... I haven't tested the code.
And just for laughs, here's the first query transmogrified into a version that would work even on SQL Server 2000!
SELECT
T.travelDate,
T.Miles,
T.ToDate,
TripCost = P.Total * T.Miles / M.MilesThisFill
FROM
(
SELECT
T.travelDate,
T.Miles,
ToDate = (
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
)
FROM
dbo.Trips T
) T
INNER JOIN (
SELECT
ToDate = (
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T2.travelDate
ORDER BY P.[Date]
),
MilesThisFill = Sum(T2.Miles)
FROM dbo.Trips T2
GROUP BY
(
SELECT TOP 1 P.Date
FROM dbo.Purchases P
WHERE P.[Date] > T2.travelDate
ORDER BY P.[Date]
)
) M ON T.ToDate = M.ToDate
INNER JOIN dbo.Purchases P
ON T.ToDate = P.[Date];
This actually exposes that I might not need to look up the previous purchase date in my first query, if I do it right... so here's a final version:
WITH TripData AS (
SELECT
T.Miles,
T.travelDate,
ToDate = (
SELECT TOP 1 P.[Date]
FROM dbo.Purchases P
WHERE P.[Date] > T.travelDate
ORDER BY P.[Date]
)
FROM
dbo.Trips T
)
SELECT
T.*,
P.*,
TripCost = P.Total * T.Miles / M.MilesThisFill
FROM
TripData T
INNER JOIN dbo.Purchases P
ON T.ToDate = P.[Date]
INNER JOIN (
SELECT
T2.ToDate,
Sum(T2.Miles) MilesThisFill
FROM TripData T2
GROUP BY
T2.ToDate
) M ON T.ToDate = M.ToDate;
Note: the order of the TripCost expression is important, because Miles and TotalMiles are integers. If you put P.Total last, you will get wrong answers because Miles / TotalMiles will be converted to an integer.

What's wrong on this query?

I'm selecting total count of villages, total count of population from my tables to build statistics. However, there is something wrong. It returns me everything (530 pop (there are 530 pop in total), (106 villages (there are 106 users in total)) in first row, next rows are NULLs
SELECT s1_users.id userid, (
SELECT count( s1_vdata.wref )
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
)totalvillages, (
SELECT SUM( s1_vdata.pop )
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
)pop
FROM s1_users
WHERE s1_users.dp >=0
ORDER BY s1_users.dp DESC
Try removing s1_users from inner SELECTS
You're already using INNER JOINs. Whan you list tables separated with comma, it is a shortcut for INNER JOIN.
Now, the most obvious answer is that your subqueries using aggregating functions (COUNT and SUM) are missing a GROUP BY clauses.
SELECT s1_users.id userid, (
SELECT count( s1_vdata.wref )
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
GROUP BY s1_vdata.owner
)totalvillages, (
SELECT SUM( s1_vdata.pop )
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
GROUP BY s1_vdata.owner
)pop
FROM s1_users
WHERE s1_users.dp >=0
ORDER BY s1_users.dp DESC
However, using subqeries in column list is really inefficient. It casues subqueries to be run once for each row in outer query.
Try like this instead
SELECT
s1_users.id AS userid,
COUNT(s1_vdata.wref) AS totalvillages,
SUM(s1.vdata.pop) AS pop
FROM
s1_users, s1_vdata --I'm cheating here! There's hidden INNER JOIN in this line ;P
WHERE
s1_users.dp >= 0
AND s1_users.id = s1_vdata.owner
GROUP BY
s1_users.id
ORDER BY
s1_users.dp DESC
SELECT s1_users.id AS userid,
(
SELECT COUNT(*)
FROM s1_vdata
WHERE s1_vdata.owner = userid
) AS totalvillages,
(
SELECT SUM(pop)
FROM s1_vdata
WHERE s1_vdata.owner = userid
) AS pop
FROM s1_users
WHERE dp >= 0
ORDER BY
dp DESC
Note that this is less efficient than this query:
SELECT s1_users.id AS user_id, COUNT(s1_vdata.owner), SUM(s1_vdata.pop)
FROM s1_users
LEFT JOIN
s1_vdata
ON s1_vdata.owner = s1_users.id
GROUP BY
s1_users.id
ORDER BY
dp DESC
since the aggregation needs to be done twice in the former.
SELECT userid,totalvillages,pop from
(
SELECT s1_users.id as userid, count( s1_vdata.wref ) as totalvillages
FROM s1_vdata, s1_users
WHERE s1_vdata.owner = userid
GROUP BY s1_users.id) tabl1 INNER JOIN
(
SELECT s1_users.id as userid, SUM( s1_vdata.pop ) as pop
FROM s1_users, s1_vdata
WHERE s1_vdata.owner = userid
GROUP BY s1_users.id) tabl2 on tabl1.userid = tabl2.userid