On MySQL prior to 8.0, trying to achieve this is quite a long way. In MySQL v8.0, one possible solution is using REGEXP_REPLACE like:
SELECT * FROM test
ORDER BY
REGEXP_REPLACE(val, "[0-9]", "") ASC,
ABS(REGEXP_REPLACE(val, "[A-Za-z]", "")) ASC;
However, since you're using MysQL v5.7, this is what I come up with:
SELECT val FROM
(SELECT val,
LEAST(IF(LOCATE(0, val)=0,999,LOCATE(0, val)),
IF(LOCATE(1, val)=0,999,LOCATE(1, val)),
IF(LOCATE(2, val)=0,999,LOCATE(2, val)),
IF(LOCATE(3, val)=0,999,LOCATE(3, val)),
IF(LOCATE(4, val)=0,999,LOCATE(4, val)),
IF(LOCATE(5, val)=0,999,LOCATE(5, val)),
IF(LOCATE(6, val)=0,999,LOCATE(6, val)),
IF(LOCATE(7, val)=0,999,LOCATE(7, val)),
IF(LOCATE(8, val)=0,999,LOCATE(8, val)),
IF(LOCATE(9, val)=0,999,LOCATE(9, val))) lv
FROM test) t
ORDER BY SUBSTRING(val,1,lv-1) ASC, SUBSTRING(val,lv)+0 ASC;
The idea is to get the first number occurrence then use that to separate between the values before numbers and the numbers itself using LOCATE function:
LOCATE(1, val)
*translate to "Locate the number 1 in `val` column".
IF(LOCATE(1, val)=0, 999, LOCATE(1, val))
*translate to "If you can't locate the number 1 in `val`, return 999
otherwise return the location of it.".
Since these are numbers we're looking for, it's much easier because I'll only need to locate 0 to 9
number values. Then, if LOCATE
return 0
as result, replace it with 999
. I replace them because I'm using LEAST to return the smallest value; which is the first number occurrence:
LEAST(IF(LOCATE ..
After that, I make the query as a sub-query then uses the result from LEAST(IF(LOCATE ..
as the centre of SUBSTRING
in the ORDER BY
:
SUBSTRING(val,1,lv-1)
*Will return the value before the first number occurrence.
(e.g ZZZ-AA1 to ZZZ-AA OR ZZZ-AAA1 to ZZZ-AAA).
AND
SUBSTRING(val,lv)
*Will return the value on the first number onwards.
(e.g. ZZZ-AA1 to 1 OR ZZZ-AAA1 to 1).
Of course, if you want to look at the value, you can simply add the statement in ORDER BY
to SELECT
like :
SELECT val, SUBSTRING(val,1,lv-1) , SUBSTRING(val,lv)+0 FROM
...
*If you notice, in the first SUBSTRING
I've added lv-1
because without minus 1
, the SUBSTRING
will return together with the first number occurrence. And the second SUBSTRING
I added +0
at the end because without plus 1
the result datatype is string so the ordering will mess up.
Demo fiddle