mariadb order by a user function gives wrong result - mysql

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

Related

SELECT Where ID in (List of IDs) and limit records of each IDs in MySQL

I have met a situation that I have a list of IDs of a Store table and need to fetch the latest 10 files from each store.
SELECT *
FROM tblFiles
WHERE storeId in (IDs)
ORDER BY createdDate DESC
LIMIT 10
But, this limits the whole results. I found an answer to a similar SO question. But, the answer recommends using loop for each ID. This results in multiple DB hit.
Another option is to fetch all records and group them in the code. But, this will be heavy if there are large no.of records.
It'll be nice if it can be handled at the query level. Any help will be appreciated.
NB: The tables used here are dummy ones.
Pre-MySQL 8.0, the simplest method is probably variables:
select f.*
from (select f.*,
(#rn := if(#s = storeId, #rn + 1,
if(#s := storeId, 1, 1)
)
) as rn
from (select f.*
from tblfiles f
where storeId in (IDs)
order by storeId, createdDate desc
) f cross join
(select #s := 0, #rn := 0) params
) f
where rn <= 10;
In MySQL 8+ or MariaDB 10.3+, you would simply use window functions:
select f.*
from (select f.*,
row_number() over (partition by storeid order by createdDate desc) as seqnum
from tblfiles f
) f
where seqnum <= 10;
In older versions of MySQL and MariaDB, the innermost subquery may not be needed.
use select in where
SELECT * from tblFiles where storeId in (SELECT id from store ORDER BY datefield/id field desc limit 10)
You could workaround it with an UNIONed query, where each subquery searches for a particular id and enforces a LIMIT clause, like :
(SELECT *
FROM tblFiles
WHERE storeId = ?
ORDER BY createdDate DESC
LIMIT 10)
UNION
(SELECT *
FROM tblFiles
WHERE storeId = ?
ORDER BY createdDate DESC
LIMIT 10)
...
With this solution only one db hit will happen, and you are guarantee to get the LIMIT on a per id basis. Such a SQL can easily be generated from within php code.
Nb : the maximum allowed of UNIONs in a mysql query is 61.

MySQL: How to ORDER BY and SET ids?

I already googled but couldn't find a solution.
I have two columns called order_id and name. I want to ORDER BY name and then SET ascending order_id.
Like this:
order_id name
1 Arya
2 Herbert
3 Paul
4 Peter
5 Tiffany
My id column is int(4) and by default value is 0. It's not PRIMARY or UNIQUE. (It'a also not the main id. Main id is of course PRIMARY.
How can I do this with SQL?
You could use ROW_NUMBER(MySQL 8.0+):
SELECT name, ROW_NUMBER() OVER(ORDER BY name) AS rn
FROM tab
ORDER BY rn;
Update:
UPDATE tab
SET order_id = (SELECT rn FROM (SELECT name,ROW_NUMBER() OVER(ORDER BY name) AS rn
FROM tab)s
WHERE s.name = tab.name); -- assuming that name is UNIQUE
DBFidde Demo
For versions < 8.0 you can use this:
select #rn := 0;
UPDATE tbl T1
JOIN (select #rn := #rn + 1 rn, `name` from tbl order by `name`) T2
ON T1.`name` = T2.`name`
SET T1.order_id = T2.rn
Demo
Useful article related to your problem: MySQL UPDATE JOIN
As your mysql version is below 8.0 so you have to manually generate orderid
below can help you
select t.*,
#rownum := #rownum + 1 AS order_id from
(
select * from
tab o order by name asc
) as t , (SELECT #rownum := 0) r
http://www.sqlfiddle.com/#!9/ae3fda/3

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;

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.

Rank in MySQL table

I have a MySQL table called "MyTable" and it basically lists usernames and points (two columns, name and points). I want to say something like "what is joe1928's rank?", which of course is based off his points. How could I do this in MySQL without having to download all that data and sort it and determine the rank myself?
The person with the highest number of points would be ranked 1.
Try getting the number of people with a higher score than your user:
select count(*) from MyTable where score > (select score from MyTable where user = 'Joe');
That will return 0 for the top user.
This page seems to describe and solve your problem.
Notes from that page:
SET #rownum := 0;
SELECT rank, correct FROM (
SELECT #rownum := #rownum + 1 AS rank, correct, uid
FROM quiz_user ORDER BY correct DESC
) as result WHERE uid=xxxxxxxx
SELECT #r AS Rank
FROM MyTable u, (SELECT #r := 0)
WHERE (#r := #r + 1) * (u.Username = 'joe1928')
ORDER BY u.Score DESC
LIMIT 1
select * from [TABLENAME] where [USERNAME] = blah order by [POINTS] desc limit 1;
Based on the link posted by #Dave your query will look like something below:
select Rank,name from
(select #rownum:=#rownum+1 AS 'Rank', p.name
from calls p, (select #rownum:=0) r
order by p.points desc) as rankResults
where name = 'joe';
This is from another stack overflow page, seems to solve your problem.
SELECT uo.*,
(
SELECT COUNT(*)
FROM users ui
WHERE (ui.points, ui.id) >= (uo.points, uo.id)
) AS rank
FROM users uo
WHERE id = #id