MYSQL get user rank by performance - mysql

I am not an expert at MySQL which is proven obvious today but I need help ordering users by sales.
I have two tables. One called users
+------------------------+
+ id | fname | lname +
+------------------------+
+ 1 | bob | french +
+ 2 | fred | smith +
+ 3 | ted | nugent +
+ 4 | kyle | frank +
+------------------------+
and another for sales
+------------------------------------------------------------+
+ id | date | commission | lister | seller +
+------------------------------------------------------------+
+ 1 | 2017-11-01 | 2200 | 2 | 2 +
+ 2 | 2018-01-15 | 1800 | 1 | 1 +
+ 3 | 2017-11-07 | 3600 | 2 | 1 +
+ 4 | 2017-11-30 | 1252 | 4 | 1 +
+------------------------------------------------------------+
Commission is split 50/50 by the lister and seller.
the lister and seller columns correspond to the user id
I need to find 2 things.
A) a persons (eg. Bob French) RANK for SUM of his commissions, for sales THIS MONTH
So Bob should have 50% of sale #3, and 50% of sale #4.
sale #2 isn't in this month
so the half of sale #3 (1800) and half of sale #3 (626) is 2426
The ranks for this month SHOULD be
Fred # 4000 total commission
Bob # 2426 total commission
Kyle # 626 total commission
Ted # 0 total commission
I need to return RANK (which is 2 in this case) and TOTAL COMMISSION (which is 2426 in this case) for USER (bob in this case) in this month (november in this case)
B) I need show the entire above table in a different statement (for supervisors to see everyone ranked. users only see their ranking anonymously.
It may be the same SQL query for both and then I just pluck the user by id from the result set unless there is a more efficient way.
What I've tried
SELECT x.id, x.fname, x.lname, y.commission,
FIND_IN_SET( commission , (
SELECT GROUP_CONCAT( commission ORDER BY commission DESC )
FROM sales
) ) AS rank
FROM users x
JOIN sales y ON x.id IN (lister, seller)
ORDER BY rank ASC
This is working BUT it doesn't limit it to the month. I have tried adding
WHERE (date between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW() )
but it seems to have no effect, as I tried limiting the dates to 2010, and results came back regardless.
Also the above query is returning the RANK by whoever got the SINGLE HIGHEST commission, not the sum of their commissions.
Please help.

You can use UNION ALL :
select x.id, x.name, sum(commision) as commision from
(select a.date as date, b.id as id, b.fname as name, a.commision/2 as commision
from sales a join users b
on a.lister = b.id
union all
select a.date as date, b.id as id, b.fname as name, a.commision/2 as commision
from sales a join users b
on a.seller = b.id) as x
where x.date between '2017-11-01' and '2017-11-30'
group by x.id, x.name
Here the fiddle : http://sqlfiddle.com/#!9/8ae69a/7
For your filter WHERE and ordering, you can add :
select x.id, x.name, sum(commision) as commision from
(select a.date as date, b.id as id, b.fname as name, a.commision/2 as commision
from sales a join users b
on a.lister = b.id
union all
select a.date as date, b.id as id, b.fname as name, a.commision/2 as commision
from sales a join users b
on a.seller = b.id) as x
where (x.date between DATE_FORMAT(NOW() ,'%Y-%m-01') AND NOW() )
group by x.id, x.name
order by commision
- EDIT -
Adding rank : (fiddle : http://sqlfiddle.com/#!9/8ae69a/30)
SET #rank = 0;
SELECT #rank := #rank + 1 AS rank,
y.id,
y.name,
y.commision
FROM (SELECT x.id,
x.name,
Sum(commision) AS commision
FROM (SELECT a.date AS date,
b.id AS id,
b.fname AS name,
a.commision / 2 AS commision
FROM sales a
JOIN users b
ON a.lister = b.id
UNION ALL
SELECT a.date AS date,
b.id AS id,
b.fname AS name,
a.commision / 2 AS commision
FROM sales a
JOIN users b
ON a.seller = b.id) AS x
WHERE ( x.date BETWEEN Date_format(Now(), '%Y-%m-01') AND Now() )
GROUP BY x.id,
x.name
ORDER BY x.commision) AS y

select #a:=#a+1 as rank,'#',u.fname ,COALESCE(sum(commission),0)commission,'total commission' from (SELECT sub.dates,(commission*2)commission,sub.lister as id
FROM (SELECT s.dates,(s.commission/4)as commission,s.lister,(select #a:=0)rank
FROM sales s where s.dates
between '2017-01-01' and '2017-12-31')sub
union all
SELECT sub.dates,(commission*2)seller,sub.seller FROM
(SELECT s.dates,(s.commission/4)as commission,s.sellerFROM sales s where s.dates
between '2017-01-01' and '2017-12-31')sub)main
right join users u on u.id = main.id
group by main.id order by sum(commission)desc

Related

How to pick a row randomly based on a number of tickets you have

I have this table called my_users
my_id | name | raffle_tickets
1 | Bob | 3
2 | Sam | 59
3 | Bill | 0
4 | Jane | 10
5 | Mike | 12
As you can see Sam has 59 tickets so he has the highest chance of winning.
Chance of winning:
Sam = 59/74
Bob = 3/74
Jane = 10/74
Bill = 0/74
Mike = 12/74
PS: 74 is the number of total tickets in the table (just so you know I didn't randomly pick 74)
Based on this, how can I randomly pick a winner, but ensure those who have more raffles tickets have a higher chance of being randomly picked? Then the winner which is picked, has 1 ticket deducted from their total tickets
UPDATE my_users
SET raffle_tickets = raffle_tickets - 1
WHERE my_id = --- Then I get stuck here...
Server version: 5.7.30
For MySQL 8+
WITH
cte1 AS ( SELECT name, SUM(raffle_tickets) OVER (ORDER BY my_id) cum_sum
FROM my_users ),
cte2 AS ( SELECT SUM(raffle_tickets) * RAND() random_sum
FROM my_users )
SELECT name
FROM cte1
CROSS JOIN cte2
WHERE cum_sum >= random_sum
ORDER BY cum_sum LIMIT 1;
For 5+
SELECT cte1.name
FROM ( SELECT t2.my_id id, t2.name, SUM(t1.raffle_tickets) cum_sum
FROM my_users t1
JOIN my_users t2 ON t1.my_id <= t2.my_id
WHERE t1.raffle_tickets > 0
GROUP BY t2.my_id, t2.name ) cte1
CROSS JOIN ( SELECT RAND() * SUM(raffle_tickets) random_sum
FROM my_users ) cte2
WHERE cte1.cum_sum >= cte2.random_sum
ORDER BY cte1.cum_sum LIMIT 1;
fiddle
You want a weighted pull from a random sample. For this purpose, variables are probably the most efficient solution:
select u.*
from (select u.*, (#t := #t + raffle_tickets) as running_tickets
from my_users u cross join
(select #t := 0, #r := rand()) params
where raffle_tickets > 0
) u
where #r >= (running_tickets - raffle_tickets) / #t and
#r < (running_tickets / #t);
What this does is calculate the running sum of tickets and then divide by the number of tickets to get a number between 0 and 1. For example this might produce:
my_id name raffle_tickets running_tickets running_tickets / #t
1 Bob 3 3 0.03571428571428571
2 Sam 59 62 0.7380952380952381
4 Jane 10 72 0.8571428571428571
5 Mike 12 84 1
The ordering of the original rows doesn't matter -- which is why there is no ordering in the subquery.
The ratio is then used with rand() to select a particular row.
Note that in the outer query, #t is the total number of tickets.
Here is a db<>fiddle.

MYSQL - count how many times a person comes second in a competition

I have the following table called 'players' which contains scores from 3 games played by Tim, Bob and Jon.
playid | name | score
1 | Tim | 10
1 | Bob | 5
2 | Tim | 5
2 | Bob | 10
3 | Tim | 5
3 | Bob | 10
3 | Jon | 4
I want to be able to count the number of times that Tim, Bob and Jon have come second i.e. Tim = 2, Bob = 1, Jon = 0.
I have the following query:
SELECT name FROM players WHERE playid = 1 ORDER BY score Desc LIMIT 1, 1
Which returns the name of the person in second place in the first game i.e. Bob, but I can't figure out how to extend this to cover all games and players. Eventually I also want to be able to count the number of times they come 3rd, 4th etc.
Thanks in advance
Try with following one:
SELECT count(playid), name, score
FROM `players`
WHERE score = (SELECT MAX(score) FROM players WHERE score < (SELECT MAX(score) FROM players))
GROUP BY score, name;
With multiple joins and groupings:
select pl.name, ifnull(counter, 0) counter from (
select distinct name from players) pl
left join (
select players.name, count(*) counter from players
inner join (
select p.playid, max(p.score) as secondscore from (
select players.* from players
left join (
select playid, max(score) as maxscore
from players
group by playid) p
on p.playid = players.playid and p.maxscore = players.score
where p.maxscore is null) p
group by p.playid
) p
on p.playid = players.playid and p.secondscore = players.score
group by players.name) p
on p.name = pl.name
See the demo
You can use this query below to find the secondly-ranked people :
SELECT q2.playid, q2.name
FROM
(
SELECT q1.* , if(switch1,#r:=#r+1,#r:=0) as switch2
FROM
(
SELECT playid, score, name,
if(playid=#p,#p:=0,#p:=playid+1) as switch1
FROM players
JOIN ( SELECT #p:=0, #r:=-1 ) p2
ORDER BY playid, score desc
) q1
) q2
WHERE q2.switch2 = 1
ORDER BY q2.playid
playid name
------ ----
1 Bob
2 Tim
3 Tim
4 George
Rextester Demo
As per the comment by Raymond Nijland, in MySQL 8.0+ you can use window functions to achieve this:
SELECT name, COUNT(*) AS second_place_count
FROM (
SELECT
name,
playid,
ROW_NUMBER() OVER (PARTITION BY playid ORDER BY score DESC) AS rn
FROM players
) AS ranks
WHERE ranks.rn = 2
GROUP BY name
...or if you want to extend this to all places:
SELECT
name,
rn AS place,
COUNT(*) AS place_count
FROM (
SELECT
name,
playid,
ROW_NUMBER() OVER (PARTITION BY playid ORDER BY score DESC) AS rn
FROM players
) AS ranks
GROUP BY
name,
rn

MySQL includes a specific row with order by

Given 2 tables, I want to generate top 3 highest amount from [Purchase] table.
Additional criteria is [Crocs] must be included in top 3 of the records.
I have following SQL, but it cannot generates the result as I wanted (Result A), please guide me on how to pull out the result in Result B. Thank you.
Table (Purchase):
Purchase_ID | StoreID | Amount
------------|---------|--------
1 | 21 | 22
2 | 23 | 13
3 | 25 | 6
4 | 26 | 23
5 | 28 | 18
Table (Store):
Store_ID | StoreName
---------|----------
21 | Adidas
22 | Nike
23 | Puma
24 | New Balance
25 | Crocs
26 | Converse
SQL:
SELECT IF(SUM(amount) IS NULL, 0, SUM(amount)) as totalAmount
FROM (
SELECT a.amount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
GROUP BY a.amount
HAVING b.StoreName = 'Crocs'
ORDER BY a.amount DESC
LIMIT 3
) t
Result A: $6
Explanation A: Amount of Crocs is $6
Result B: $51
Explanation B: Total Amount of top 3 = $22 (Adidas) + 23 (Puma) + $6 (Crocs)
The answer from scaisEdge is almost right, but the first query could also return a row with crocs and the sorting is wrong (order by max(a.amount) limit 2 means that the lowest 2 results will be shown). Additionally you could wrap the query in another select query to sort the results
SELECT * FROM (
SELECT b.storename, max(a.amount) as maxAmount
FROM purchase a
INNER JOIN store b ON a.store_id = b.storeid
WHERE b.storename != 'crocks'
GROUP BY a.storename
ORDER BY max(a.amount) DESC
LIMIT 2
UNION
SELECT b.storename, a.amount as maxAmount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
WHERE b.storename='crocks'
ORDER BY a.amount DESC
LIMIT 1
) ORDER BY maxAmount DESC
You could use an union
SELECT b.storename, max(a.amount)
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
GROUP BY a.storename
order by max(a.amount) limit 2
union
SELECT b.storename, a.amount
FROM purchase a
INNER JOIN store b
ON a.store_id = b.storeid
where b.storename='crocks'
try this one:
SELECT sum(amount)as sum_amount,a.store_id,storename,category from
(select amount,store_id from tbl_purchase) as a
inner JOIN
(select store_id,storename,category from tbl_store)as b on a.store_id = b.store_id where b.category = 'supermarket' GROUP BY category

Divide with the rollup value

I have this code where it sums up the hours of the employee and uses rollup to get the total of the hours:
SELECT IFNULL(users, 'Total') AS Employee,
SUM(actual) AS Amount
FROM table1
WHERE name = "ProjectName"
GROUP BY users
WITH ROLLUP
Employee | Amount
A | 15
B | 10
C | 10
Total | 35
What I would like to do for my third column (Percent) is to divide the sum(actual) to the value of the total to get the percentage.
But for that Percent column I don't need to get the Total Percent.
The total value is not constant to just 35.
Employee | Amount | Percent
A | 15 | 42.85
B | 10 | 28.57
C | 10 | 28.57
Total | 35 |
How can I do that?
Here's the sqlfiddle: http://sqlfiddle.com/#!2/4543b/5
This works as desired:
SET #project_name = 'ProjectName';
SELECT IFNULL(users, 'Total') AS Employee, SUM(actual) AS Amount,
IF(ISNULL(users), '', TRUNCATE(SUM(actual) / sum_table.amount_sum * 100, 2)
) AS Percent
FROM Table1
INNER JOIN (
SELECT SUM(actual) AS amount_sum
FROM Table1
WHERE name = #project_name
) AS sum_table
WHERE name = #project_name
GROUP BY users
WITH ROLLUP;
DEMO # SQL Fiddle
Perhaps a job best left to the logic tier of your application, but if you absolutely must do it in the data tier then you merely need to join your query with another that finds the overall total:
SELECT IFNULL(users, 'Total') AS Employee,
SUM(actual) AS Amount,
SUM(actual)/t.Total AS Percent
FROM Table1, (
SELECT SUM(actual) AS Total
FROM Table1
WHERE name = 'ProjectName'
) t
WHERE name = 'ProjectName'
GROUP BY users WITH ROLLUP
SELECT if(users is NULL,'Total',users) as Employee, sum(actual) as Amount,
(CASE
WHEN users is not null THEN CAST(sum(actual)/sum.sumAmt * 100 as DECIMAL(10,2))
END) as Percent
FROM Table1, (SELECT sum(actual) as sumAmt FROM Table1
WHERE name = 'ProjectName') sum
WHERE name = "ProjectName"
GROUP BY users
WITH ROLLUP
DEMO

Mysql refrencing derived tables from nested query

I posted something similar to this yesterday, but now I'd like something a little different from my query-
I'm trying to query a database to retrieve the number of one-time users who have visited a website over time. The data looks something like this:
Day | UserID
1 | A
1 | B
2 | B
3 | A
4 | B
4 | C
5 | D
I'd like the query result to look this this
Time Span | COUNT(DISTINCT UserID)
Day 1 to Day 1 | 2
Day 1 to Day 2 | 1
Day 1 to Day 3 | 0
Day 1 to Day 4 | 1
Day 1 to Day 5 | 2
The result is 2,1,0,1,2 because, at the end of those days, there are X number of users who have visited a single time. e.g. for day 5, at the end of day 5, users c and d have visited only once each.
I think I'm looking for a query similar to this:
select d.day, (select count(distinct userid) from visits where day<=d.day)
from (select distinct day from visits) d
The difference between the query above and what I'm looking for is that I'd like this new query to consider only one-time users for each time span, and not repeat users.
Thanks
This subquery should work for the clarified requirements.
select d.day, count(distinct case when b.userid is null then a.userid end)
from (select day from visits group by day) d
inner join
(
select a.day, a.userid, count(*) c
from visits a
join visits b on a.userid=b.userid and b.day <= a.day
group by a.day, a.userid
having count(*) = 1
) a on a.day <= d.day
left join
(
select a.day, a.userid, count(*) c
from visits a
join visits b on a.userid=b.userid and b.day <= a.day
group by a.day, a.userid
having count(*) > 1
) b on a.userid = b.userid and b.day <= d.day
group by d.day
Original
You must have taken the idea from SQL Server - it is the only RDBMS (IIRC) that will allow you to reference a twice removed (nesting) query. Please indicate what you want and we can rewrite the query.
For the exact query shown, you don't need 2 levels of subquery
SELECT
C.col_c1 AS Data,
(
SELECT count(col_b1)
FROM tbl
WHERE col_b2 <= C.col_c1
) A
FROM (
SELECT col_c1 # subquery to get distinct c1
FROM tbl
GROUP BY col_c1) C;