Ordering List of Members Closest To Their Next Award - mysql

I have two tables, one 'gift_limit_count' and one 'score' as follows:
'gift_limit_count' containing:
gift_id
gift_limit
gift_amount
'score' containing:
user_id
user_name
user_score
When a user's 'user_score' count gets to the next highest 'gift_limit' they are awarded with the 'gift_amount'. What i'd like to do is show a list of say, the top 10 users closest to their next award.
For example:
David(user_name) has 1 point to go before being awarded with £40 (gift_amount)
Suzi has 2 points to go before being awarded with £20
Ian has 2 points to go before being awarded with £40
Zack has 3 points to go before being awarded with £30
...
...

Select S.user_id, S.user_name, S.user_score
, GLC.gift_limit, GLC.gift_amount
From (
Select S1.user_id
, Min( GLC1.gift_limit ) As NextLimit
From score As S1
Join gift_limit_count As GLC1
On GLC1.gift_limit > S1.user_score
Group By S1.user_id
) As Z
Join score As S
On S.user_id = Z.user_id
Join gift_limit_count As GLC
On GLC.gift_limit = Z.NextLimit
Order By ( gift_limit - user_score )
Limit 10

You might be able to do a query with a join based on min( score - gift_limit ) ( with score < gift_limit ) and then sort by the lowest value for each person. Group it to get one answer per person too.

Related

Group mysql results by cumulative column value

I have a database table events and a table bets. All bets placed for a particular event are located in the bets table while information about the event is stored in the events table.
Let's say I have these tables:
events table:
id event_title
1 Call of Duty Finals
2 DOTA 2 Semi-Finals
3 GTA V Air Race
bets table:
id event_id amount
1 1 $10
1 2 $50
1 2 $100
1 3 $25
1 3 $25
1 3 $25
I want to be able to sort by popularity aka how many bets have been placed for that event and by prize aka the total amount of money for that event.
SORTING BY PRIZE
Obviously this query doesn't work but I want to do something like this:
SELECT * FROM bets GROUP BY event_id SORT BY amount
amount from the query above should be a cumulative value of all the bet amounts for that event_id added together, so this query would return
Array (
[0]=>Array(
'event_id'=>2
'amount'=>$150
)
[1]=>Array(
'event_id'=>3
'amount'=>$75
)
[2]=>Array(
'event_id'=>1
'amount'=>$10
)
)
SORTING BY POPULARITY
Obviously this query doesn't work either but I want to do something like this:
SELECT * FROM bets GROUP BY event_id SORT BY total_rows
total_rows from the query above should be the number of rows that exist in the bets table added together, so this query would return
Array (
[0]=>Array(
'event_id'=>3
'total_rows'=>3
)
[1]=>Array(
'event_id'=>2
'total_rows'=>2
)
[2]=>Array(
'event_id'=>1
'total_rows'=>1
)
)
I wouldn't necessarily need it to return the total_rows value as I could calculate that, but it does need to be sorted by the number of occurrences for that particular event_id in the bets table.
I think count and sum are your friends here:
SELECT COUNT(event_id) AS NumberBets,
SUM(amount) AS TotalPrize
FROM bets
GROUP BY event_id
Should do the trick.
Then you can ORDER BY either the NumberBets(popularity) or TotalPrize as you need. JOIN only needed if you want event titles.
You can use SUM and COUNT aggregate functions:
SELECT
e.id AS event_id, SUM(amount) AS sum_amount
FROM [events] e
LEFT JOIN bets b
ON b.event_id = e.id
GROUP BY
e.id
ORDER BY
sum_amount DESC
SELECT
e.id AS event_id, COUNT(e.event_id) AS no_of_events
FROM [events] e
LEFT JOIN bets b
ON b.event_id = e.id
GROUP BY
e.id
ORDER BY
no_of_events DESC

Exclude users with zero score from ranking

