I have two tables, one contains the results of a game (which game played and number of games won), the second lists how many selections they have gotten right for a given game (i.e % correct).
I need to make a single query to calculate the percentage of times a user has won for each game. Each user may have played a different number of games.
I've tried to do this using COUNT, but if I group the count function to determine the number of times a user has player I can then not get the total number of times the user has won. See fiddle..
http://sqlfiddle.com/#!2/defc3/1
UPDATE result, games_played
SET result.precentage_correct =
(
**SELECT (COUNT(gp.user_id)/COUNT(gp.user_id)*100)**
FROM games_played as gp
WHERE result.user_id = gp.user_id
AND gp.winner != 'n'
AND gp.game = 1
GROUP BY gp.user_id
)
WHERE games_played.user_id = result.user_id
So, somehow I need to have two different COUNT functions with a math operator
You could simply combine two queries, one that selects only winning records, and another that selects all queries. Once you have these two counts, you can select from them to calculate the ratio.
SELECT user_id, 100*SUM(n_win)/SUM(n_total) AS pct_win FROM
(SELECT user_id, COUNT(user_id) AS n_win,
NULL AS n_total
FROM games_played
WHERE winner != 'n'
AND game = 1
GROUP BY user_id
UNION SELECT user_id, NULL AS n_win,
COUNT(user_id) AS n_total
FROM games_played
WHERE game = 1
GROUP BY user_id
) AS counts
GROUP BY counts.user_id;
Note that to combine the queries, one field of either n_win or n_total will be NULL in each subquery. The query will yield:
USER_ID PCT_WIN
1 50
2 66.6667
3 50
4 100
The union of the two subqueries will have two records for each user, one record for which n_win is known, the other record will have the value for n_total. For the first two users, it would look like
USER_ID N_WIN N_TOTAL
1 1 NULL
2 2 NULL
1 NULL 2
2 NULL 3
The outer query selects from that union the user_id and n_win / n_total grouped by user_id, thus yielding 50.0% and 66.6%. I am using SUM because it allows me to collect the non-NULL value for each column for each user.
Related
I have a relatively simple question but I'm stuck with writing a proper SQL query to display the results that I need. I have a table which stores results from matches with columns indicating the IDs of the players that took part in the match, the winner and another boolean column which let's say indicates whether I want to include that match in the result or not. So the columns are:
player1_id | player2_id | winner_id | use
So winner_id is the value from one of the first two columns depending on which player won. If I want to count how many times a certain player won a game just using the rows where the use flag is up, I can easily do so with:
SELECT COUNT(*) AS total, winner_id
FROM table
WHERE use = 1
GROUP BY winner_id
ORDER BY total DESC
However, I also want to do the same count but for the players that lost their matches. In other words, I want to group not by the winner_id but by the loser id, which would be the value of either player1_id or player2_id depending on which one of them is different from the winner_id. Any clues on how to do that with a simple query that works?
You can do like this to count the loosers:
SELECT
COUNT(*) AS total,
IF(player1_id = winner_id, player2_id, player1_id) AS looser_id
FROM table
WHERE use = 1
GROUP BY looser_id
ORDER BY total DESC
Problem Statement: I need my result set to include records that would not naturally return because they are NULL.
I'm going to put some simplified code here since my code seems to be too long.
Table Scores has Company_type, Company, Score, Project_ID
Select Score, Count(Project_ID)
FROM Scores
WHERE company_type= :company_type
GROUP BY Score
Results in the following:
Score Projects
5 95
4 94
3 215
2 51
1 155
Everything is working fine until I apply a condition to company_type that does not include results in one of the 5 score categories. When this happens, I don't have 5 rows in my result set any more.
It displays like this:
Score Projects
5 5
3 6
1 3
I'd like it to display like this:
Score Projects
5 5
4 0
3 6
2 0
1 3
I need the results to always display 5 rows. (Scores = 1-5)
I tried one of the approaches below by Spencer7593. My simplified query now looks like this:
SELECT i.score AS Score, IFNULL(count(*), 0) AS Projects
FROM (SELECT 5 AS score
UNION ALL
SELECT 4
UNION ALL
SELECT 3
UNION ALL
SELECT 2
UNION ALL
SELECT 1) i
LEFT JOIN Scores ON Scores.score = i.score
GROUP BY Score
ORDER BY i.score DESC
And gives the following results, which is accurate except that the rows with 1 in Projects should actually be 0 because they are derived by the "i". There are no projects with a score of 5 or 2.
Score Projects
5 1
4 5
3 6
2 1
1 3
Solved! I just needed to adjust my count to specifically look at the project count - count(project) rather than count(*). This returned the expected results.
If you always want your query to return 5 rows, with Score values of 5,4,3,2,1... you'll need a rowsource that supplies those Score values.
One approach would be to use a simple query to return those fixed values, e.g.
SELECT 5 AS score
UNION ALL SELECT 4
UNION ALL SELECT 3
UNION ALL SELECT 2
UNION ALL SELECT 1
Then use that query as inline view, and do an outer join operation to the results from your current query
SELECT i.score AS `Score`
, IFNULL(q.projects,0) AS `Projects`
FROM ( SELECT 5 AS score
UNION ALL SELECT 4
UNION ALL SELECT 3
UNION ALL SELECT 2
UNION ALL SELECT 1
) i
LEFT
JOIN (
-- the current query with "missing" Score rows goes here
-- for completeness of this example, without the query
-- we emulate that result with a different query
SELECT 5 AS score, 95 AS projects
UNION ALL SELECT 3, 215
UNION ALL SELECT 1, 155
) q
ON q.score = i.score
ORDER BY i.score DESC
It doesn't have to be the view query in this example. But there does need to be a rowsource that the rows can be returned from. You could, for example, have a simple table that contains those five rows, with those five score values.
This is just an example approach for the general approach. It might be possible to modify your existing query to return the rows you want. But without seeing the query, the schema, and example data, we can't tell.
FOLLOWUP
Based on the edit to the question, showing an example of the current query.
If we are guaranteed that the five values of Score will always appear in the Scores table, we could do conditional aggregation, writing a query like this:
SELECT s.score
, COUNT(IF(s.company_type = :company_type,s.project_id,NULL)) AS projects
FROM Scores s
GROUP BY s.score
ORDER BY s.score DESC
Note that this will require a scan of all the rows, so it may not perform as well. The "trick" is the IF function, which returns a NULL value in place of project_id, when the row would have been excluded by the WHERE clause.)
If we are guaranteed that project_id is non-NULL, we could use a more terse MySQL shorthand expression to achieve an equivalent result...
, IFNULL(SUM(s.company_type = :company_type),0) AS projects
This works because MySQL returns 1 when the comparison is TRUE, and otherwisee returns 0 or NULL.
Try something like this:
select distinct score
from (
select distinct score from scores
) s
left outer join (
Select Score, Count(Project_ID) cnt
FROM Scores
WHERE company_type= :company_type
) x
on s.score = x.score
Your posted query would not work without a group by statement. However, even there, if you don't have those particular scores for that company type, it wouldn't work either.
One option is to use an outer join. That would require a little more work though.
Here's another option using conditional aggregation:
select Score, sum(company_type=:company_type)
from Scores
group by Score
Im using MySQL
I cant change the DB structure, so thats not an option sadly
THE ISSUE:
When i use GROUP BY with CASE (as need in my situation), MYSQL uses
file_sort and the delay is humongous (approx 2-3minutes):
http://sqlfiddle.com/#!9/f97d8/11/0
But when i dont use CASE just GROUP BY group_id , MYSQL easily uses
index and result is fast:
http://sqlfiddle.com/#!9/f97d8/12/0
Scenerio: DETAILED
Table msgs, containing records of sent messages, with fields:
id,
user_id, (the guy who sent the message)
type, (0=> means it's group msg. All the msgs sent under this are marked by group_id. So lets say group_id = 5 sent 5 msgs, the table will have 5 records with group_id =5 and type=0. For type>0, the group_id will be NULL, coz all other types have no group_id as they are individual msgs sent to single recipient)
group_id (if type=0, will contain group_id, else NULL)
Table contains approx 10 million records for user id 50001 and with different types (i.e group as well as individual msgs)
Now the QUERY:
SELECT
msgs.*
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.user_id IN (50111)
AND msgs.type IN (0, 1, 5, 7)
GROUP BY CASE `msgs`.`type` WHEN 0 THEN `msgs`.`group_id` ELSE `msgs`.`id` END
ORDER BY `msgs`.`group_id` DESC
LIMIT 100
I HAVE to get summary in a single QUERY,
so msgs sent to group lets say 5 (have 5 records in this table) will be shown as 1 record for summary (i may show COUNT later, but thats not an issue).
The individual msgs have NULL as group_id, so i cant just put 'GROUP BY group_id ' coz that will Group all individual msgs to single record which is not acceptable.
Sample output can be something like:
id owner_id, type group_id COUNT
1 50001 0 2 5
1 50001 1 NULL 1
1 50001 4 NULL 1
1 50001 0 7 5
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 5 NULL 1
1 50001 0 10 5
Now the problem is that the GROUP condition after using CASE (which i currently think that i have to because i only need to group by group_id if type=0) is causing alot of delay coz it's not using indexes which it does if i dont use CASE (like just group by group_id ). Please view SQLFiddles above to see the explain results
Can anyone plz give an advice how to get it optimized
UPDATE
I tried a workaround , that does somehow works out (drops INITIAL queries to 1sec). Using union, what it does is, to minimize the resultset by union that forces SQL to write on disk for filesort (due to huge resultset), limit the resultset of group msgs, and individual msgs (view query below)
-- first part of union retrieves group msgs (that have type 0 and needs to be grouped by group_id). Applies the limit to captivate the out of control result set
-- The second query retrieves individual msgs, (those with type !=0, grouped by msgs.id - not necessary but just to be save from duplicate entries due to joins). Applies the limit to captivate the out of control result set
-- JOins the two to retrieve the desired resultset
Here's the query:
SELECT
*
FROM
(
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (msgs.user_id = accounts.id)
WHERE 1
AND accounts.id IN (50111 ) AND type = 0
GROUP BY msgs.group_id
ORDER BY msgs.id DESC
LIMIT 40
)
UNION
ALL
(
SELECT
msgs.id as reference_id, user_id, type, group_id
FROM
msgs
INNER JOIN accounts
ON (
msgs.user_id = accounts.id
)
WHERE 1
AND msgs.type != 0
AND accounts.id IN (50111)
GROUP BY msgs.id
ORDER BY msgs.id
LIMIT 40
)
) AS temp
ORDER BY reference_id
LIMIT 20,20
But has alot of caveats,
-I need to handle the limit in inner queries as well. Lets say 20recs per page, and im on page 4. For inner queries , i need to apply limit 0,80, since im not sure which of the two parts had how many records in the previous 3 pages. So, as the records per page and number of pages grow, my query grows heavier. Lets say 1k rec per page, and im on page 100 , or 1K, the load gets heavier and time exponentially increases
I need to handle ordering in inner queries and then apply on the resultset prepared by union , conditions need to be applied on both inner queries seperately(but not much of an issue)
-Cant use calc_found_rows, so will need to get count using queries seperately
The main issue is the first one. The higher i go with the pagination , the heavier it gets
Would this run faster?
SELECT id, user_id, type, group_id
FROM
( SELECT id, user_id, type, group_id, IFNULL(group_id, id) AS foo
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
)
GROUP BY foo
ORDER BY `group_id` DESC
LIMIT 100
It needs INDEX(user_id, type).
Does this give the 'correct' answer?
SELECT DISTINCT *
FROM msgs
WHERE user_id IN (50111)
AND type IN (0, 1, 5, 7)
GROUP BY IFNULL(group_id, id)
ORDER BY `group_id` DESC
LIMIT 100
(It needs the same index)
I have a mysql table called Game which has two columns, Name and Score. I want to select only the Names whose scores have been atleast 100 and atleast twice. In the below example Ron and Mary will get selected. I am not sure how to write the select statement for this.
Game table
Use GROUP BY with a HAVING clause:
SELECT Name
FROM mytable
GROUP BY Name
HAVING COUNT(CASE WHEN Score >= 100 THEN 1 END) >= 2
HAVING clause checks for Name groups, having at least two records with Score >= 100.
I want to return an associative array based on a mySQL query such that the query results in 3 columns which are a count of a single set of data in 3 different ways. My best solution is a bit unweildly and I'm hoping there's a better way as there are some irrelevant complexities not shown below. Specifically I have a user table with a user number, their gender, and a place - the place being the dynamic variable which needs to be bound into the query later (in this instance I'm looking for place = 1). The basic table looks like this:
user gender place
1 m 1
2 m 2
3 f 1
4 m 1
5 f 2
I'd like to return 3 columns which are total, total male, total female at place 1.
My first attempt returns 3 rows with the right values, but as they are rows I can't access them cleanly using an associative array:
SELECT COUNT(DISTINCT user) as total FROM users WHERE place=1
UNION ALL
SELECT COUNT(DISTINCT id_customer) as male FROM users
WHERE gender = 'm' AND place=1
UNION ALL
SELECT COUNT(DISTINCT id_customer) as female FROM users
WHERE gender = 'f' AND place=1
My second attempt gives me the correct result but seems a bit verbose as I'll have to bind the place variable 3 times - is there a better way to do this?
SELECT total, male, female FROM
(SELECT COUNT(DISTINCT user) as total FROM users
WHERE place=1
) as total
INNER JOIN
(SELECT COUNT(DISTINCT user) as male FROM users
WHERE place=1 AND gender='m') as male
INNER JOIN
(SELECT COUNT(DISTINCT user) as female FROM users
WHERE place=1 AND gender='f') as female
Do you need the DISTINCT part? or is "user" field unique (primary key or otherwise)?
I prepared both versions in http://sqlfiddle.com/#!9/928fa/7
If user is unique, then this should be enough:
SELECT count(1), sum(gender='m'), sum(gender='f')
FROM users
WHERE place=1;