MSSQL: ROW_NUMBER() in Sub-Queries - mysql

This below was originally written for MySQL, with the #rownumber is defined as a scalar-variable.
SELECT CONCAT( z.expected, IF(z.got-1>z.expected, CONCAT(' thru ',z.got-1), '') AS `Missing Receipt ID`
FROM (SELECT #rownum \\:= #rownum+1 AS expected,
IF(#rownum=recpt_id, 0, #rownum \\:= recpt_id) AS got
FROM
(SELECT #rownum \\:= (SELECT MIN(CAST(recpt_id AS SIGNED))-1 FROM report_receipt
WHERE outlet_desc IN "+Branch+" )
) AS a
JOIN report_receipt r
ON r.outlet_desc IN "+Branch+"
ORDER BY CAST(recpt_id AS SIGNED)
) AS z
WHERE z.got!=0
I am trying to create the same query but for the SQL Server platform. I am aware that in MSSQL, we can use the ROW_NUMBER function and then have a OVER(ORDER ) clause at a place where the ROW_NUMBER is called. The query is also going to be used in native query format in our Java code.
So far, after some minor syntax adjustments, I have reached to this current state in MSSQL but with some uncertainties:
SELECT CONCAT( z.expected, CASE WHEN z.got-1>z.expected THEN CONCAT(' thru ',z.got-1) ELSE '' END) AS [Missing Receipt ID]
FROM
(SELECT #rownum \:= #rownum+1 AS expected,
IF(#rownum=recpt_id, 0, #rownum \:= recpt_id) AS got
FROM (SELECT ROW_NUMBER() OVER(ORDER BY CAST(recpt_id AS INT))) \:= (SELECT MIN(CAST(recpt_id AS INT))-1 FROM report_receipt
WHERE outlet_desc IN "+Branch+" )
) AS a
JOIN report_receipt r
ON r.outlet_desc IN ('MY011')) AS z
WHERE z.got!=0
What I'm unsure but looking for answers would be:
If there's is only one place ORDER clause is placed, do we just place it inside the innermost query while for other places we have to use only ROW_NUMBER() OVER(ORDER BY SELECT NULL)?
If the query is considered having unnecessary additional nesting, what is the other alternative without having to call too many ROW_NUMBER functions repeatedly?
Thanks in advance.
Note: The '\' is an escape character added for the Java code.

Related

MySQL behaves differently on 3 servers, same version

I've got this query running on two different fiddle sites, both set to use MySQL 5.6:
SELECT name, rank, position FROM(
SELECT name, position,
#rank:= IF(#prev = name, #rank + 1, 1) AS rank,
#prev:= name
FROM (SELECT * FROM drivers
LEFT JOIN results on drivers.id = results.driver_id
JOIN (SELECT #rank := 1) AS init
ORDER BY name, results.position ASC) AS temp
) AS derived WHERE rank <= 3 ORDER BY name, rank
It's supposed to give the top 3 finishing positions of each driver. The query works on fiddle #1, but not on fiddle #2 or the production server, although all three of them are running on MySQL 5.6.
Is there a setting that I'm missing?
Fiddle #1 - working
Fiddle #2 - not working
You can use the following solution:
SELECT name, rank, position FROM (
SELECT name, position,
#rank:= IF(#prev = name, #rank + 1, 1) AS rank,
#prev:= name
FROM (
SELECT *
FROM drivers LEFT JOIN results ON drivers.id = results.driver_id
JOIN (SELECT #rank := 1) AS init_rank
JOIN (SELECT #prev := '') AS init_prev
ORDER BY name, results.position ASC
) AS temp
) AS derived
WHERE rank <= 3
ORDER BY name, rank
I added the initialization for the #prev variable on a JOIN too.
different demos:
demo on db-fiddle.com
demo on sqlfiddle.com
demo on dbfiddle.uk
Since MySQL 8.0 you can use the built-in RANK window function. So you don't need the #prev or #rank variables:
SELECT name, `rank`, position FROM (
SELECT name, position, RANK() OVER (PARTITION BY name ORDER BY name, position) AS `rank`
FROM drivers LEFT JOIN results ON drivers.id = results.driver_id
) AS derived
WHERE `rank` <= 3
ORDER BY name, `rank`
demo on dbfiddle.uk

mariadb order by a user function gives wrong result

Using MySQL/MariaDB, I usually do this kind of query below to get the rank of a specific record so that I can display the proper page in an application:
SET #rownum := 0;
SELECT rank
FROM
( SELECT #rownum := #rownum+1 AS rank, ordid
FROM ord
order by ord_status, ordid
) AS derived_table
WHERE ordid = 1234
limit 1;
I used it for years and it usually works just fine.
However, today, I tried to sort the query according to the description of the order status instead of the order status id (field ord_status). So, I had to sort data using the user funtion named getStatusDescription() that I created in my database. Here is my new query:
SET #rownum := 0;
SELECT rank
FROM
( SELECT #rownum := #rownum+1 AS rank, ordid
FROM ord
order by getStatusDescription(ord_status), ordid
) AS derived_table
WHERE ordid = 1234
limit 1;
For an unknown reason, the rank result is wrong and I do not understand why it is not working. Is it possible that there is a problem or a limitation with MariaDB ?
I'm using MariaDB 10.0.17 on a Centos 7 machine as my development plaftform.
For your information, my function getStatusDescription() just receive a parameter (the order status id) then according to the parameter received select the proper varchar(35) field from a specific table then just return it.
Any help is very welcome.
Guylain Plante
Your method of getting rank is fine, but you can also use a subquery method:
select count(*)
from ord cross join
(select ordid, getStatusDescription(o2.ord_status) as gsd
from ord o2
where o2.ordid = 1234
) oo
where getStatusDescription(ord_status) < oo.gsd or
(getStatusDescription(ord_status) = oo.gsd and o.ordid <= oo.ordid)
As for your situation, I don't know about the root cause. However, sometimes GROUP BY is problematic with variables and an additional subquery fixes the probelm:
SELECT rank
FROM (SELECT #rownum := #rownum+1 AS rank, ordid
FROM (SELECT ord.*
FROM ord
ORDER BY getStatusDescription(ord_status), ordid
) derived_table CROSS JOIN
(SELECT #rownum := 0) params
) o
WHERE ordid = 1234
LIMIT 1;
Finally, I remembered that I had to put a LIMIT (and also a sub-query) so that the database have a good reason to apply the sort order.
The following query works fine now:
SELECT rank FROM (SELECT #rownum := #rownum+1 AS rank, ordid
FROM (SELECT ordid FROM ord order by getStatusDescription(ord_status), ordid LIMIT 9999999999999
) as t2
) AS derived_table
WHERE ordid = 1234 limit 1;
See the following thread as well:
mysql - order by inside subquery

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;

what does `(SELECT #rn :=0) var_init` do?

I have seen some MySql queries using
(SELECT #rn :=0, #ct := NULL ) var_init
i don't know what it does .. i have searched for it for a long time and still i don't have an answer. any help is much appreciated .
it will be much appriaced if some one could explain this query to me ...
SELECT * FROM (
SELECT c. * , #rn := IF( `type` != #ct , 1, #rn +1 ) AS rownumber, #ct := `type` FROM jb_company c ,
(SELECT #rn :=0, #ct := NULL ) var_init ORDER BY `type`
) c
WHERE rownumber <=20
I am using the above query to fetch limited number of rows( i.e 20) of each type in from the table ( see the link below..for the question where i needed this )
Mysql query to fetch limited rows of each type
but i am still not getting the query.. some please help
Thanks in advance.
It plainly initializes the values (#rn :=0, #ct := NULL), which results in an alias var_init containing one row, and joins the rest on it (so, having no effect on the rows themselves other then setting up the variables in the beginning).
This is often used to avoid needing multiple statements to set up the variables. That single query is equal to:
SET #rn :=0;
SET #ct := NULL;
SELECT * FROM (
SELECT c. * ,
#rn := IF( `type` != #ct , 1, #rn +1 ) AS rownumber,
#ct := `type`
FROM jb_company c
ORDER BY `type`
) c
WHERE rownumber <=20
.. which is multiple statements, so usually used due to API limitations of the using code, or to make sure the variables start out as they should be on shared connections.
This is an expression in MySQL used to define variables that might be used in the expression.
By default, MySQL defaults variables to strings. So, to get a numeric variable, you want to assign the variable to a number the first time it is seen. This can be done in a set statement. However, a "single" query would then consist of multiple statements.
Often, variables in MySQL are used to approximate the window/analytic functions available in most other databases. Other databases do not encourage the use of such variables in queries (although they are typically allowed and can be useful under some -- more limited -- circumstances).
The query that you mention would be expressed as the following in most databases:
SELECT *
FROM (SELECT c.* ,
row_number() over (partition by `type` order by `type`) as rownumber
FROM jb_company c
) c
WHERE rownumber <= 20;
The way the MySQL version works is by creating a derived table and using the variables to add rows with the appropriate values.

unable to create view

I am getting error : View's SELECT contains a variable or parameter with the query below.How do I create view for the same.I really appreciate any help.Thanks in Advance.
CREATE VIEW V AS SELECT #rownum := #rownum + 1 AS rank, name, vote
FROM uservotes, (SELECT #rownum := 0) t ORDER BY vote DESC
declare #rownum int
CREATE VIEW V AS SELECT #rownum := #rownum + 1 AS rank, name, vote
FROM uservotes, (SELECT #rownum := 0) t ORDER BY vote DESC
Try like this
CREATE VIEW V AS
(
SELECT (SELECT 1 + COUNT(*) FROM uservotes where votes < T.votes ) AS NUM, name, votes
FROM uservotes T ORDER BY votes DESC
)
Fiddle Demo
You can define a variable in order to get psuedo row number functionality, because MySQL doesn't have any ranking functions:
Can't Use A Variable in a MySQL View
If you do, you'll get the 1351 error, because you can't use a variable in a view due to design. The bug/feature behavior is documented here.