Cannot figure out SQL SUM() with a WHERE - php

I'm trying to get a result from SQL database, and I want it to get a ratio and sort it by that ratio. That is number of rows where the value is yes and costumerid is the same, divided by the total number of rows with the costumerid, and ORDER BY that ratio. This is what I have at the moment:
$sqlsortby = "SELECT DISTINCT q2, q14,
(SUM(WHERE costumerid='$costumerid' AND option1='yes') /
SUM(WHERE costumerid='$costumerid')) AS costumerratio
FROM costumers
WHERE q14='successful' AND ordernum='$ordernumsession'
ORDER BY costumerratio";
Thanks :)
-------------------------------------------------------------
| q2 | costumerid | option1 | ordernum | q14 |
-------------------------------------------------------------
| Bob | 1 | yes | 001 | successful|
| Bob | 1 | no | 002 | successful|
-------------------------------------------------------------

In MySQL, I think this is the simplest way to do what you want:
SELECT q2, q14, AVG(option1 = 'yes') as costumerratio
FROM costumers
WHERE q14 = 'successful' AND ordernum = '$ordernumsession' AND
costumerid = '$costumerid'
GROUP BY q2, q14
ORDER BY costumerratio;
You can do essentially the same thing in other databases, but it requires CASE.
Note that this assumes that you want rows where customerid = $costumerid.

Using SUM() without GROUP BY as you currently have probably is not what you want, because it will return just a single record in the result set. More typically, we use SUM() with GROUP BY. Here is my guess as to what you want:
SELECT t.*
FROM
(
SELECT q2, q14,
SUM(CASE WHEN costumerid='$costumerid' AND option1='yes'
THEN 1 ELSE 0 END) /
SUM(CASE WHEN costumerid='$costumerid' THEN 1 ELSE 0 END) AS costumerratio
FROM costumers
WHERE q14='successful' AND
ordernum='$ordernumsession'
GROUP BY q2, q14
) t
ORDER BY t.costumerratio
Also note that the code you were using inside SUM() to aggregate was broken. Instead, I have replaced it with CASE expressions.

Related

Using 1st SQL select statement results for 2nd select statement

I'd like some help combining Multiple SQL queries into one...
I have a search box for orderid or sampleref. An order may have up to 99 sampleref in it so I want the customer to be able to pull up a list of all sampleref associated with their order number regardless of if they search by orderid or one of their sampleref. Essentially what I want to do is,
SELECT `orderid` as OrderNumber FROM `results` WHERE `sampleref` = 'TEST12345';
SELECT * FROM `results` WHERE `orderid` = OrderNumber GROUP BY `sampleref`;
For clarity I'm putting this into a PHP script for a Maria DB mysql server
Here is a sample database
+----+---------+-----------+
| id | orderid | sampleref |
+----+---------+-----------+
| 1 | 101388 | TEST12345 |
| 2 | 101388 | TEST54321 |
| 3 | 333444 | ABC123 |
| 4 | 333444 | ABC321 |
+----+---------+-----------+
Thanks
Henry
Following will give you what you are looking for.
select r2.orderid, r2.sampleref
from result r
join result r2 on r.orderid = r2.orderid
where r.sampleref = 'TEST12345' or r.orderid = <orderid>
You can use or with a correlated subquery:
SELECT r.*
FROM results r
WHERE r.orderid = $orderid OR
EXISTS (SELECT 1
FROM results r2
WHERE r2.orderid = r.orderid AND r2.sampleref = $sampleref
);
Note: This takes two parameters -- either the order id or the sample ref. The first condition returns everything with the same order, if that is given. The second returns everything with the same order as the given sample ref.

mysql - get closest value to correct value return zero to many depending on result

