Resetting a variable within subquery - mysql

If I can increment a variable within a query to append a rank column as such:
SET #i = 0;
SELECT *, #i:=#i+1 AS rank FROM table WHERE column="value" ORDER BY time;
How can I perform this as a sub-query for all unique column values? I believe the real issue is resetting the variable.

If you want to an incremental number for each column (in the fashion of row_number() in most other databases), you can still use variables:
SELECT t.*,
(#i := if(#c = column, #i + 1,
if(#c := column, 1, 1)
)
) as rank
FROM table t CROSS JOIN
(SELECT #i := 0, #c := '') params
ORDER BY column, time;

Related

A view with internal variables

I have a query where I want to fetch the top x for each value of another column. For doing this I have the following query
SELECT *,
(#num := IF(#job = job_id, #num + 1, IF(#job := job_id, 1, 1))) row_number
FROM job_user_rank t
CROSS JOIN (SELECT #num := 0, #job := null) c
where is_match = 1
ORDER BY job_id, is_match DESC, rank DESC
I then wrap this query and add a where row_number <= ?, however I want to make that inner query into a view instead, but I get the following error [HY000][1351] View's SELECT contains a variable or parameter, how can I get around that?

Weird result in MySQL query

I have this query:
SELECT COUNT(1), name, (#i := #i + 1) AS counter FROM mytbl, (SELECT #i := 0) tmp_tbl GROUP BY counter
For this query, the counter column increases its value with 2.
But if I remove COUNT(1), such as:
SELECT name, (#i := #i + 1) AS counter FROM mytbl, (SELECT #i := 0) tmp_tbl GROUP BY counter
counter column increases its value with 1.
Can anyone explain why this behavior?
Table would be:
create table mytbl (name VARCHAR(20));
With data:
INSERT INTO mytbl VALUES
('a1'),
('a2'),
('a3');
As mentioned in MySQL document, we should not assign a value to a user variable and read the value within the same statement. We might get the expected results, but this is not guaranteed. Changing the statement (for example, by adding a GROUP BY, HAVING, or ORDER BY clause) may cause MySQL to select an execution plan with a different order of evaluation.
In your query, counter field will be evaluated in SELECT statement and then be used in GROUP BY statement. Seem when we add an aggregation function to SELECT statement, the field that be used in GROUP BY statement will be evaluated 2 times.
I've create a demo, you could check it. In the demo, I've this query
SELECT Count(1),
name,
( #i := #i + 1 ) AS counter,
( #j := #j + 1 ) AS group_field
FROM (SELECT 'A' AS name
UNION
SELECT 'B' AS name
UNION
SELECT 'C' AS name) mytable,
(SELECT #i := 0) tmp_tbl,
(SELECT #j := 0) tmp_tbl1
GROUP BY group_field;
In the execution result, counter field only be increased by 1 and group_field be increased by 2.
To make the counter field only increasing by 1, you could try this
SELECT Count(1),
name,
counter
FROM (SELECT name,
( #i := #i + 1 ) AS counter
FROM mytbl,
(SELECT #i := 0) tmp_tbl) data
GROUP BY counter;

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;

MySQL Query get the last N rows per Group

Suppose that I have a database which contains the following columns:
VehicleID|timestamp|lat|lon|
I may have multiple times the same VehicleId but with a different timestamp. Thus VehicleId,Timestamp is the primary key.
Now I would like to have as a result the last N measurements per VehicleId or the first N measurements per vehicleId.
How I am able to list the last N tuples according to an ordering column (e.g. in our case timestamp) per VehicleId?
Example:
|VehicleId|Timestamp|
1|1
1|2
1|3
2|1
2|2
2|3
5|5
5|6
5|7
In MySQL, this is most easily done using variables:
select t.*
from (select t.*,
(#rn := if(#v = vehicle, #rn + 1,
if(#v := vehicle, 1, 1)
)
) as rn
from table t cross join
(select #v := -1, #rn := 0) params
order by VehicleId, timestamp desc
) t
where rn <= 3;

Mysql count per name reset per name change

I have a table with entries names,date etc.
select name from names
order by names
for example.
I need an output like
1|paul
2|paul
1|morne
1|marone
2|marone
3|marone
i have no clue where to start to do a count like this for when i grou i cant get this right.
In MySQL, the easiest way to do this is with variables:
select n.name,
(#rn := if(#name = name, #rn + 1, 1)) as seqnum,
#name := name
from names n cross join
(select #name := '', #rn := 0) var
order by names;
EDIT:
In case anyone comes across this answer, the above sort-of works, but MySQL does not guarantee the order of evaluation of expressions. So, I now write the above as:
select n.name,
(#rn := if(#name = name, #rn + 1,
if(#name := name, 1, 1)
)
) as seqnum
from names n cross join
(select #name := '', #rn := 0) var
order by names;
Because all the variable assignments are in one statement, there is no problem with the order of evaluation of expressions in the select statement.