Sorting alphanumerically with a twist - mysql

I want to sort a field alphanumerically in the database. It turns out to be trickier than I thought. This is just example values, the content can vary, but I hope it's enough to get the idea.
I want to sort this list:
11
01
1
1A
01B
20a
01a
20
1b
2b
02a
Like this:
1
01
1A
01a
1b
01B
02a
2b
11
20
20a
Note that the relative ordering of equivalent numbers with and without leading zeroes is not important, it can be 1 01 or 01 1.
I've tried CAST(field AS UNSIGNED) but it doesn't work. Ideas?

If you're using MySQL 8.0 or higher, you can use REGEXP_SUBSTR(colname, '[a-z]+$') to get the alphabetic suffix, and CAST(colname AS UNSIGNED) to get the numeric prefix. Then you can sort by these.
SELECT code
FROM yourTable
ORDER BY CAST(code AS UNSIGNED), REGEXP_SUBSTR(code, '[a-z]+$')
See What is the equivalent of REGEXP_SUBSTR in mysql? for how to get similar functionality in earlier versions of MySQL.
Another possibility is:
ORDER BY CAST(code AS UNSIGNED), TRIM(LEADING '0' FROM code)

Related

MySQL extract first 4 digits

I want to run a query in mysql which will return the record where the first 4 digits are '0123' or '0798' from the following column:
Number
0123 427 6465
0123 1451
01 23 46 47
0123 945675
07984 473456
0845 46 47
(012377) 5258
0800 586931
012 3668 6098
0 1238592371
I want the query to return all records where '0123' or '0798' are the first 4 numeric characters regardless of if there are other characters before or in between. E.g. I would want record 7 returned even though '0123' is in brackets. And I would want record 10 returned even though it is written as '0 123' i.e. there is a space in between.
Is regex relevant here? If so, what would the regex expression be?
Use a combination of LEFT and REPLACE.
REPLACE will strip out any unwanted brackets and whitespaces, and LEFT will select the first four characters, starting from left, of the newly formatted value which will be used in the WHERE clause selecting for values IN '0123', '0798'.
SELECT `number` FROM Numbers WHERE LEFT(REPLACE(REPLACE(REPLACE(`number`, '(', ''), ')', ''), ' ', ''), 4) IN ('0123', '0798')
Fiddle.
Result:
Number
0123 427 6465
0123 1451
01 23 46 47
0123 945675
07984 473456
(012377) 5258
012 3668 6098
0 1238592371
Also, it's worth noting, number is a Reserved Word in MySQL. I used backticks ` to escape it, however, it is advised that you do not use reserved words in your naming conventions.
We can use REGEXP_REPLACE function to remove all others characters other than number and get first four using the below query,
SELECT LEFT(REGEXP_REPLACE(Number, '[^0-9]+', ''), 4) as 4digitonly FROM Numbers a;
Please refer How to get only Digits from String in mysql?
Nothing is better than regex, yes they make us think even think recursivelly :)
Here is the query(of course it can be refactored N times):
SELECT n.number FROM Numbers n WHERE n.number REGEXP '^.*(0[ \t\r\n]*1[ \t\r\n]*2[ \t\r\n]*3).*|^.*(0[ \t\r\n]*7[ \t\r\n]*9[ \t\r\n]*8).*$'
Fiddle

SQL Order By but invert for one element

I've got an SQL query which results upto the following
Code int1 int2 int3 S
C12 21 22 14 1
C33 43 56 2 3
C34 23 2 1 3
C55 33 92 12 5
CB56 45 66 10 5
MA10 10 11 12 1
This is the result of using OrderBy on Code
However I do not want it to order according to alphabets
But by the number after it for ex 1 in M1 and 33 in C33
In some cases the number after the alphabet may be 3 digits like E344
What I want it to look like
Code int1 int2 int3 S
MA10 10 11 12 1
C12 21 22 14 1
C323 43 56 2 3
C325 43 56 2 3
C34 23 2 1 3
C525 33 92 12 5
CB56 45 66 10 5
What I need is
M Should always show on top if Present
Then Sort According to Number on first place
Then Sort it according to the number on the Second place
Then sort it according to the number on the third place
The field 's' will always consist of the first digit from the Code
step 1 split up the column into 2 columns, 1 containing the letters, the other containing the numbers:
SELECT
substring(code,0,PATINDEX('%[0-9]%', Code)) as letters,
substring(code,PATINDEX('%[0-9]%', Code)) as numbers,
fields
FROM table
step 2 Convert the numbers to integer and sort
CONVERT(substring(code,PATINDEX('%[0-9]%', Code)),UNSIGNED INTEGER) as numbers
step 3 sort
Order by field1 asc, field2 desc... etc
It might be easier to use a subquery:
select * from
(SELECT
substring(code,0,PATINDEX('%[0-9]%', Code)) as letters,
CONVERT(substring(code,PATINDEX('%[0-9]%', Code)),UNSIGNED INTEGER) as numbers,
fields
FROM table) T
order by numbers asc, letters desc
This is conceptually simple: you want to order first by whether the code starts or not with the letter 'M', and then by the numeric portion of the code. You say in a comment:
extracting the first digit then order and then extracting the second
digit and then order and then extracting the third digit and then
order
This is exactly how alphabetical order has always worked. You order by first character; when it is the same you order by the second character, etc. so you need no special treatment for that case. Just get the numeric part of the code as a string, then order by it.
At this point, the only problem left to resolve is how to extract the numeric part of the code. That would be easy with PATINDEX() (as Alfons pointed out) but unfortunately MySQL does not support PATINDEX() as far as I know.
Now, what follows is extremely ugly, but it does work. Basically we get the non-numeric part of the string by removing all the numeric characters from it, then use the length of the non-numeric part to extract the numeric part.
SELECT mytable.* FROM mytable
INNER JOIN (
SELECT
code,
RIGHT(code, LENGTH(letters)) numbers
FROM (
SELECT
code,
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(
code, 0, ''), 1, ''), 2, ''), 3, ''), 4, '')
, 5, ''), 6, ''), 7, ''), 8, ''), 9, '') letters
FROM mytable
) letters_table
) numbers_table
ON numbers_table.code = mytable.code
ORDER BY (numbers_table.code like 'M%') DESC,
numbers_table.numbers ASC
This solution is probably inefficient. However, I don't think you can get any acceptable efficiency anyway unless you store the numeric part on a separate column that you can index.
As you are guaranteeing that s is the value of the first digit in the code, this can be used to find the start of the code and from there, get the numeric part of the code. As you want to have all the codes starting with 'M' first, this results in the following ORDER BY clause:
... ORDER BY IF(SUBSTR(code, 1, 1) = 'M', 0, 1), SUBSTR(code, LOCATE(s, code))