My site allows users to guess the result of a sports match. At the end of the match the guesses should be compared to the actual result. The winner(s) are the members with the closest correct guess
Im looking for a way to return all members who guessed the correct result and score difference IF NO (zero) member guessed correctly return members who guessed closest to the correct result
See MYSQL FIDLE EXAMPLE
I modified the script to change fixed values taking variables as you can see below
if(isset($_POST['resultBtn'])){
foreach($_POST['winner'] as $id =>$winner){
$winScore = $_POST['score'][$id];
:
:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)";
My problem is if I run this script on the following table:
NOTHING gets displayed even if I put result exactly correct:
My variable values are correct in the sql statment so that is not the problem
Any help will be welcomed...
IMPORTANT THE USER WHO SELECTED CLOSEST CORRECT RESULTS, FOR ALL GAME, DURING THE ROUND IS THE WINNER
example: if user A won 4 of the picks and user B won 5 of the picks then user B is the winner of the round
Why don't you want just
SELECT p.*, abs(p.score-'$winScore') as diff
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND p.pick='$winner'
ORDER BY diff ASC
LIMIT 1
This will return the closest member for the event. Remove the LIMIT if you need a few of them.
Also, never put your parameters directly into the SQL query, even trusted ones (not your case) and even if you're sure they will always be integer or non-string type. Use prepared statements.
In this answer I call a "Best" pick any pick that has chosen the correct winner for a particular match, and has the closest score to the actual match score.
These scripts also respect the different "rounds" in the competition, since that is an important complication.
This answer comes in two parts: first a query that is similar to the one in the question that returns all the "Best" picks for a particular match. To make it easier to run in SQL Fiddle, I have used MySQL variables instead of PHP variables.
Schema with test data:
create table Multiple_Picks (
pick_id int,
member_nr int,
event_id int,
pick varchar(100),
score int
);
insert into Multiple_Picks
values
(11,100,1,'Crusaders',15),
(12,100,2,'Waratahs',10),
(13,100,3,'Chiefs',4),
(21,200,1,'Crusaders',15),
(22,200,2,'Waratahs',10),
(23,200,3,'Lions',4),
(31,300,1,'Crusaders',15),
(32,300,2,'Waratahs',12),
(33,300,3,'Lions',6),
(41,100,4,'Crusaders',20),
(42,100,5,'Waratahs',20),
(43,100,6,'Lions',20)
;
Queries to show all picks and then best picks for a particular match:
set #matchId = 2;
set #winner = 'Waratahs';
set #winScore = 8;
-- Show all picks for a particular match
select * from Multiple_Picks
where event_id = #matchId;
-- Show best picks for a particular match
select p.*
from Multiple_Picks p
where p.event_id = #matchId
and p.pick = #winner
and abs(p.score - #winScore) =
(select min(abs(other.score - #winScore))
from Multiple_Picks other
where other.event_id = #matchId
and other.pick = #winner
)
;
SQL Fiddle to show picks for particular match
-- Show all picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
| 32 | 300 | 2 | Waratahs | 12 |
+---------+-----------+----------+----------+-------+
-- Show best picks for a particular match
+---------+-----------+----------+----------+-------+
| pick_id | member_nr | event_id | pick | score |
+---------+-----------+----------+----------+-------+
| 12 | 100 | 2 | Waratahs | 10 |
| 22 | 200 | 2 | Waratahs | 10 |
+---------+-----------+----------+----------+-------+
Now we need to work towards finding the winner of each round of the competition.
First we have extra test data that contains the actual scores for Matches in rounds 1 and 2.
create table Matches (
event_id int,
winner varchar(100),
score int,
round int
);
insert into Matches
values
(1,'Crusaders',10,1),
(2,'Waratahs',11,1),
(3,'Lions',4,1),
(4,'Crusaders',20,2),
(5,'Waratahs',20,2),
(6,'Chiefs',20,2)
;
Now select the best picks for all Matches. The subselect (aliased as m) calculates best_diff for each match as the minimum difference between the actual score and every guessed score. This subselect is then joined to every pick so that only "Best" picks are returned.
-- Show all best picks for all Matches
select p.*, m.round
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
order by m.round, p.event_id
;
It is then easy to get a count of Best picks for each player for each round by just grouping the previous query by member_nr and round:
-- Show a count of best picks for each player for each round
select p.member_nr, m.round, count(*) as best_count
from Multiple_Picks p
join (
select m2.event_id, m2.winner, m2.score, m2.round,
min(abs(m2.score-p2.score)) as best_diff
from Matches m2
join Multiple_Picks p2
on p2.event_id = m2.event_id and p2.pick = m2.winner
group by m2.event_id, m2.winner, m2.score, m2.round
) as m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
group by p.member_nr, m.round
order by m.round, count(*) desc
;
SQL Fiddle for all best picks and counts for all matches
-- Show all best picks for all Matches
+---------+-----------+----------+-----------+-------+-------+
| pick_id | member_nr | event_id | pick | score | round |
+---------+-----------+----------+-----------+-------+-------+
| 31 | 300 | 1 | Crusaders | 15 | 1 |
| 21 | 200 | 1 | Crusaders | 15 | 1 |
| 11 | 100 | 1 | Crusaders | 15 | 1 |
| 12 | 100 | 2 | Waratahs | 10 | 1 |
| 32 | 300 | 2 | Waratahs | 12 | 1 |
| 22 | 200 | 2 | Waratahs | 10 | 1 |
| 23 | 200 | 3 | Lions | 4 | 1 |
| 41 | 100 | 4 | Crusaders | 20 | 2 |
| 42 | 100 | 5 | Waratahs | 20 | 2 |
+---------+-----------+----------+-----------+-------+-------+
-- Show a count of best picks for each player for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 300 | 1 | 2 |
| 100 | 1 | 2 |
| 100 | 2 | 2 |
+-----------+-------+------------+
The final stage is to select only those players for each round who have the highest number of Best picks. I tried modifying the above queries, but the nesting becomes two confusing, so my solution was to create a few logical views so that the final query can be more easily understood. The views basically encapsulate the logic of the queries I have explained above:
create view MatchesWithBestDiff as
select m.event_id, m.winner, m.score, m.round,
min(abs(m.score-p.score)) as best_diff
from Matches m
join Multiple_Picks p
on p.event_id = m.event_id and p.pick = m.winner
group by m.event_id, m.winner, m.score, m.round
;
create view BestPicks as
select p.*, m.round
from Multiple_Picks p
join MatchesWithBestDiff m
on p.event_id = m.event_id and p.pick = m.winner
and abs(m.score - p.score) = m.best_diff
;
create view BestPickCount as
select member_nr, round, count(*) as best_count
from BestPicks
group by member_nr, round
;
So that the query that shows the winners of each round is simply:
-- Show the players with the highest number of Best Picks for each round
select *
from BestPickCount p
where best_count =
(
select max(other.best_count)
from BestPickCount other
where other.round = p.round
)
order by round
;
SQL Fiddle for players with most Best picks for each round
-- Show the players with the highest number of Best Picks for each round
+-----------+-------+------------+
| member_nr | round | best_count |
+-----------+-------+------------+
| 200 | 1 | 3 |
| 100 | 2 | 2 |
+-----------+-------+------------+
This whole investigation has reminded me how tricky it can be to get SQL to do much manipulation where records need to be selected depending on maximums and sums. Some of these types of queries can be much easier with window functions (the OVER and PARTITION BY clauses), but they are not available in MySQL.
While designing the above queries, I found a few interesting restrictions:
MySQL does not allow joins to subqueries in views definitions.
ANSI SQL does not allow an aggregate in a subquery to reference both a column from the inner query and a column from the outer query. MySQL seems to sometimes allow this, but I couldn't find clear guidance as to when it is allowed, so I chose to code the above queries to avoid this "feature".
scenario 1: NO USERS SELECTED THE CORRECT TEAM
I believe that result in this situation should be empty result because everyone has made a mistake.
SCORE RETURN MEMBERS WHO SELECTED THE CLOSEST TO CORRECT SCORE AND
RESULT
It seems to be already working in your code example except one mistake in select.
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-1))
Instead of constant 1 (one) it should be variable '$winScore'
and to control the number of users you get, you may limit your results so you will get something like this:
$sql="SELECT p.*
FROM Multiple_Picks p
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id limit '$numberOfMembers'";
SCENARIO 2: SCENARIO 2: MULTIPLE USERS SELECTED CORRECT TEAM BUT
SCORES ARE DIFFERENT RETURN USER(S) WHO GUESSED CLOSEST TO CORRECT
SCORE
Same as in the previous question.
SCENARIO 3: MULTIPLE USERS SELECTED CORRECT TEAM AND SCORE RETURN ALL
USERS WHO SELECTED CORRECT TEAM AND SCORE
You can achieve this using same query just replace the LIMIT with 'rank' function, and also if you will get several closest scores, but you have to limit their number according to their voting order by id, for this purpose I suggest sorting.
So final query will be:
$sql="select * from (SELECT p.*,
abs(p.score-'$winScore') scr_diff,
#rownum := #rownum + 1 rank
FROM Multiple_Picks p,
(SELECT #rownum := 0) rank_gen
WHERE p.event_id='$matchId' AND
p.pick='$winner' AND
abs(p.score-'$winScore') = (SELECT min(abs(p2.score-'$winner'))
FROM Multiple_Picks p2
Where p2.pick=p.pick AND
p2.event_id = p.event_id)
order by p.id
) sq
where sq.scr_diff = 0
or sq.rank < '$numberOfMembers'";
Fiddle.
Best guesser for one match
First find the member(s) who picked the winner and had the closest score guess:
SELECT p.*
FROM
( SELECT MIN(ABS(score-'$winScore')) AS closest
FROM Multiple_Picks
WHERE event_id = '$matchId'
AND pick='$winner'
) AS c
JOIN Multiple_Picks p
WHERE p.event_id = '$matchId'
AND p.pick = '$winner'
AND ABS(score-'$winScore') = c.closest
If that return no results, then what should happen? (It would be because no one picked the winner for a particular event.)
But, I think your question is much more complex. However, the above gives a mapping from (event_id, pick) -> list-of-members who "won". Starting over...
Missing info
There is a mystery -- Where do the event results come from? I will assume this table is already populated:
CREATE TABLE Win (
event_id ..., -- which game
winnner ..., -- who won
score ... -- by what score
)
Best guesser overall
So, create a table of BestGuessers(event_id, member). The details of "all game" and "round" are a bit vague. So I will carry this at least one step further.
CREATE TEMPORARY TABLE BestGuessers(
event_id ...,
member_nr ... -- who guessed the best for that event
)
SELECT p.event_id, p.member_nr
FROM
( SELECT w.event_id, w.winner, MIN(ABS(mp.score-w.score)) AS closest
FROM Multiple_Picks AS mp
JOIN Win AS w ON mp.event_id = w.event_id
AND mp.pick = w.winner
GROUP BY w.event_id, w.winner
) AS c
JOIN Multiple_Picks p
ON p.event_id = c.event_id
AND p.pick = c.pick
AND p.score = c.closest
Now, from that, you can pick the best guesser(s).
SELECT y.member_nr
FROM
( SELECT COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
ORDER BY COUNT(*) DESC
LIMIT 1
) AS x -- the max number of correct guesses
STRAIGHT_JOIN
( SELECT member_nr, COUNT(*) AS ct
FROM BestGuessers
GROUP BY member_nr
) AS y -- the users who guessed correctly that many times
USING (ct);
All this is pretty complex; I may have some typos, even logic errors. But maybe I came close.
It seems an additional table to store the actual results would help here.
E.g let's say this is in a table called results with sample values as follows:
event_id winner result
1 Crusaders 16
2 Waratahs 15
3 Chiefs 4
4 Crusaders 17
5 Reds 12
0 Rebels 14
7 Cheetahs 15
8 Crusaders 14
This can then be JOINed on each row and results compared as follows:
SELECT p.*
, CASE WHEN ABS(p.score - r.result)
- CASE WHEN p.pick = r.winner THEN 999999 ELSE 0 END
= (SELECT MIN(ABS(p2.score - r2.result)
- CASE WHEN p2.pick = r2.winner THEN 999999 ELSE 0 END)
FROM picks p2
JOIN results r2
ON p2.event_id = r2.event_id
WHERE p2.event_id = p.event_id)
THEN 1
ELSE 0
END AS win
FROM picks p
JOIN results r
ON p.event_id = r.event_id;
Explanation
The rightmost win column is 1 if the member is calculated to have won or drawn the event, otherwise it is 0. The method used is similar to the one in your post, with the main difference being the team and score are combined. The main thing to be explained here is the 999999, which is subtracted when a correct team is picked - so this can be sure to eclipse the score difference. (Of course, an even bigger value could be picked if needed).
Demo
SQL Fiddle Demo

How to Sum amount of same role records?

I want to get amount of same role in one sum. When I fetch a records of agent details introduced by some agent (11150000001). It will return lot of agents records with different role(1000,2000,3000,4000,5000,6000).
From that I need to get the agents business amount total. Now I'm getting it. But If there is some agent in same role I want to get that same role agent's amount in single amount, but I'm getting separately.
//get all employee under one employee
$rank1=''; $rank2='';$rank3='';$rank4='';$rank5='';$rank6='';
$get_employee="SELECT emp_code,intro_code FROM emp_details WHERE intro_code='".$emp_code."'";
$exe_employee=mysql_query($get_employee);
while($fetch_emp=mysql_fetch_assoc($exe_employee))
{
$total_amount1=0;
$role='';
$employee_code=$fetch_emp['emp_code'];
//echo $employee_code."<br>";
$get_premium="SELECT emp_code,user_role,premium_amount FROM business_details WHERE emp_code='".$employee_code."'";
//echo $get_premium."<br>";
$exe_premium=mysql_query($get_premium);
while($fetch_amount=mysql_fetch_array($exe_premium))
{
$total_amount1 +=$fetch_amount['premium_amount'];
$role=$fetch_amount['user_role'];
}
}
echo $role."-".$total_amount1."<br>";
// Role-Amount
// 5000-75000
// 5000-105000
//3000-15000
In the above image I'm getting 10,500 in rank 5, but I want to get the 75000+10500 in rank 5 column.
Below is my table structure:
The entire block of code than be replaced by a single SQL query. You want to create a pivot table.
Lets start by a relatively simple query that fetches the total premium_amount of all the employee-role combinations:
SELECT emp_code,
user_role,
SUM(premium_amount) AS total_amount
FROM business_details
GROUP BY emp_code, user_role;
This would yield something like this:
emp_code | user_role | total_amount
-----------------------------------
15040000001 | 3000 | 15000
15040000001 | 5000 | 180000
11130000001 | 4000 | 30000
11130000001 | 1000 | 5000
This already contains all the information you want in your table, but not in the correct layout. Instead of using PHP to reformat this into the rows/columns you want for your table, SQL can do it for us.
Instead of grouping by user_role, we're going to specify the different values user_role can take as different columns:
SELECT emp_code,
SUM(CASE WHEN user_role=1000 THEN premium_amount ELSE 0 END) AS rank1,
SUM(CASE WHEN user_role=2000 THEN premium_amount ELSE 0 END) AS rank2,
SUM(CASE WHEN user_role=3000 THEN premium_amount ELSE 0 END) AS rank3,
SUM(CASE WHEN user_role=4000 THEN premium_amount ELSE 0 END) AS rank4,
SUM(CASE WHEN user_role=5000 THEN premium_amount ELSE 0 END) AS rank5,
SUM(CASE WHEN user_role=5000 THEN premium_amount ELSE 0 END) AS rank6
FROM business_details
GROUP BY emp_code;
The result would be:
emp_code | rank1 | rank2 | rank3 | rank4 | rank5 | rank6
------------------------------------------------------------
15040000001 | 0 | 0 | 15000 | 0 | 180000 | 0
11130000001 | 5000 | 0 | 0 | 30000 | 0 | 0
Bonus advise
The PHP code you've posted contains a glaring security flaw that leaves your database (or probably your entire system) wide open to anyone. SQL injection is not fun to have in your web application! I've written about it in a previous answer.
Try this...
$get_premium="SELECT emp_code,user_role,premium_amount,SUM(premium_amount) FROM business_details WHERE emp_code='".$employee_code."' group by user_role" ;

Calculate quantiles for a score/ranking system (PHP / MySQL)

There are two tables:
Table user:
+----+-----------+
| id | user_name |
+----+-----------+
| 1 | Alice |
| 2 | Steve |
| 3 | Tommy |
+----+-----------+
Table result:
+----+---------+-------+-------------+
| id | user_id | score | timestamp |
+----+---------+-------+-------------+
| 1 | 1 | 22 | 1410793838 |
| 2 | 1 | 16 | 1410793911 |
| 3 | 2 | 9 | 1410793920 |
| 4 | 1 | 27 | 1410794007 |
| 5 | 3 | 32 | 1410794023 |
+----+---------+-------+-------------+
What I have so far is a "top 3", which works great and looks like this:
SELECT MAX(m.score) AS score, u.user_name
FROM result AS r
INNER JOIN user AS u ON r.user_id = u.id
GROUP BY r.user_id
ORDER BY r.score DESC
LIMIT 3;
+-------+-----------+
| score | user_name |
+-------+-----------+
| 32 | Tommy |
| 27 | Alice |
| 9 | Steve |
+-------+-----------+
The table is actually filled with hundreds of results, this is just an example. I'm looking for a compact algorithm to get the rank of a specific user in relation to all other users in %. The goal is to output something like "you are in the top 5%/10%/20%/50%" or "you are below average". While it's easy to determine if someone is below average (score < AVG(score)), I have no clue how to determine the other ranks.
If I got all correctly, it's just relative maximum calculation:
SELECT
user_name,
MAX(score) AS max_score,
CASE
WHEN ROUND(100*MAX(score)/maximum, 2)>=95 THEN 'In top 5%'
WHEN ROUND(100*MAX(score)/maximum, 2)>=90 THEN 'In top 10%'
WHEN ROUND(100*MAX(score)/maximum, 2)>=75 THEN 'In top 25%'
WHEN ROUND(100*MAX(score)/maximum, 2)>=50 THEN 'In top 50%'
WHEN ROUND(100*MAX(score)/maximum, 2)>=0 THEN 'Below average'
END AS score_mark
FROM
`result`
INNER JOIN `user`
ON `result`.user_id=`user`.id
CROSS JOIN
(SELECT MAX(score) AS maximum FROM `result`) AS init
GROUP BY
user_id
So, counting from maximum score per all table and grouping it for specific user. Check the fiddle.
As mentioned below, this counting method involves simple way to determine average (i.e. all it's based on total maximum). This may be not the thing which is needed. By that I mean, that if question is about calculation relative position according to other scores (not maximum) - then it's more complicated:
SELECT
maxs.*,
#num:=#num+1 AS order_num,
CASE
WHEN 100*(#num-1)/(user_count-1) <= 5 THEN 'In top 5%'
WHEN 100*(#num-1)/(user_count-1) <= 10 THEN 'In top 10%'
WHEN 100*(#num-1)/(user_count-1) <= 25 THEN 'In top 25%'
WHEN 100*(#num-1)/(user_count-1) <= 50 THEN 'In top 50%'
WHEN 100*(#num-1)/(user_count-1) <= 100 THEN 'Below average'
END AS score_mark
FROM
(SELECT
user_name,
MAX(score) AS max_score
FROM
`result`
INNER JOIN `user`
ON `result`.user_id = `user`.id
GROUP BY
user_id
ORDER BY
max_score DESC) AS maxs
CROSS JOIN
(SELECT
#num:=0,
COUNT(DISTINCT user_id) AS user_count
FROM
`result`) AS init
-since now we must first re-count our positions and later build relative calculations over that. Here is the corresponding fiddle. Here, however, I'm applying linear formula to count 1-st position as "zero" and last position as "100". If that's not an intention (there will be edge-cases, like "3" in "50%" for "5 total" in fiddle) - then you may change divisor to user_count
Here is another version
SELECT user_name, score,(CASE
WHEN score BETWEEN #max-((#max-#min)/10) AND #max THEN '10'
WHEN score BETWEEN #max-((#max-#min)/5) AND #max THEN '20'
WHEN score BETWEEN #max-((#max-#min)/2) AND #max THEN '50'
ELSE 'more50'
END) as rangescore,
user_name
FROM result r
INNER JOIN user u ON r.user_id = u.id,
(SELECT #max := MAX(score) FROM result)x,
(SELECT #min := MIN(score) FROM result)y
ORDER BY score DESC
You can use AVG(score) instead of MAX f you want to compare the average score of the user.
Or remove the aggregate functions and GROUP BY if you want every score.
FIDDLE
FIDDLE GROUP
Ok, now with your new declarations, I will provide you another solution:
As you said you can use PHP and MySQL togheter, I provide you a combined one.
You want to compute your quantiles (to have an idea what this is quantiles on wikipedia), because if you have a top scorer with about 10000 points and all other players just have 100 points and below, the ones with 100 points are in the top 5% as player, but there score is under 50% of the top scorer.
With this in mind, we can count the number of players, get the score at the wanted percent of players and compare, were the players score fits in.
First select all the maximas, minimas, counter etc.
SELECT
COUNT(`result`.`score`) `count`,
MAX(`result`.`score`) `max`,
MIN(`result`.`score`) `min`,
AVG(`result`.`score`) `avg`
FROM
`result`
GROUP BY
`result`.`user_id`
ORDER BY
`result`.`score` DESC
After you have the complete data, you can compute your quantiles.
SELECT
`result`.`score`
FROM
`result`
GROUP BY
`result`.`user_id`
ORDER BY
`result`.`score` DESC
LIMIT FLOOR($count*$percent), 1
//where $count is the value from the first query and $percent is the wanted quantile e.g. 5%
After that you know the value for your quantile and you can compare the actual value with the ones here.
//where $percentNN is the score from the previous query
if($score > $percent50) echo "top 50%";
if($score > $percent20) echo "top 20%";
if($score > $percent10) echo "top 10%";
if($score > $percent5) echo "top 5%";
maybe, we can combine the multiple queries to one.

How best to get someone's 'rank' from a scores table with php and mysql without looping

My table will hold scores and initials.
But the table wont be ordered.
I can get the total row count easy enough and I know I can get all of them and Order By and then loop through them and get the rank THAT way...
But is there a better way? Could this maybe be done with the SQL statement?
I'm not TOO concerned about performance so if the SQL statement is some crazy thing, then Ill just loop.
Sorry - Table has id as primary key, a string to verify unique app install, a column for initials and a column for score.
When someone clicks GET RANK... I want to be able to tell them that their score is 100 out of 1000 players.
SELECT s1.initials, (
SELECT COUNT(*)
FROM scores AS s2
WHERE s2.score > s1.score
)+1 AS rank
FROM scores AS s1
Do you have a primary key for the table or are you fetching data by the initials or are you just fetching all the data and looping through it to get the single record? How many results are you trying to fetch?
If you only want to fetch one record, as the title suggests, you would query the database using a WHERE conditional:
SELECT score FROM table WHERE user_id = '1';
See this answer: https://stackoverflow.com/a/8684441/125816
In short, you can do something like this:
SELECT id, (#next_rank := IF(#score <> score, 1, 0)) nr,
(#score := score) score, (#r := IF(#next_rank = 1, #r + 1, #r)) rank
FROM rank, (SELECT #r := 0) dummy1
ORDER BY score DESC;
And it will produce a result like this:
+------+----+-------+------+
| id | nr | score | rank |
+------+----+-------+------+
| 2 | 1 | 23 | 1 |
| 4 | 1 | 17 | 2 |
| 1 | 0 | 17 | 2 |
| 5 | 1 | 10 | 3 |
| 3 | 1 | 2 | 4 |
+------+----+-------+------+
Note that users with equal scores have equal ranks. :-)

Categories