I have a table for my users scores like this:
id | kills
----------
2 | 1
1 | 1
1 | 5
1 | 3
2 | 4
2 | 5
3 | 5
I want to get the first 2 rows of each player which have more than 2 kills. So the result should look like this
id | kills
----------
1 | 5
1 | 3
2 | 4
2 | 5
3 | 5
I tried this but it doesn't work:
SELECT *
FROM user_stats us
WHERE
(
SELECT COUNT(*)
FROM user_stats f
WHERE f.id=us.id AND f.kills > 2
) <= 2;
I suspect that you just want the two largest values for users that have kills > 2. If so, use variables:
select us.*
from (select us.*,
(#rn := if(#i = id, #rn + 1,
if(#i := id, 1, 1)
)
) as seqnum
from user_stats us cross join
(select #rn := 0, #i := -1) params
where us.kills > 2
order by us.id, kills desc
) us
where seqnum <= 2;
select * from user_stats
where (id,kills) in (select id, max(kills) from user_stats where kills > 2 group by id
union
select id, min(kills) from user_stats where kills > 2 group by id)
Try this. I am coming from Oracle, where rownum is a count of rows selected. This should have the same effect.
select #rownum:=#rownum+1, us.*
from user_stats us , (select #rownum := 0) r
where id in (
select id from user_stats f
group by id
having count(*) > 2
)
and #rownum < 3;
based on response of vkp. Take min and max when id has more then 1 kill
select id, max(kills)
from user_stats
group by id
having count(kills) > 2
union
select id, min(kills)
from user_stats
group by id
having count(kills) > 2
order by id
Related
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.
I have a value store in a database like this:
ID | Date | Value
----------------------------------------------
1 | 11/20 | 1
1 | 11/21 | 2
2 | 11/20 | 10
2 | 11/21 | 20
However, I need it to be like this:
Date | Value ID 1 | Value ID 2
----------------------------------------------
11/20| 1 | 10
11/21| 2 | 20
So the new column can be plot in a trend (column 1 = date, column 2 = value#1, column 3 = value #2, column 4 = value#4, etc).
Here is the query for a single tag:
SELECT *
FROM (
SELECT ID, _date, ESYNC_TAGSHISTORY.Val, #curRow := #curRow + 1 AS row_number
FROM ESYNC_TAGSHISTORY
JOIN (SELECT #curRow:=0) i
INNER JOIN ESYNC_TAGS ON ESYNC_TAGSHISTORY.TAGID=ESYNC_TAGS.ID
WHERE ESYNC_TAGS.NAME='I_TT_21052' AND ESYNC_TAGS.STATIONID=1 AND (_date BETWEEN now()-INTERVAL 45 MINUTE AND now()) ) s
WHERE row_number mod 60 = 0;
And the results:
ID | Date | Value ID 1 | Row
----------------------------------------------
1 | 11/20| 1 | 1
1 | 11/21| 2 | 2
EDIT :
With some modification my query look like this
SELECT *
FROM (
SELECT ID, _date, ESYNC_TAGSHISTORY.Val, #curRow := #curRow + 1 AS row_number,
if (ESYNC_TAGS.NAME='I_TT_21052', ESYNC_TAGSHISTORY.Val, NULL) as 'I_TT_21052',
if (ESYNC_TAGS.NAME='I_TT_91214', ESYNC_TAGSHISTORY.Val, NULL) as 'I_TT_40011'
FROM ESYNC_TAGSHISTORY
JOIN (SELECT #curRow:=0) i
INNER JOIN ESYNC_TAGS ON ESYNC_TAGSHISTORY.TAGID=ESYNC_TAGS.ID
WHERE ESYNC_TAGS.STATIONID=1 AND (_date BETWEEN now()-INTERVAL 5 MINUTE AND now()) ) s
WHERE row_number mod 1 = 0
ORDER BY ID ,_date;
Result look like this
SQL RESULT
My Problem now is to move the data from the last column at the same place as the other (get the value line up with the date)
EDIT #2 : Finally for further reference query look like this :
SELECT _date, I_TT_21052, I_TT_40011, row_number
From(
SELECT max(_date) as _date, max(I_TT_21052) as I_TT_21052, max(I_TT_40011) as I_TT_40011, #curRow := #curRow + 1 AS row_number
FROM (
SELECT ID, _date, ESYNC_TAGSHISTORY.Val,
if (ESYNC_TAGS.NAME='I_TT_21052', ESYNC_TAGSHISTORY.Val, NULL) as 'I_TT_21052',
if (ESYNC_TAGS.NAME='I_TT_91214', ESYNC_TAGSHISTORY.Val, NULL) as 'I_TT_40011'
FROM ESYNC_TAGSHISTORY
JOIN (SELECT #curRow:=0) i
INNER JOIN ESYNC_TAGS ON ESYNC_TAGSHISTORY.TAGID=ESYNC_TAGS.ID
WHERE ESYNC_TAGS.STATIONID=1 AND (_date BETWEEN now()-INTERVAL 24 HOUR AND now()) ) s
GROUP BY _date)v
WHERE row_number mod 150 = 0;
select
case when id=1 then count(Id) else 0 end) as Value1,
case when id=2 then count(Id) else 0 end) as Value2
from ESYNC_TAGSHISTORY
This is not exact but try this kind of query you will get result
I'm always "irk" by unnecessary join. But in this case, I wonder if it's possible to not use join.
This is an example of the table I have:
id | team | score
1 | 1 | 300
2 | 1 | 257
3 | 2 | 127
4 | 2 | 533
5 | 3 | 459
This is what I want:
team | score | id
1 | 300 | 1
2 | 533 | 4
3 | 459 | 5
Doing a query looking like this:
(basically: who's the best player of each team)
SELECT team, MAX(score) AS score, id
FROM my_table
GROUP BY team
But I get something like that:
team | score | id
1 | 300 | 1
2 | 533 | 3
3 | 459 | 5
But it's not the third player that got 533 points, so the result have no consistency.
Is it possible to get truthworthy results without joining the table with itself? How to achieve that?
You can do it without joins by using subquery like this:
SELECT id, team, score
FROM table1 a
WHERE score = (SELECT MAX(score) FROM table1 b WHERE a.team = b.team);
However in big tables this can be very slow as you have to run the whole subquery for every row in your table.
However there's nothing wrong with using join to filter results like this:
SELECT id, team, score FROM table1 a
INNER JOIN (
SELECT MAX(score) score, team
FROM table1
GROUP BY team
) b ON a.score = b.score AND a.team = b.team
Although joining itself is quite expensive, this way you only have to run two actual queries regardless how many rows are in your tables. So in big tables this method can still be hundreds, if not thousands of times faster than the first method with subquery.
You can use variables:
SELECT id, team, score
FROM (
SELECT id, team, score,
#seq := IF(#t = team, #seq,
IF(#t := team, #seq + 1, #seq + 1)) AS seq,
#grp := IF(#t2 = team, #grp + 1,
IF(#t2 := team, 1, 1)) AS grp
FROM mytable
CROSS JOIN (SELECT #seq := 0, #t := 0, #grp := 0, #t2 := 0) AS vars
ORDER BY score DESC) AS t
WHERE seq <= 3 AND grp = 1
Variable #seq is incremented each time a new team is met as the records are being processed in descending score order. Variable #grp is used to enumerate records within each team partition. Records with #grp = 1 are the ones having the greatest score value within the team slice.
Demo here
Unfortantly , MySQL doesn't support window functions like ROW_NUMBER() which could have solved this easily.
There are several ways on doing that:
NOT EXISTS() :
SELECT * FROM YourTable t
WHERE NOT EXISTS(SELECT 1 FROM YourTable s
WHERE t.team = s.team AND s.score > t.score)
NOT IN() :
SELECT * FROM YourTable t
WHERE (t.team,t.score) IN(SELECT s.team,MAX(s.score)
FROM YourTable s
GROUP BY s.team)
A correlated query:
SELECT distinct t.id,t.team,
(SELECT s.score FROM YourTable s
WHERE s.team = t.team
ORDER BY s.score DESC
LIMIT 1)
FROM YourTable t
Or a join which I understand you already have.
EDIT : I take my words back, you can do it with a variable like #GiorgosBetsos solution.
You could do something like this:
SELECT team, score, id
FROM (SELECT *
,RANK() OVER
(PARTITION BY team ORDER BY score DESC) AS Rank
FROM my_table) ranked_result
WHERE Rank = 1;
Some info on Rank functionality: Clicketyclickclick
I have 2 different tables in my database by the name of: rank, settings.
Here is how each table looks like with a few records in them:
Table #rank:
id points userid
-- ----- ------
1 500 1
2 300 2
3 900 3
4 1500 4
5 100 5
6 700 6
7 230 7
8 350 8
9 850 9
10 150 10
Table #settings:
userid active
------ ------
1 0
2 1
3 1
4 1
5 1
6 0
7 1
8 1
9 0
10 1
What I basically want to achieve is to select a specific row from #rank by ID, sort it by points and select 3 rows above the specific ID and 3 row below the specific ID but only for rows where the active column (from #settings) for the user equals 1.
For example:
I would like to select from #rank the ID of 8, and it should return me the following:
rank points userid
---- ----- ------
2 150 10
3 230 7
4 300 2
5 350 8
6 900 3
7 1500 4
I have created quite an extensive query for this, but the problem is, that it is ranking the columns before it decides that the user is active or not. However I need to rank the columns after it is decided that the user is active or not.
SELECT sub2.sort, sub2.points, sub2.userid
FROM
(
SELECT #sort1 := #sort1 + 1 AS sort, puu.points, puu.userid
FROM rank as puu,
(SELECT #sort1 := 0) s
LEFT JOIN
(
settings as p11
)
ON puu.userid = p11.userid,
WHERE p11.active = 1
ORDER BY puu.points DESC
) sub1
INNER JOIN
(
SELECT #sort2:=#sort2+1 AS sort, p2.points, p2.userid
FROM rank as p2,
(SELECT #sort2 := 0) s
LEFT JOIN
(
settings as p12
)
ON p2.userid = p12.userid,
WHERE p12.active = 1
ORDER BY points DESC
) sub2
ON sub1.userid = :userid
AND sub2.sort BETWEEN (sub1.sort - 5) AND (sub1.sort + 5)
Can you guys find any solution for my problem? If you can provide an SQLfiddle demo, that would be really awesome!
SELECT sort, points, user_id, active FROM (
SELECT #pos := #pos + 1 AS sort, id, points, r.user_id, s.active,
IF(user_id = :userid, #userpos := #pos, 0)
FROM rank r
JOIN settings s USING(user_id)
JOIN (SELECT #pos := 0, #userpos := 0) p
WHERE s.active = 1
ORDER BY points DESC
) list
WHERE sort BETWEEN #userpos - 3 AND #userpos + 3
I made a fiddle here: sqlfiddle
Possibly using 2 unioned queries to get the values before the ranking:-
SELECT #rank:=#rank+1 AS rank, points, userid
FROM
(
SELECT id, points, userid
FROM
(
SELECT rank.id, rank.points, rank.userid
FROM rank
INNER JOIN
(
SELECT points
FROM rank
WHERE id = 8
) sub0
ON rank.points >= sub0.points
INNER JOIN settings
ON rank.userid = settings.userid
WHERE settings.active = 1
ORDER BY rank.points LIMIT 3
) sub1
UNION ALL
SELECT id, points, userid
FROM
(
SELECT rank.id, rank.points, rank.userid
FROM rank
INNER JOIN
(
SELECT points
FROM rank
WHERE id = 8
) sub0
ON rank.points < sub0.points
INNER JOIN settings
ON rank.userid = settings.userid
WHERE settings.active = 1
ORDER BY rank.points DESC LIMIT 3
) sub1
) sub2
CROSS JOIN (SELECT #rank:=0) sub3
ORDER BY points
I've got a table of data thats ordered by a non-primary key e.g.
id | likes
4 | 6
2 | 5
5 | 2
3 | 2
1 | 2
I need a query to find the row after id #5 which would be id #3.
I've tried using row numbers and written this but it seems really inefficient
select * from (
SELECT l.id,
l.likes,
#curRow := #curRow + 1 AS row_number
FROM sk_posters l
JOIN (SELECT #curRow := 0) r
WHERE active = 'yes'
order by likes desc, id desc)
as mycount where row_number =
(select row_number from (
SELECT l.id,
l.likes,
#curRow := #curRow + 1 AS row_number
FROM sk_posters l
JOIN (SELECT #curRow := 0) r
WHERE active = 'yes'
order by likes desc, id desc)
as mycount
where id=5)+1 limit 1
If there a better, more efficient way to do this?
You can use limit at the end of query like
SELECT * FROM YOUR_TABLE LIMIT 2,1