I have a query that loops through each result and updates a column:
SET #counter = 0;
UPDATE users SET rank_level = #counter := #counter + 1 ORDER BY level DESC;
SELECT rank_level, level FROM users ORDER BY rank_level ASC;
Which outputs:
But what I am trying to do is only increment the variable if the level value changes. So where the two rows that have the same level are, they would have the same rank of 8 too.
Any ideas? Thanks.
rank() is a bit tricky in MySQL. One way is with a correlated subquery:
select u.*,
(select count(*) + 1
from users u2
where u2.level < u.level
) as rank
from users u;
This is tricky to put into an update. Assuming you have a userId column, you can use join:
update users u join
(select u.*,
(select count(*) + 1
from users u2
where u2.level < u.level
) as rank
from users u
) ur
on u.userId = ur.userId
set rank_level = rank;
Doing a rank with variables is rather tricky (row_number() and dense_rank() are easier), because it requires three variables. Here is the select version:
select u.*,
(#r := if(#l = level,
if(#rn := #rn + 1, #r, #r)
if(#l := level,
#rn := #rn + 1, #rn := #rn + 1
)
)
) as ranking
from users u cross join
(select #l := -1, #rn : = 0, #r := 0) params
order by level;
So in the end I went with this:
SET #prev_value = NULL;
SET #rank_count = 0;
UPDATE users SET rank_level = CASE
WHEN #prev_value = level THEN #rank_count
WHEN #prev_value := level THEN #rank_count := #rank_count + 1
END
ORDER BY level DESC;
Based on this answer: Rank function in MySQL
Related
I have a message (id, userid, message) table that grows rapidly.
I would like to delete all messages per user except his last 30
ex:
if user1 has 100 messages, we will delete the first 70,
if user2 has 40 messages, we will delete the first 10,
if userN has 10 messages, no action is taken
Is there a way to do it with a single SQL ?
My idea for now is to make a LOOP with PHP and lake N sql, which is very long for N users.
MySQL (pre 8.0) doesn't have a really convenient way to do this. One method uses variables to enumerate the values:
select m.*,
(#rn := if(#u = userid, #rn + 1,
if(#u := userid, 1, 1)
)
) as seqnum
from (select m.*
from messages m
order by userid, id desc
) m cross join
(select #u := -1, #rn := 0) params;
You can turn this into a delete using join:
delete m
from messages m join
(select m.*,
(#rn := if(#u = userid, #rn + 1,
if(#u := userid, 1, 1)
)
) as seqnum
from (select m.*
from messages m
order by userid, id desc
) m cross join
(select #u := -1, #rn := 0) params
) mm
on m.id = mm.id
where seqnum > 30;
As I say in a comment, I don't think this is a good solution for a real-world problem. The history of messages is useful and there are probably other ways to achieve the performance you want. The difference between 30 messages for a user and 70 messages for a user should not have that much of an effect on performance, in a tuned system.
SET #row_number = 0;
DELETE FROM MESSAGE
WHERE ID IN
( SELECT ID FROM
(SELECT ID,
#row_number:=CASE
WHEN #userid = userid THEN
#row_number + 1
ELSE 1
END AS num,
#userid:=userid as userid
FROM MESSAGE) A
WHERE NUM > 70 )
I'm working on my website and I noticed all the users have ID's like 1.337.134.060
I want to recount this so it will start from UserID 1 again.
How can I do this without harming the existing user accounts?
If you want to have an additional column as row count then,
select tbl.*,
#r := #r + 1 as new_userID
from tbl,
(select #r := 0) r
If you want to update the user id like 1, 2 , 3 etc then,
update tbl t
join (
select tbl.id,
#r := #r + 1 as new_userID
from tbl,
(select #r := 0) r
) x
on x.id = t.id
set t.id = x.new_userID
Assuming User id is all unique.
I am trying to get the rank of specified record, and I have some success with this code:
SELECT `rank`
FROM
(
select #rownum:=#rownum+1 `rank`, p.*
from TableName p, (SELECT #rownum:=0) r
order by point DESC
) s
WHERE names = 'house'
(See the schema here.)
This SQL query works, but if I want to get the result according to city_id and name, I must use two where clauses, and then the code doesn't work.
I want to get rank of house only for its city. How can I do this?
I want to get rank of "house" only for its city
You can do this by introducing another variable to keep track of the city:
SELECT `rank`
FROM (select p.*,
(#rn := if(#c = city, #rn + 1,
if(#c := city, 1, 1)
)
) as rank
from TableName p CROSS JOIN
(SELECT #rn := 0, #c := -1) params
order by city_id, point DESC
) s
WHERE names = 'house' ;
You can also use your original query, with a tweak, if you know that "house" only appears once in the data:
SELECT `rank`
FROM (select p.*, (#rn := #rn + 1) as rank
from TableName p CROSS JOIN
(SELECT #rn := 0) params
where city_id = (select city_id from TableName t2 where t2.names = 'house')
order by point DESC
) s
WHERE names = 'house' ;
I have a 1 table database with (in a simplified form) the following fields:
user_id(INT), ref_id(INT), points(INT), pointsgiven(BOOL/TINY_INT)
I want a query that returns the RANK of the user_id I specify based on points, given that pointsgiven is true. The kicker is, I need ties included. I can get a result set for ALL ranks if I want with the following query
SELECT
user_id, ref_id, points, pointsgiven,
CASE
WHEN #points = COALESCE(points, 0) THEN #rownum
ELSE #rownum := #rownum + 1
END AS rank,
#points := COALESCE(points, 0)
FROM users CT
JOIN
(
SELECT #rownum := 0, #points := NULL
) r
WHERE pointsgiven=TRUE ORDER BY points DESC
So, based on that, I thought I could just use that as a subquery to get a certain user_id as follows:
select * from
(
SELECT
user_id, ref_id, points, pointsgiven,
CASE
WHEN #points = COALESCE(points, 0) THEN #rownum
ELSE #rownum := #rownum + 1
END AS rank,
#points := COALESCE(points, 0)
FROM users CT
JOIN
(
SELECT #rownum := 0, #points := NULL
) r
WHERE pointsgiven=TRUE ORDER BY points DESC
) as derived WHERE user_id = 15
But this returns [BLOB - 1 B] as the rank on the correct user_id. What am I doing wrong here?
I have no idea why your query isn't working. For a single user id, though, you can use a correlated subquery:
select user_id, ref_id, points, pointsgiven,
coalesce((select count(distinct user_id)
from users u2
where u2.pointsgiven=TRUE and
u2.points > u.points
) + 1, 1) as rank
from users u
where user_id = 15;
An index on users(pointsgiven, points, user_id) should be used by the query.
To look at just one ranking, this might even be faster than your method.
I'm working on fixing our (forum) database as it seems that several posts in certain threads are showing up with the same post number (#2). It seems that they all hold a position of "1" within each respective forum thread.
I've managed to find a query that will set these position values to the proper number via the query below:
select #i := -1; update `xf_post` set position = (select #i := #i + 1) where thread_id=1;
Unfortunately, I've only been doing this update query through the MySQL command line where I'm constantly selecting the Up Arrow key, incrementing the 'thread_id' value by 1, and hitting the Return key. Is there a faster way to do this through a loop or cursor? I'm not very familiar with the other parts of the SQL syntax and I only get by with the basics.
You can use the following query to get the numbers that you want for each thread:
select thread_id,
(case when #prev = thread_id then #rn := 1 else #rn := #rn + 1 end) as rn,
#prev = #threadid
from xf_post p cross join
(select #rn := 0, #thread_id = -1) const
order by thread_id, position
You can test this out to be sure that it produces the right values. Next you can do an update with a join:
update xf_post join
(select thread_id, post_id,
(case when #prev = thread_id then #rn := 1 else #rn := #rn + 1 end) as rn,
#prev = #threadid
from xf_post p cross join
(select #rn := 0, #thread_id = -1) const
order by thread_id, post_id
) toupdate
on xf_post.post_id = toupdate.post_id
set xf_post.position = toupdate.rn;