I've come across this great solution to rank users based on their score in mysql.
SELECT d.*, c.ranks
FROM
(
SELECT Score, #rank:=#rank+1 Ranks
FROM
(
SELECT DISTINCT Score
FROM tableName a
ORDER BY score DESC
) t, (SELECT #rank:= 0) r
) c
INNER JOIN tableName d
ON c.score = d.score
However, I would like to know if there is a way to exclude users with 0 or without score from the ranking, but still return these users in the results.
So for example
KEY username password score Ranks
1 Anna 123 8 1
2 Bobby 345 5 2
3 Helen 678 5 2
4 Jon 567 -2 3
5 Arthur ddd -9 4
4 Chris 444 0
5 Liz eee 0
Since you want to SELECT all of your users, start with that:
SELECT
user.*
FROM user
Now, we want to add in a table of ranked users, so we'll start to add in complexity. We're aiming to get a temporary table of ranked users, so we'll LEFT JOIN as to not filter out any non-ranked users.
SELECT
user.*,
ranked_user.score,
ranked_user.rank
FROM user
LEFT JOIN(
// subquery
) AS ranked_user ON ranked_user.user_id = user.id
Then we'll have to figure out the section for the subquery where the ranks are determined. You have most of it already, I'm just going to add in an IF statement to only assign a rank if they have a score. Altogether, you get this:
SELECT
user.*,
ranked_user.score,
ranked_user.rank
FROM user
LEFT JOIN(
SELECT
score,
user_id,
IF(score = 0 OR score IS NULL, null, #rank:=#rank+1) AS rank
FROM(
SELECT
DISTINCT score,
user_id
FROM stat
ORDER BY score DESC
) t, (SELECT #rank:= 0) r
) ranked_user ON ranked_user.user_id = user.user_id
Here's how that IF statement works:
IF(score = 0 OR score IS NULL,
# if c.score is 0 or missing
null,
# set the value to null
#rank:=#rank+1)
# otherwise, calculate a rank
AS rank
# call this value "rank"
Just a side note: I'd change user.* and actually list each column of user that you want. That's considered best practice.

MySQL query problems with combined SUM

I have three tables here, that I'm trying to do a tricky combined query on.
Table 1(teams) has Teams in it:
id name
------------
150 LA Lakers
151 Boston Celtics
152 NY Knicks
Table 2(scores) has scores in it:
id teamid week score
---------------------------
1 150 5 75
2 151 5 95
3 152 5 112
Table 3(tickets) has tickets in it
id teamids week
---------------------
1 150,152,154 5
2 151,154,155 5
I have two queries that I'm trying to write
Rather than trying to sum these each time i query the tickets, I've added a weekly_score field to the ticket. The idea being, any time a new score is entered for the team, I could take that teams id, get all tickets that have that team / week combo, and update them all based on the sum of their team scores.
I've tried the following to get the results i'm looking for (before I try and update them):
SELECT t.id, t.teamids, (
SELECT SUM( s1.score )
FROM scores s1
WHERE s1.teamid
IN (
t.teamids
)
AND s1.week =11
) AS score
FROM tickets t
WHERE t.week =11
AND (t.teamids LIKE "150,%" OR t.teamids LIKE "%,150")
Not only is the query slow, but it also seems to not return the sum of the scores, it just returns the first score in the list.
Any help is greatly appreciated.
If you are going to match, you'll need to accommodate for the column only having one team id. Also, you'll need to LIKE in your SELECT sub query.
SELECT t.id, t.teamids, (
SELECT SUM( s1.score )
FROM scores s1
WHERE
(s1.teamid LIKE t.teamids
OR CONCAT("%,",s1.teamid, "%") LIKE t.teamids
OR CONCAT("%",s1.teamid, ",%") LIKE t.teamids
)
AND s1.week =11
) AS score
FROM tickets t
WHERE t.week =11
AND (t.teamids LIKE "150,%" OR t.teamids LIKE "%,150" OR t.teamids LIKE "150")
You don't need SUM function here ? The scores table already has it? And BTW, avoid subqueries, try the left join (or left outer join depending on your needs).
SELECT t.id, t.name, t1.score, t2.teamids
FROM teams t
LEFT JOIN scores t1 ON t.id = t1.teamid AND t1.week = 11
LEFT JOIN tickets t2 ON t2.week = 11
WHERE t2.week = 11 AND t2.teamids LIKE "%150%"
Not tested.
Well not the most elegant query ever, but it should word:
SELECT
tickets.id,
tickets.teamids,
sum(score)
FROM
tickets left join scores
on concat(',', tickets.teamids, ',') like concat('%,', scores.teamid, ',%')
WHERE tickets.week = 11 and concat(',', tickets.teamids, ',') like '%,150,%'
GROUP BY tickets.id, tickets.teamids
or also this:
SELECT
tickets.id,
tickets.teamids,
sum(score)
FROM
tickets left join scores
on FIND_IN_SET(scores.teamid, tickets.teamids)>0
WHERE tickets.week = 11 and FIND_IN_SET('150', tickets.teamids)>0
GROUP BY tickets.id, tickets.teamids
(see this question and the answers for more informations).

Get User Rank Based on Sum with Pagination

There are several rank posts out there but I have yet to see one dealing with when the results are paginated and when the ranking criteria (in this case: points) is equal to the previous user. I have tried a few of the pre-existing examples but none have worked.
I have a table called "users" with the column "id". I also have a table called "points" with the columns "user_id" and "amount".
I need:
1.) Users with duplicate sum of points to have the same rank
Points Table
user_id amount
1 10
2 20
1 5
3 20
3 -5
4 5
Rank should be
rank user_id total
1 2 20
2 1 15
2 3 15
3 4 5
2.) Needs to maintain the ranking from one page to another so the rank has to be gathered in the query and not the resulting PHP.
3.) Display ALL users not just ones with rows in the points table because some users have 0 points and I want to display them last.
Right now I'm just listing the users in order of their points but their rank is not gathered because it wasn't working.
$getfanspoints = mysql_query("SELECT DISTINCT id,
(SELECT SUM(amount) AS points FROM points WHERE points.user_id = users.id) AS points
FROM users
ORDER BY points DESC LIMIT $offset, $fans_limit", $conn);
I've read these solutions and none have worked.
[Roland's Blog][1]
[How to get rank based on SUM's][2]
[MySQL, get users rank][3]
[How to get rank using mysql query][4]
and a few others whose link I can't find right now.
Any suggestions?
[EDIT]
I used ypercube's bottom answer.
SELECT COUNT(*) AS rank
, t.user_id
, t.total
FROM
( SELECT user_id
, SUM(amount) AS total
FROM points
GROUP BY user_id
) AS t
JOIN
( SELECT DISTINCT
SUM(amount) AS total
FROM points
GROUP BY user_id
) AS dt
ON
t.total <= dt.total
GROUP BY t.user_id
ORDER BY rank
, user_id
But the above may be really slow with a big table and points awarded often. It might be really better to have just this and calculate the ranks in your application code:
SELECT users.id AS user_id
, SUM(amount) AS total
FROM
users
LEFT JOIN
points
ON points.user_id = users.id
GROUP BY users.id
ORDER BY total DESC
, user_id
This will work, too (edited, to work with the users table and with OFFSET):
SELECT *
FROM
( SELECT
#rank := #rank + (#t <> total) AS rank
, user_id
, #t := total AS total
FROM
( SELECT users.id AS user_id
, COALESCE(SUM(amount),0) AS total
FROM users
LEFT JOIN points
ON users.id = points.user_id
GROUP BY users.id
) AS o
CROSS JOIN
( SELECT #rank := 0, #t := -999999
) AS dummy
ORDER BY total DESC
, user_id
) tmp
LIMIT x OFFSET y

Show biggest margin between 1st and 2nd within group

Name Day Points
Brian 1 6
Tom 1 11
Freddy 1 7
Kim 2 10
Sandra 2 1
Brian 2 3
I need to know who has won with the biggest margin to number two - but only between people on the same day.
Thus if done properly it would tell me Kim has won by the biggest margin.
I don't quite know how to handle on this one.
select
first_place.name,
max_points-max(points) as max_margin
from the_table
inner join
(select name, day, max(points) as max_points
from the_table group by day) as first_place
on the_table.day=first_place.day
where the_table.points<max_points
group by the_table.day
order by max_margin desc limit 1 ;
This would need to be done with two sub queries... Inner most to get the highest score for a single day, then, find the next highest scrore under the first place position, then find the margin... However, due to your sample data of just names, no consideration for unique names which would otherwise be by some internal ID... Say "Brian" in your sample data... is it the same Brian on both days, or is it a different person. Additionally, what if two people are tied for first place with 11 points, then my query would show BOTH people in first place before the margin to the now "3rd" place person as the detected margin. You will probably have to modify some to accommodate such conditions described..
SELECT
FS.Day,
FS.FirstPlace,
FS.SecondPlace,
FS.FirstPlace - FS.SecondPlace as Margin,
G.Name
FROM
( SELECT G2.Day,
FirstPlace.FirstPlacePoints FirstPlace,
MAX( G2.Points ) as SecondPlace
FROM
Games G2,
( SELECT Day,
MAX( Points ) as FirstPlacePoints
FROM
Games
GROUP BY
Day ) FirstPlace
WHERE
G2.Day = FirstPlace.Day
AND G2.Points < FirstPlace.FirstPlacePoints
GROUP BY
1, 2 ) as FS,
Games G
WHERE
FS.Day = G.Day
and FS.FirstPlace = G.Points
ORDER BY
Margin desc
LIMIT 1