MYSQL - select all time high score - php

Assuming we have a table like below
id | name | userid | score | datestamp |
------------------------------------------------------------
1 | john | 1 | 44 | 2013-06-10 14:25:55
2 | mary | 2 | 59 | 2013-06-10 09:25:51
3 | john | 1 | 38 | 2013-06-10 21:25:15
4 | elvis | 3 | 19 | 2013-06-10 07:25:18
5 | john | 1 | 100 | 2013-06-14 07:25:18
I want to select the all time high-score of each user.
so for example if the player john have played ten rounds in 2013-06-10 and his score is 430 in total for that day. and in 2013-06-14 he plays 16 rounds and his scores is 1220 in total for that day. i want to display the the best score of user john, and so on for the others players. in the example above johns best score was 2013-06-14 with score 1220.
Detailed example:
If user John plays 3 rounds in 2013-06-10. First round he scores 44, second time he scores 38 and third time he scores 55. His total score for that day is 137. And on the next day 2013-06-11 he plays 5 rounds with a total score of 220. In this example his best total score so far is 220. So it should group all scores for each day to a total. And then compare this total with other days total and display the highest total of that user
Thanks in advance

This should do the trick:
SELECT userId, name, MAX(score) as highScore
FROM (
SELECT userID, name, SUM(score) as score, date(datestamp) as scoreDate
FROM TableName
GROUP BY userID, scoreDate
) dailyScores
GROUP BY userId
The inner query fetches the totals of each user's scores on each date (timestamp converted to date to remove time information), the outer query then gets the highest total score of each date for each user.
I took the liberty of basing on J W's fiddle, and I added another row to your example data so the functionality is obvious, try it out here:
http://www.sqlfiddle.com/#!2/f6bea/3

SELECT a.*
FROM Tablename a
INNER JOIN
(
SELECT userID, MAX(score) max_Score
FROM TableName
GROUP BY userID
) b ON a.userID = b.userID AND
a.score = b.max_Score
SQLFiddle Demo
For faster searching, you need to have compound index on (userid, score). Here's how to implement it:
ALTER TABLE TableName ADD INDEX (userid, score)

Related

Joining a calendar table to a sales table to get total sales for every day in a specified range

