SQL Order By but invert for one element - mysql

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))

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

Custom number sequence formatting

The system I am working with has a numbering system where the numbers 0-999 are represented by the usual 0-999, but 1000 is represented by A00, followed by A01, A02, A03, etc, 1100 being B00 etc.
I can't think of a way to handle this in T-SQL without resorting to inspecting individual digits with huge case statements, and there must be a better way than that. I had thought about using Hexadecimal but that's not right.
DECLARE #startint int = 1,
#endint int = 9999;
;WITH numbers(num)
AS
(
SELECT #startint AS num
UNION ALL SELECT num+1 FROM numbers
WHERE num+1 <= #endint
)
SELECT num, convert(varbinary(8), num) FROM [numbers] N
OPTION(MAXRECURSION 0)
With this 999 is now 3E7, where it should just be 999.
This currently produces this:
Number Sequence
0 0x00000000
1 0x00000001
...
10 0x0000000A
...
100 0x00000064
...
999 0x000003E7
1000 0x000003E8
What I'm looking for:
Number Sequence
0 000
1 001
...
10 010
11 011
12 012
...
999 999
1000 A00
1001 A01
...
1099 A99
1100 B00
1101 B01
1200 C00
I need this to work in SQL Server 2008.
You can use integer division and modulo to separate the hundreds part from the tens.
After that, you can add 64 to the quotient to get an ASCII value starting from A.
create function function dbo.fn_NumToThreeLetters(#num integer)
RETURNS nchar(3)
AS
begin
RETURN (SELECT (case
when #num/1000 >0 then
CHAR(( (#num-900)/100) +64)
+ replace(cast( #num %100 as nchar(2)),' ','0')
else cast(#num as nvarchar(3))
end)
)
END
select dbo.fn_NumToThreeLetters(1100)
-------
B00
select dbo.fn_NumToThreeLetters(999)
-------
999
The first when clause ensures that the conversion is applied only if a number is above 1000. If it is, subtract 900 then divide by 100, so we get a number that starts from 1 for 1000, 2 for 1100, etc.
Add 64 to it to get an ASCII starting from A and convert it back to a character with CHAR.
The remainder just needs to be converted to a 2-digit nchar, where spaces are replaced with 0.
This will work only up to 3500. The question doesn't specify what should be done with larger numbers

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.

Why is this select ordering not working as expected?

My select statement is
SELECT styleNo , right( concat( '######', trim( styleNo ) ) , 6 ) AS keyn
FROM `styles` WHERE 1 ORDER BY keyn
StyleNo is varchar(6) and contains mostly numeric strings from 3 to 6 characters long, some of which include a leading or trailing capital letter. The aim is to show the styles in magnitude order.
I want 100 to 999 first, then A00 to Z99, then 1000 to 9999 and A000 to Z999 etc.
I get these results.
101 ###101
180 ###180
105 ###105
104 ###104
102 ###102
123 ###123
124 ###124
432 ###432
1004 ##1004
1001 ##1001
1002 ##1002
1003 ##1003
1006 ##1006
1234 ##1234
1231 ##1231
1255 ##1255
1288 ##1288
2005 ##2005
2006 ##2006
2007 ##2007
2008 ##2008
Why are the results not in ascending order? keyn is what I expect.
I am using MySQL 5.1.36-community with UTF-8 characters sets everywhere. None of the styleNo shown contain anything other than '0' to '9' characters.
Some further information: I changed the sql statement to
SELECT styleNo , right( concat(repeat('#',5), trim( styleNo ) ) , 6 ) AS keyn
FROM `styles` ORDER BY keyn ASC
and tried with different values for the 5. 3 and larger should all produce suitable values for keyn.
However 3, 4 5, 6 and 7 all produce different orders of the results - all wrong.
A value of 8 works for the first 75 lines in my test file.
Without understanding why, I don't feel confident this is correct.
It seems you want to order by the length of the (trimmed) field, so you could also say:
SELECT styleNo FROM `styles`
WHERE 1
ORDER BY LENGTH(TRIM(`styleNo`)),TRIM(`styleNo`)
potentially saving you a headache as well when styleNo is changed one day to be longer than 6 chars.

MySQL append , prefix a range of numbers with 0

I have around a few thousand rows with which contain 3 digit numbers starting with 100 and ranging to 199 which i need to prefix with 0. There are also thousands of other numbers 4 digit numbers as well which i don't want to change.
I need find all the 3 digit numbers in the range and prefix only those ranging from 100 -199 with a 0 so as they are 4 digits eg 100 > 0100 , 104 > 0104 and so on.
Also these numbers may step eg 110 next is 124.
Is there a way I can do this using SQL? as i don't fancy changing these manually!
Many Thanks
This is best done with a programming language. That said, here's a SQL query that will update all the existing numbers:
UPDATE tableName SET fieldName = right(concat('0000',fieldName), 4) WHERE length(fieldName) < 4
The LPAD function is what you are looking for. You can use this in your query to pad the numbers on the fly.
SELECT LPAD(CONVERT(num AS CHAR), 4, '0') FROM tbl WHERE num > 99 AND num < 200
If you prefer to do this on the script side, str_pad will do the same in php.