I have a query that is something like the one below. My goal is to add all the 'total_points' and retrieve the single result of the sum of all the rows.
SELECT
SUM(
COALESCE(
CASE
WHEN COUNT(DISTINCT `table_1`.views) > 50 OR COUNT(DISTINCT `table_1`.views) = 50 AND COUNT(DISTINCT `table_1`.views) < 100 THEN COUNT(DISTINCT `table_1`.views) +5
ELSE COUNT(DISTINCT `table_1`.views)
END,0)
+
CASE
WHEN COUNT(DISTINCT `table_2`.views) > 50 OR COUNT(DISTINCT `table_2`.views) = 50 AND COUNT(DISTINCT `table_2`.views) < 100 THEN COUNT(DISTINCT `table_2`.views) +5
ELSE COUNT(DISTINCT `table_2`.views)
END,0)) AS sum
FROM `table_1`
LEFT JOIN `table_2`
ON `table_1`.id = `table_2`.id
GROUP BY `table_1`.primary_id
This will give me a result of something like this
rank | total_points
1 321
2 111
3 100
4 90
5 72
6 60
7 45
8 23
9 11
10 5
This is my desire results:
sum |
838
Try this:
SELECT IFNULL(rank, 'Sum') rank, total_points
FROM (SELECT #rank := #rank + 1 AS rank, T1.total_points
FROM (SELECT COALESCE((CASE WHEN COUNT(DISTINCT t1.views) betwnn 50 AND 100 THEN COUNT(DISTINCT t1.views) + 5 ELSE COUNT(DISTINCT t1.views) END) +
(CASE WHEN COUNT(DISTINCT t2.views) betwnn 50 AND 100 THEN COUNT(DISTINCT t2.views) + 5 ELSE COUNT(DISTINCT t2.views) END), 0) AS total_points
FROM table_1 t1
LEFT JOIN table_2 t2 ON t1.id = t2.id
GROUP BY t1.primary_id
ORDER BY total_points
) AS T1, (SELECT #rank := 0) AS r
ORDER BY total_points DESC
) AS A
GROUP BY rank WITH ROLLUP
Related
Assume we have the following MYSQL-Query to generate a leaderboard:
SELECT x.player_id, x.position,x.leaderboard_value
FROM (SELECT player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
which produces a leaderboard like this:
Textfile as CSV: https://www.dropbox.com/s/70xw3ocbonqs98s/sql.csv?dl=0
How do I change or extend the query to deliver a specific players position, and the total amount of positions (highest position)
for above table and player_id=10649 I'd expect a result-table with 1 row containing these fields:
position: 6,
totalpositions: 20,
percentage= 0.3 (which is 6/20)
Backstory of this is to join this percentage with a different table rank_map(rank_id,minvalue,maxvalue) defining ranks (rank is "9" when this value is between 0.2 and 0.4 for example)
In the end this query should simply return
rank: 9
as answer
Thank you very much.
UPDATE:
with Gordon Linoffs answer, using this query:
SELECT MAX(CASE WHEN player_id = 10649 THEN position END) as play_position,
COUNT(*) as total_position,
MAX(CASE WHEN player_id = 10649 THEN position END) / COUNT(*) as ratio
FROM (SELECT t1.*, #rownum := #rownum + 1 AS position
FROM table1 t1 CROSS JOIN
(SELECT #rownum := 0) r
WHERE restrictive_value < 200
ORDER BY leaderboard_value DESC
) x;
I can get this table:
A final step is left, which is, how to join it with the rank(rank_id, minvalue,maxvalue) table to only get the rank_id row where ratio is between minvalue and maxvalue?
You can achieve this by adding COUNT(*) to your subquery JOIN to get the total number of rows, then display the percentage as position/total:
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
Some of the output from your sample data:
player_id position leaderboard_value percentage
2730 1 1090 0.01
1369848 2 1017 0.02
1665922 3 960 0.03
1607632 4 910 0.04
1853500 5 909 0.05
10649 6 883 0.06
1538490 7 877 0.07
1898051 8 866 0.08
1510162 9 828 0.09
1898129 10 825 0.1
1863538 11 821 0.11
1522562 12 806 0.12
1380267 13 805 0.13
1404318 14 797 0.14
8793 15 769 0.15
21793 16 767 0.16
14658 17 756 0.17
1690659 18 729 0.18
1429094 19 723 0.19
1727977 20 719 0.2
SQLFiddle Demo
To get the data for only a specific player, just add a WHERE x.player_id=nnnn clause e.g.
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
WHERE x.player_id = 10649
Output:
player_id position leaderboard_value percentage
10649 6 883 0.06
To then get their ranking from the rank table, you just need to JOIN it based on percentage (note you have to use the formula as you can't use an alias in a JOIN):
SELECT x.player_id, x.position,x.leaderboard_value, (x.position/x.total) AS percentage, m.rank_id
FROM (SELECT total, player_id,#rownum := #rownum + 1 AS position,leaderboard_value
FROM table1
JOIN (SELECT #rownum := 0) r
JOIN (SELECT COUNT(*) AS total FROM table1) c
WHERE restrictive_value<200
ORDER BY leaderboard_value DESC) x
JOIN rank_map m ON m.minvalue <= (x.position/x.total) AND m.maxvalue > (x.position/x.total)
WHERE x.player_id = 10649
Output:
player_id position leaderboard_value percentage rank_id
10649 6 883 0.06 3
Updated Demo
For this purpose, you can use conditional aggregation:
SELECT MAX(CASE WHEN player_id = 10649 THEN position END) as play_position,
COUNT(*) as total_position,
MAX(CASE WHEN player_id = 10649 THEN position END) / COUNT(*) as ratio
FROM (SELECT t1.*, #rownum := #rownum + 1 AS position
FROM table1 t1 CROSS JOIN
(SELECT #rownum := 0) r
WHERE restrictive_value < 200
ORDER BY leaderboard_value DESC
) x;
You can use either COUNT(*) or MAX(position) for the highest position.
I am fiddling on SQL fiddle with group by clause and finding the percentage based on the temp table "A" result.
Here is the fiddle.
http://sqlfiddle.com/#!9/faf2f/6959
I did come across that there are different ways of achieving this like by using union all but my question is why this query brings back only one row instead of two rows.
List item
Data:
TotalCost
230
200
100
1254
Query:
SELECT Category, Cnt , Cnt/sum(Cnt) as percent from (
SELECT
Case When TotalCost < 301 Then '0-300'
Else ' > 300' End as Category,
count(*) as cnt
FROM Cars
group by Category
) A
;
Expected Result:
Category Cnt percent
0-300 3 75
> 300 1 25
Actual Result:
Category Cnt percent
> 300 1 25
you can try to group by case when instead of an alias name.
seconde your total count need to do all count so you can do a subquery.
look like this.
SELECT Category, Cnt , Cnt/(select count(*) from Cars) * 100 as percent
from (
SELECT
(Case When TotalCost < 301 Then '0-300'
Else ' > 300' End) as Category,
count(*) as cnt
FROM Cars
GROUP BY (Case When TotalCost < 301 Then '0-300'
Else ' > 300' End)
) A
ORDER BY 3 DESC
or you can use CROSS JOIN to get the total.
SELECT Category, Cnt , Cnt/v.totle * 100 as percent
from (
SELECT
(Case When TotalCost < 301 Then '0-300'
Else ' > 300' End) as Category,
count(*) as cnt
FROM Cars
GROUP BY (Case When TotalCost < 301 Then '0-300'
Else ' > 300' End)
) A CROSS JOIN (select count(*) totle from Cars) v
sqlfiddle
Results:
| Category | Cnt | percent |
|----------|-----|---------|
| 0-300 | 3 | 75 |
| > 300 | 1 | 25 |
i'm creating a Rock Papper Scisors game, the problem that i'm facing is that i need to set a winner based on a Best Of 3, 5 or 7, and to do so i need to calculate the number of consecutive querys my bet table is simple:
BO3
ID|GAME_ID|WINNER
1 |145 |15
2 |145 |14
3 |145 |15
4 |145 |15
15 needs to win , how can i calculate it in mysql?
ex:
GAME_ID|WINNER|CONSECUTIVES
145|15 |2
Thanks a lot.
I think you are going to need variables for this:
select gameid, winner, max(rn)
from (select s.*,
(#rn := if(#gw = concat_ws(':', gameid, winner), #rn + 1,
if(#gw := concat_ws(':', gameid, winner), 1, 1)
)
) as rn
from scores s cross join
(select #gw := '', #rn := 0) params
order by s.id
) s
group by gameid, winner;
Here is a SQL Fiddle.
Maybe something like this:
select y.winner,
case when y2.cnt <= 3 then 'Best of 3'
when y2.cnt <= 5 then 'Best of 5'
when y2.cnt <= 7 then 'Best of 7'
end, count(*)
from yourtable y join (
select count(*) cnt, gameid
from yourtable
group by gameid) y2 on y.gameid = y2.gameid
group by y.gameid, y.winner, y2.cnt
having count(*) = 2 and y2.cnt <= 3 or
count(*) = 3 and y2.cnt <= 5 or
count(*) = 4 and y2.cnt <= 7
SQL Fiddle Demo
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions must demonstrate a minimal understanding of the problem being solved. Tell us what you've tried to do, why it didn't work, and how it should work. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
In a MySQL DB, I have a purchases table that has these columns:
USERID PURCHASE_AMOUNT
3 20
9 30
3 5
4 5
1 10
1 5
I would like to generate a report like this
SUM_OF_PURCHASES_RANGE NUM_OF_USERS
0-1 0
1-5 1
5-20 1
20-30 2
Where it means: there are 0 users who bought up to 1(SUM of purchases) (inclusive), there are 1 users who bought between 1 to 5 etc...
What query should I use to generate it?
You can create the range using a UNION, and just LEFT JOIN to that to get all categories; (edited for your change in the desired result)
SELECT CONCAT(base.lower,'-',base.upper) PURCHASE_RANGE, COUNT(userid) NUM_OF_USERS
FROM (
SELECT 0 lower, 1 upper UNION SELECT 2, 5 UNION SELECT 6,20 UNION SELECT 21,30
) base
LEFT JOIN (
SELECT userid, SUM(purchase_amount) pa FROM purchases GROUP BY userid
) p
ON p.pa >= base.lower AND p.pa <= base.upper
GROUP BY base.upper
An SQLfiddle to test with.
More easier syntax :
SELECT PURCHASE_RANGE , COUNT(*) as NUM_OF_USERS
FROM
(
SELECT
CASE
WHEN PURCHASE_AMOUNT <= 1 THEN 1
WHEN PURCHASE_AMOUNT > 1 AND PURCHASE_AMOUNT <= 5 THEN 5
WHEN PURCHASE_AMOUNT > 5 AND PURCHASE_AMOUNT <= 10 THEN 10
WHEN PURCHASE_AMOUNT > 10 AND PURCHASE_AMOUNT <= 20 THEN 20
WHEN PURCHASE_AMOUNT > 20 AND PURCHASE_AMOUNT <= 30 THEN 30 END AS PURCHASE_RANGE
FROM Table1
) AS A
GROUP BY PURCHASE_RANGE
ORDER BY PURCHASE_RANGE
SqlFiddle
try this
select PURCHASE_RANGE , NUM_OF_USERS
from (
select 1 as PURCHASE_RANGE ,count(*) as NUM_OF_USERS from table1 where PURCHASE_AMOUNT between 0 and 1
union all
select 5 ,count(*) from table1 where PURCHASE_AMOUNT between 1 and 5
union all
select 20 ,count(*) from table1 where PURCHASE_AMOUNT between 6 and 20
union all
select 30 ,count(*) from table1 where PURCHASE_AMOUNT between 21 and 30
)t
DEMO HERE
There are faster ways to do this if you need the performance (this will do a full table scan), but try this:
SELECT
SUM(CASE WHEN purchase_amount BETWEEN 0 AND 1 THEN 1 ELSE 0) bucket_0_to_1,
SUM(CASE WHEN purchase_amount BETWEEN 1 AND 5 THEN 1 ELSE 0) bucket_1_to_5,
SUM(CASE WHEN purchase_amount BETWEEN 5 AND 20 THEN 1 ELSE 0) bucket_5_to_20,
SUM(CASE WHEN purchase_amount BETWEEN 20 AND 30 THEN 1 ELSE 0) bucket_20_to_30,
SUM(CASE WHEN purchase_amount > 30 THEN 1 ELSE 0) bucket_over_30, FROM my_table LIMIT 1;
To get the values you want in rows, you need to start with a driver table that has all the values you are interested in, and then left outer join to the data:
select driver.mina, coalesce(sum(cnt), 0) as Num_Of_Users
from (select 1 as mina, 5 as maxa union all
select 5, 10 union all
select 10, 20 union all
select 20, 30 union all
select 30, NULL
) driver left outer join
(select purchase_amount, count(*) as cnt
from purchases
group by purchase_amount
) pa
on driver.mina >= pa.purchase_amount and
(pa.purchase_amount < driver.maxa or driver.maxa is null)
group by driver.mina
order by driver.mina
You can actually do this without the inner group by. That is likely to reduce the size of the data significantly (especially in your example) before join.
I would encourage you to include both the lower and upper bounds of the range on each row.
This might be easier if the ranges will ever change.
with ranges(rstart, rfinish) as (
select 0, 1 union all
select 2, 5 union all
select 6, 20 union all
select 21, 30
), purchases(amount) as (
select sum(PURCHASE_AMOUNT)
from <purchases_basetable> -- <-- your tablename goes here
group by USERID
)
select
-- concat(case when r.rstart = 0 then 0 else r.rstart-1 end, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* op's name for the group */,
concat(r.rstart, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* better name for the group */,
count(*) as NUM_OF_USERS
from
purchases as p inner join
ranges as r
on p.amount between r.start and r.finish
group by r.rstart, r.rfinish
order by r.rstart, r.rfinish
I don't know what the mysql query plan will look like. It's trivial to change the query to use derived tables rather than table expressions. (But I include it below anyway.)
You might also find the UNPIVOT operation to be useful on a platform that supports it.
select
-- concat(case when r.rstart = 0 then 0 else r.rstart-1 end, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* op's name for the group */,
concat(r.rstart, '-', r.rfinish) as SUM_OF_PURCHASES_RANGE /* better name for the group */,
count(*) as NUM_OF_USERS
from
(
select sum(PURCHASE_AMOUNT) as amount
from <purchases_basetable> -- <-- your tablename goes here
group by USERID
) as p inner join
(
select 0 as rstart, 1 as rfinish union all
select 2, 5 union all
select 6, 20 union all
select 21, 30
) as r
on p.amount between r.start and r.finish
group by r.rstart, r.rfinish
order by r.rstart, r.rfinish
Please consider my table (this is just a simplified version, in my project, I got 600,000+ records):
Id TransactionId TransactionTypeId Description
1 1 1 Description1
2 1 1 Description2
3 1 2 Description3
4 1 2 Description4
5 1 1 Description5
6 1 2 Description6
7 2 1 Description7
8 2 1 Description8
9 2 2 Description9
10 2 2 Description10
What I need to do is to when TransactionTypeId = 1, I need to get the latest data from that table. Otherwise, when TransactionTypeId <> 1, I need to get them all.
For this instance, I have this query:
SELECT MAX(T.Id)
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionId ORDER BY T.Id DESC), ',', 1) AS TransactionId
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionTypeId ORDER BY T.Id DESC), ',', 1) AS TransactionTypeId
, SUBSTRING_INDEX(GROUP_CONCAT(T.Description ORDER BY T.Id DESC), ',', 1) AS Description
FROM Transactions T
GROUP BY T.TransactionId
, CASE WHEN T.TransactionTypeId = 1 THEN T.TransactionTypeId END
ORDER BY T.TransactionId, T.TransactionTypeId
And my desired result would be:
Id TransactionId TransactionTypeId Description
5 1 1 Description5
3 1 2 Description3
4 1 2 Description4
6 1 2 Description6
8 2 1 Description8
9 2 2 Description9
10 2 2 Description10
But my problem is, even when TransactionTypeId <> 1, the query still groups them.
My query returns:
Id TransactionId TransactionTypeId Description
5 1 1 Description5
6 1 2 Description6
8 2 1 Description8
10 2 2 Description10
I know I can use UNION here, to seperate queries for the TransactionTypeId column, but I can't, it took me more than 5 minutes to get the results. Is there any possible (if there's none, I've got no choice but to use UNION) way to solve this?
Thanks :)
UPDATE #1
Here's my query when I'm using UNION.
SELECT Transaction.Id
, Transaction.TransactionId
, Transaction.TransactionTypeId
, Transaction.Description
FROM (
SELECT MAX(T.Id)
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionId ORDER BY T.Id DESC), ',', 1) AS TransactionId
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionTypeId ORDER BY T.Id DESC), ',', 1) AS TransactionTypeId
, SUBSTRING_INDEX(GROUP_CONCAT(T.Description ORDER BY T.Id DESC), ',', 1) AS Description
FROM Transactions T
WHERE T.TransactionTypeId = 1
GROUP BY T.TransactionId
UNION
SELECT T.Id
, T.TransactionId
, T.TransactionTypeId
, T.Description
FROM Transactions T
WHERE T.TransactionTypeId <> 1
) Transaction
ORDER BY Transaction.TransactionId, Transaction.TransactionTypeId
TRY WITH THIS
SELECT MAX(T.Id)
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionId ORDER BY T.Id DESC), ',', 1) AS TransactionId
, SUBSTRING_INDEX(GROUP_CONCAT(T.TransactionTypeId ORDER BY T.Id DESC), ',', 1) AS TransactionTypeId
, SUBSTRING_INDEX(GROUP_CONCAT(T.Description ORDER BY T.Id DESC), ',', 1) AS Description
FROM Transactions T
GROUP BY CASE WHEN T.TransactionTypeId = 1 THEN CONCAT(T.TransactionId,'-',T.TransactionTypeId) ELSE T.Id END
ORDER BY T.TransactionId, T.TransactionTypeId
Try this one. The only slowing factor I see is the final sorting - so you may try it first without the ORDER BY:
SELECT T.Id
, T.TransactionId
, 1 AS TransactionTypeId
, T.Description
FROM Transactions T
JOIN
( SELECT MAX(Id) AS Id
FROM Transactions
WHERE TransactionTypeId = 1
GROUP BY TransactionId
) AS grp
ON grp.Id = T.Id
UNION ALL
SELECT T.Id
, T.TransactionId
, T.TransactionTypeId
, T.Description
FROM Transactions T
WHERE T.TransactionTypeId <> 1
ORDER BY TransactionId, TransactionTypeId
You can also use this (which is non UNION but has an OR so I can't be sure about performance) query:
SELECT T.Id
, T.TransactionId
, T.TransactionTypeId
, T.Description
FROM Transactions T
WHERE T.TransactionTypeId <> 1
OR NOT EXISTS
( SELECT *
FROM Transactions tm
WHERE tm.TransactionTypeId = 1
AND tm.TransactionId = T.TransactionId
AND tm.Id > T.Id
)
ORDER BY T.TransactionId, T.TransactionTypeId