I have a 'sales' table called phpbb_sold which records each 'sale' as a row.
I am able to use a WHERE clause with the uitemid field to select one particular item in the sales records, as seen below:
SELECT uitemid, locktime, migrated_sold FROM phpbb_sold WHERE uitemid=342;
+---------+------------+---------------+
| uitemid | locktime | migrated_sold |
+---------+------------+---------------+
| 342 | 1632523854 | 1 |
| 342 | 1634239244 | 1 |
| 342 | 1634240072 | 1 |
| 342 | 1636367271 | 1 |
+---------+------------+---------------+
uitemid = number that identifies this as a sale of X item. locktime = UNIX timestamp that shows the datetime that the item was sold. migrated_sold = the quantity of the item sold. So this is nice, I have a table that keeps a record of each sale as it happens.
What I want to achieve though, is a record of the total number of sales of this item type, for each day in a 6 month period spanning back from the current date, and including each day regardless of whether a sale was made or not. So the desired output of my query would be:
SELECT (the query I want goes here) and returns the following rows...;
+------------+------------+
| caldate | sold_total |
+------------+------------+
| 2021-09-23 | 2 |
| 2021-09-24 | 0 |
| 2021-09-25 | 1 |
| 2021-09-26 | 0 |
| 2021-09-27 | 0 |
| 2021-09-28 | 1 |
+------------+------------+
Note that each day is included as a row in the results, even where the sales total for that day is 0. I read that to do this, I would be required to create a calendar table with one column and all the days I want as rows, so I went ahead and did that:
SELECT caldate FROM phpbb_calendar;
+------------+
| caldate |
+------------+
| 2021-09-23 |
| 2021-09-24 |
| 2021-09-25 |
| 2021-09-26 |
| 2021-09-27 |
| 2021-09-28 |
+------------+
Now all that remains is for me to make the query. I need to somehow return all the rows from the phpbb_calendar table, joining the data from sum() (?) of the total migrated_sold for those days where exists, and a 0 where no sales took place.
I anticipated some issues with the UNIX timestamp, but it's okay because I am able to get caldate and locktime fields to be the same format by using from_unixtime(locktime, '%Y-%m-%d'), so both dates will be in the YYYY-MM-DD format for comparison.
Please could someone help me with this. I've gotten so close every time but it seems that everyone else's request is only slightly different from mine, so existing questions and answers have not been able to satisfy my requirements.
End goal is to use a JS chart library (AnyChart) to show a line graph of the number of sales of the item over time. But to get there, I first need to provide it with the query necessary for it to display that data.
Thanks
Update
Using this query:
SELECT c.caldate, u.uitemid, sum(v.migrated_sold) as total_sales
from phpbb_calendar c cross join
(select distinct uitemid from phpbb_sold) u left join
phpbb_sold v
on c.caldate = from_unixtime(v.locktime, '%Y-%m-%d') WHERE u.uitemid = 39 and c.caldate <= curdate() GROUP BY c.caldate ORDER BY c.caldate;
Returns:
But as you can see, it's just tallying up the total number of sales ever made or something - its clearly incrementing in a way I don't understand.
I don't want it to do that - I want it to count the number of total sales on each day individually. The results should look like this:
So that what is returned is basically a 'histogram' of sales, if any occurred, including 'empty' days where there were no sales (so these empty days must still be returned as rows).
SELECT c.caldate, u.uitemid, COALESCE(SUM(v.migrated_sold), 0) AS total_sales
FROM phpbb_calendar c
CROSS JOIN (SELECT DISTINCT uitemid FROM phpbb_sold WHERE uitemid = 37) u
LEFT JOIN phpbb_sold v
ON v.locktime BETWEEN UNIX_TIMESTAMP(TIMESTAMP(c.caldate)) AND UNIX_TIMESTAMP(TIMESTAMP(c.caldate, '23:59:59'))
AND u.uitemid = v.uitemid
WHERE c.caldate BETWEEN CURDATE() - INTERVAL 6 MONTH AND CURDATE()
GROUP BY c.caldate, u.uitemid
ORDER BY c.caldate;
N.B. I have changed your join to use the unix_timestamp as it should be more efficient and it can use any existing index on locktime
check this out:
select id, d, sum(s) from (
select U.id, d, 0 s from (
select adddate(current_date(),-rows.r) d from (
select (#row_number := #row_number + 1) r
from information_schema.columns,
(SELECT #row_number := 0) AS x
limit 200
) rows
) dates,
(SELECT distinct uitemid id FROM `phpbb_sold`) U
where d > adddate(current_date(), interval -6 month)
union
select uitemid, date(from_unixtime(locktime)),sum(migrated_sold)
from `phpbb_sold`
group by uitemid, date(from_unixtime(locktime))
) sales_union
group by id, d
order by id, d;
see dbfiddle
no need for calendar table

Assign points by rank to players of multiple games

I'm building a multi-game tournament scoreboard, where many players can play many games, many times. I want to assign points to players per game based on their rank for each game, not their actual score.
e.g.
Donkey Kong
Rank | Player | Score | Points Awarded
1 | Player2 | 34,000 | 1,000
2 | Player1 | 32,000 | 999
3 | Player3 | 29,000 | 998
Robotron
Rank | Player | Score | Points Awarded
1 | Player1 | 39,000 | 1,000
2 | Player3 | 32,000 | 999
3 | Player2 | 21,000 | 998
Tournament Standings
Player1 - 1,999 Points
Player2 - 1,998 Points
Player3 - 1,997 Points
So far I have ranking and points calculations working just fine...
SELECT
`id`,
`userID`,
`gameID`,
`gamescore`,
`rank`,
1001.0 - (rank) AS points
FROM (
SELECT
`id`,
`userID`,
`gameID`,
`gamescore`,
#curr_rank := IF(#prev_rank = id, #curr_rank, #curr_rank + 1) AS rank,
#prev_rank := id
FROM
`submit_score`,
(SELECT #curr_rank := 0) y,
(SELECT #prev_rank := NULL) z
WHERE `submit_score`.`tournID` = 2
ORDER BY `gamescore` DESC
) ranked_game;
But I need to be able to assign the points by rank-per-game and then have a grand total of points for each player that I can then show in a list.
Your DB should to look something like
Players
ID | Player Nickname
1 | Player1
2 | Player2
3 | Player3
Donkey Kong
PlayerID | Score
Player2ID | 34,000
Player1ID | 32,000
Player3ID | 29,000
Robotron
PlayerID | Score
Player1ID | 39,000
Player3ID | 32,000
Player2ID | 21,000
DB should be only used to store data. Rank and awarded points should be moved to the code as those are redundant parts.
Then query each table with desc sort on score columns. That will create ranked tables with top scorer as #1 etc. That retrieved data store in arrays for each game with Key->Values where Key will be a rank # (1+ incremental) and Values can be other array with stored players data (after join) and a score or a string something like PlayerX | Score# if you need a score data for anything because for real you need only Players data sort on scores per game.
After that you need to loop through player table and create an array of players where you will store tournament points retrieved from looping each game array and decrease tournament points on each rank and assign to a proper player.
Hope it helps
PS.
For existing data I will create views something like
set #max_points=1000;
select Rank, Player,
#current_points := IF(Rank = 1, #max_points, #current_points-1) AS points
from DonkeyKong
for each game
then do final view to sum all tournaments points
select dk.Player AS Player,(dk.Points + ro.Points) AS Total
from RO_view AS ro
left join DK_view AS dk
on dk.Player = ro.Player

Get records with max time inserted MySQL results

I have some data row in my table.
I want to select the Age which occurs the most.
Person | Group | Age
---
Bob | 1 | 32
Jill | 1 | 32
Shawn| 1 | 42
Jake | 2 | 29
Paul | 2 | 36
Laura| 2 | 39
Desired set:
The age that appears the most is 32.
Use the following query
select Person, count(*) as c FROM tableName GROUP BY Age
You can add the limit 1 to get the only only record and order by to get the maximum or minimum age. Use following query
select Person, count(*) as c,Age FROM profile GROUP BY Age ORDER BY c desc LIMIT 0,1
Try something like this
SELECT Person,Group,Age,MAX(field_name)
FROM table_name;
After grouping the values you can select the top 1 as below : This would select 32 as answer
SELECT TOP (1) Age
FROM tablename
GROUP BY age
ORDER BY COUNT(*) DESC
SELECT age FROM my_table GROUP BY age ORDER BY COUNT(*) DESC LIMIT 1;
Fairly obviously, in the case of a tie, this result will be misleading.

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.

Getting a rank from a SQL total column

I have a MySQL table like so...
ID | NAME | RANK | PTS
---+-------+------+----
12 | John | 1 | 28
18 | Andy | 2 | 31
23 | Brian | 3 | 16
41 | Mike | 4 | 33
15 | Jack | 5 | 35
68 | Anne | 6 | 24
I currently sort by...
SELECT * FROM `ranks` ORDER BY PTS desc
I am looking for some code that I can give it an ID, and it'll return it's ranking by the PTS column. For ID 41, it should return 2 as the 2nd best score of that column.
I am not sure what to google for to get an answer. Is there some SQL code that can do this simply? or maybe php? The RANK column is overall rank, and PTS is the current weekly score so far. There might be up to 2.5 million entries eventually. Please let me know if you have any questions.
// get the number of ppl w/ points higher than the given user's
SELECT count(*) + 1 FROM ranks WHERE PTS > (
SELECT PTS FROM ranks WHERE ID = 41);
...though if you are looking to scale this you probably want to reconsider how you are structuring your data.
try this query:
Step1:
CREATE TABLE t (
id INT(3),
name char(60),
score INT(5)
);
insert into t values(10,"name1",33);
insert into t values(11,"name2",43);
insert into t values(12,"name3",335);
insert into t values(13,"name4",233);
Step2:
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, name, id, score from t order by score desc
You just put the PTS into an array and create a loop to add the column output.
$sum = 0;
foreach ($array as $num){
$sum = $sum + $num;
}
return $sum;

Categories