Sorting course_numbers like sorting using natsort

I have these sample course_numbers
cmsc 11
cmsc 2
cmsc 56
cmsc 21
cmsc 128
I use this query
SELECT * FROM subject ORDER BY LENGTH(`course_number`)
to natural sort the result
and it worked, but when i add these course_numbers
it 1
it 256
it 20
they kinda mess up.
What query should i use to order them like this
cmsc 2
cmsc 11
cmsc 21
cmsc 56
cmsc 128
it 1
it 11
it 20
it 100
it 256
I've searched and saw 'case' on their select statements but I do not know how to use them
You should consider splitting up the both parts of your course number, since "CMSC"/"IT" is one Part (even with variable length), and the real number is another part. If you store the number in a number column (int), you could easily correct them.
so it would be
SELECT concat(course_type, " ", course_subnumber) as course_number, ...
from subject
order by course_type, course_subnumber
As long as you have a bit luck, you could try with you structure the following:
SELECT * from subject
order by left(course_number, 2), length(course_number), course_number
then you get only in trouble if different course_types start with the same two letters.

Mysql pivot table (concat) misuse (processing time)

I want to pivot the example below, I'm using:
SELECT Person, GROUP_CONCAT( Var ) , GROUP_CONCAT( Val )
FROM table
GROUP BY Person
This works fine, but... it takes about 20 seconds per line and my table has +/- 2.500.000 records :-P
(BTW; the table below is an example, not the actual one)
id person Var Val
-------------------------------
1 Bob Height 185
2 Bob Weight 74
3 Bob Age 40
4 Hank Height 193
5 Hank Weight 90
6 Hank Age 45
7 Bert Height 180
8 Bert Weight 85
9 Bert Age 43
PS:
Besides an answer (what would make you awesome) I also would like to know what is 'wrong' with this example (makes you even more awesomer)
There's nothing wrong with it, except that I'd write it like this
SELECT Person, GROUP_CONCAT(CONCAT(Var, ': ', Val))
FROM table
GROUP BY Person
because when you split it up in two columns, you won't know which Val belongs to which Var.
Apart from that, do you have indexes defined on those columns? If not, play around to see which index works best, separate indexes for each column or compound indexes on person, var, val.

How to show every max value in mysql?

I've multiple values with different timestamps like the following:
10 01:01:00
20 01:35:00
30 02:10:00
05 02:45:00
12 03:05:00
21 03:30:00
10 04:06:00
40 05:15:00
I don't have a column with which I can group by and find max. I want to get the records with max values like 30,21, and 40. The data is always in this format, like value increasing and then starts from zero again. What query will help me to find these records?
To clarify, it's sorted by the timestamp, and I want to get the timestamps for the local maxima, the rows where the next row has a lesser value:
value tmstmp
----- --------
10 01:01:00
20 01:35:00
30 02:10:00 <-- this one since next value is 5 (< 30).
05 02:45:00
12 03:05:00
21 03:30:00 <-- this one since next value is 10 (< 21).
10 04:06:00
40 05:15:00 <-- this one since next value is 40 (< infinity).
Somehow your question is not clear to me.
Assume that first column name is "value" and second column name is "timestamp".
Select Max(value) from group by timestamp.
This answer might be a bit late, however i think i have found the solution
SELECT * FROM temp t1 WHERE value >
IFNULL(
(SELECT value FROM temp t2
WHERE t2.tmstmp > t1.tmstmp ORDER BY t2.tmstmp ASC limit 1),
-1
)
ORDER BY tmstmp ASC
To clarify:
I find the values where the value is greater than the next value in the row.
To also get the final value I have added an IFNULL around the subquery to make sure the subquery will then return -1
The only problem i see is when the time goes over to the next day, that's why i hope you can have a date appended to it as well.
Hopefully this will still help others