MySQL UPDATE ranking query works locally, not on server - mysql

I have a sports pools database with tables:
pools (id, name, sport, type, season, startRound, endRound)
entries (poolId, userId, entryId, poolPoints, poolRank)
picks (poolId, userId, entryId, round, pick, pickResult, pickPoints)
I can query and sum the pickPoints into poolPoints by pool/user/entry. I was then using the query shown below to update the poolRank based on poolPoints highest to lowest. I am 95% certain it worked both locally and on my server, previously. Now it does not work on the server and I can't figure out why.
Queries:
-- get current round and set as variable
SELECT round FROM hockey_games WHERE gameDate <= CURDATE()
ORDER BY gameDate DESC LIMIT 1 INTO #round;
-- rank pick'em players (overall)
UPDATE mop_survivor_entries b
INNER JOIN (
SELECT poolId, userId, entryId, poolPoints, rank FROM (
SELECT poolId, userId, entryId, poolPoints,
#curRank := IF(#prevPool != poolId, 1, IF(#prevRank = poolPoints, #curRank, #incRank)) AS rank,
#incRank := IF(#prevPool != poolId, 2, #incRank + 1),
#prevRank := poolPoints,
#prevPool := poolId
FROM mop_pools c, mop_survivor_entries e,
(SELECT #curRank :=0, #prevRank := NULL, #prevPool := 604, #incRank := 1) r
WHERE c.id = e.poolId
AND sport = 'hockey'
AND type = 'pickem'
AND season = 2017
AND startRound <= #round
AND endRound >= #round
ORDER BY poolId ASC, poolPoints DESC
) s
) a ON b.poolId = a.poolId AND b.userId = a.userId AND b.entryId = a.entryId
SET poolRank = rank;
I thought it might have something to do with my user-defined variables resetting but research isn't turning up much in the way of answers.
I tried running the queries through PHP and with phpMyAdmin with same (no change) results.
There is a slight difference in MySQL versions. Local 5.6.3; server 5.5.58 ... but again, feel it did work previously. Appreciate any help someone can give.

Related

Ranking positions using SQL

Trying to rank positions but instead of giving (1,2,3,3,5..) when there is a tie in the 3rd position it gives (1,2,3,4,5...).. Help please
below is the code.. thanks
.............................................................................
SELECT t.*
FROM (SELECT #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition,student_id, #prev:=#cur, #cur:=SUM(total_marks)
FROM (SELECT m.*
FROM marks m
WHERE classform_name = ? AND term = ? AND academic_year = ? GROUP BY student_id
ORDER BY SUM(total_marks) DESC
) n CROSS JOIN
(SELECT #curRank := 0, #prev:=NULL, #cur:=NULL ) q GROUP BY student_id
) t
WHERE student_id = ?
.............................................................................
You don't need a RANK function. You can always rank rows by joining the table to itself, in which case you get to choose how the order (and rank) is determined.
I've mixing session/# variables and aggregation can be a bit unreliable, but you could simply try changing the order of your select expressions to
student_id, #cur:=SUM(total_marks), #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition, #prev:=#cur
If this doesn't help, I'd suggest separating your aggregation and ranking. Actually, you're already summing in the subquery, so I am not quite sure why you're not just including the sum in the results from n.
SELECT * FROM (
SELECT #curRank := IF(#prev= #cur, #curRank, #curRank + 1 ) AS classPosition, student_id, #prev:=#cur, #cur:=overall
FROM (SELECT m.*, SUM(total_marks) AS overall
FROM marks m
WHERE classform_name = ? AND term = ? AND academic_year = ?
GROUP BY student_id
ORDER BY overall DESC
) AS n
CROSS JOIN (SELECT #curRank := 0, #prev:=NULL, #cur:=NULL ) AS q
) AS completeRankings
WHERE student_id = ?
Actually, your original query should've had other issues anyway. n would have only included a single random total_marks value for each student_id; making the outer SUM kind of pointless.
Edit - This should allow position progression to "skip" later positions according to ties:
SELECT #curRank := #curRank + 1 AS counter, #prevRank := IF(#prev=#cur, #prevRank, #curRank) AS classPosition
... to omit counter from the final results, you'll have to expand out the * explicitly.
Ranks are a real pain in MySQL.
If you want the rank for a single student with these conditions, I would recommend:
SELECT 1 + COUNT(*)
FROM marks m
WHERE m.classform_name = ? AND m.term = ? AND m.academic_year = ? AND
m.total_marks >= (SELECT m2.total_marks
FROM marks m2
WHERE m2.classform_name = m.classform_name AND
m2.term = m.term
m2.academic_year = m.academic_year
m2.student_id = ?
);;

mysql / sql: how to delete all rows except the Nth last per user?

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 )

Top 20 percent by id - MySQL

I am using a modified version of a query similiar to another question here:Convert SQL Server query to MySQL
Select *
from
(
SELECT tbl.*, #counter := #counter +1 counter
FROM (select #counter:=0) initvar, tbl
Where client_id = 55
ORDER BY ordcolumn
) X
where counter >= (80/100 * #counter);
ORDER BY ordcolumn
tbl.* contains the field 'client_id' and I am attempting to get the top 20% of the records for each client_id in a single statement. Right now if I feed it a single client_id in the where statement it gives me the correct results, however if I feed it multiple client_id's it simply takes the top 20% of the combined recordset instead of doing each client_id individually.
I'm aware of how to do this in most databases, but the logic in MySQL is eluding me. I get the feeling it involves some ranking and partitioning.
Sample data is pretty straight forward.
Client_id rate
1 1
1 2
1 3
(etc to rate = 100)
2 1
2 2
2 3
(etc to rate = 100)
Actual values aren't that clean, but it works.
As an added bonus...there is also a date field associated to these records and 1 to 100 exists for this client for multiple dates. I need to grab the top 20% of records for each client_id, year(date),month(date)
You need to do the enumeration for each client:
SELECT *
FROM (SELECT tbl.*, #counter := #counter +1 counter
(#rn := if(#c = client_id, #rn + 1,
if(#c := client_id, 1, 1)
)
)
FROM (select #c := -1, #rn := 0) initvar CROSS JOIN tbl
ORDER BY client_id, ordcolumn
) t cross join
(SELECT client_id, COUNT(*) as cnt
FROM tbl
GROUP BY client_id
) tt
where rn >= (80/100 * tt.cnt);
ORDER BY ordcolumn;
Using Gordon's answer as a starting point, I think this might be closer to what you need.
SELECT t.*
, (#counter := #counter+1) AS overallRow
, (#clientRow := if(#prevClient = t.client_id, #clientRow + 1,
if(#prevClient := t.client_id, 1, 1) -- This just updates #prevClient without creating an extra field, though it makes it a little harder to read
)
) AS clientRow
-- Alteratively (for everything done in clientRow)
, #clientRow := if(#prevClient = t.client_id, #clientRow + 1, 1) AS clientRow
, #prevClient := t.client_id AS extraField
-- This may be more reliable as well; I not sure if the order
-- of evaluation of IF(,,) is reliable enough to guarantee
-- no side effects in the non-"alternatively" clientRow calculation.
FROM tbl AS t
INNER JOIN (
SELECT client_id, COUNT(*) AS c
FROM tbl
GROUP BY client_id
) AS cc ON tbl.client_id = cc.client_id
INNER JOIN (select #prevClient := -1, #clientRow := 0) AS initvar ON 1 = 1
WHERE t.client_id = 55
HAVING clientRow * 5 < cc.c -- You can use a HAVING without a GROUP BY in MySQL
-- (note that clientRow is derived, so you cannot use it in the `WHERE`)
ORDER BY t.client_id, t.ordcolumn
;

MariaDB/MySQL RANK() implementation

I am working on migration from MS SQL Server to MariaDB 10.0. I have query which use RANK() PARTITION BY. I already created some kind of RANK() implementation on my table but it's not working properly.
The original query was:
RANK() OVER (PARTITION BY visits.id_partner ORDER BY visits.updated_at DESC) AS rank
My implementation for MariaDB/MySQL:
SELECT
...,
(
CASE visits.id_partner
WHEN #currId THEN
#curRow := #curRow + 1
ELSE
#curRow := 1 AND #currId := visits.id_partner
END
) AS rank
FROM
records rec
JOIN visits ON visits.id = rec.id_visit,
(
SELECT
#curRank := 0,
#currId := NULL
) r
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
I want to select row ranked by id_partner order by updated_at field. When id_partner is same as on row before RANK should increase by 1. When is different than before, it should reset to 1.
But my query is not working at all. I have still rank 1 on all rows. Can you help me find mistake?
Thank you for help!
It is tricky to use variables in MySQL/MariaDB. A variable should only be used and assigned in one statement (as you do correctly). However, AND can short-circuit variable assignment.
I use a construct like this for ROW_NUMBER(). RANK() is actually a bit of a pain . . . DENSE_RANK() and ROW_NUMBER() are simpler. However, this seems to be the code that you are aiming for:
SELECT ...,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM records rec JOIN
visits
ON visits.id = rec.id_visit CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC;
EDIT:
In MySQL (and presumably in MariaDB), sometimes variables don't work quite right unless you use a subquery. So, try this:
SELECT . . .,
(#rn := if(#currId = visits.id_partner, #rn + 1,
if(#currId := visits.id_partner, 1, 1)
)
) as rank
FROM (SELECT ...
FROM records rec JOIN
visits
ON visits.id = rec.id_visit
WHERE
...
ORDER BY visits.id_partner ASC, visits.updated_at DESC
) t CROSS JOIN
(SELECT #rn := 0, #currId := NULL) params;

Finding the most rented car of each year in SQL

I have tables as follows:
Car(id, make, ....)
Deal(id,datetime,car_id,....)
I want to write a query that would return a year, and a car make for the cars that have the most deals (ie the most deal ids) and the number of deals for that car make.
I started out,
SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num
FROM Deal D, Car C
WHERE D.car_id=C.id
GROUP BY the_year
Unfortunately, this has returned the year and the total number of deals.
So I am thinking to create this within another table and then call MAX(tbl.num), but I am confused on the syntax.
Can somebody help me out please?
This is an interesting problem. What you are looking for is specifically called the "mode" in statistics. In MySQL, you would get this by using variables or the group_conat()/substring_index()` trick. I'll show the latter:
SELECT the_year,
substring_index(group_concat(cd.make order by num desc), ',', 1) as the_mark
FROM (SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num
FROM Deal D JOIN
Car C
ON D.car_id = C.id
GROUP BY the_year, c.make
) cd
GROUP BY the_year;
EDIT:
The version using variables:
SELECT the_year,
substring_index(group_concat(cd.make order by num desc), ',', 1) as the_mark
FROM (SELECT YEAR(D.datetime) AS the_year, C.make, COUNT(D.id) AS num,
#rn := if(#year = YEAR(D.datetime), #rn + 1, 1) as rn,
#year := YEAR(D.datetime)
FROM Deal D JOIN
Car C
ON D.car_id = C.id CROSS JOIN
(SELECT #year := 0, #rn := 0) vars
GROUP BY the_year, c.make
ORDER BY the_year, num DESC
) cd
WHERE rn = 1;