How can I ORDER anything that looks like a number, as a number in T-SQL? - sql

I have a column named Code that is varchar(3).
It contains numbers and strings as well. For example: ' 1', '234', 'Xxx', '9 ','Aa ' etc.
Is there way -just like in MS EXCEL- ORDER anything that looks like a number, as a number?
So that output for the given example above will be:
1. 1
2. 234
3. 9
4. Aa
5. Xxx

ORDER BY CASE WHEN ISNUMERIC(YourField) = 1 THEN CONVERT(INT, YourField) - 500 ELSE ASCII(LOWER(YourField)) END
If the field can be converted to a number it is sorted by number otherwise it uses ASCII coding to sort. I have used "- 500" just so there is no cross over in the sort, and to ensure numbers are sorted ahead of text.
ADDENDUM:
Brian Arsuaga has posted a more robust solution to this which I actually prefer, but since this has already been marked as the answer I am adding his solution to this for the benefit of anyone reading this in the future.
ORDER BY
ISNUMERIC(YourField) DESC,
CASE WHEN ISNUMERIC(YourField) = 1 THEN CONVERT(INT, YourField) ELSE 0 END,
YourField

If you don't like using an arbitrary sentinel (500), which might cause sorting issues depending on the range of numbers you expect, you can use multiple expressions for the ordering.
-- put the numbers at the top
ORDER BY ISNUMERIC(YourField) DESC,
-- sort the numbers as numbers, sort the strings as nothing
CONVERT(INT, CASE WHEN ISNUMERIC(YourField) = 1 THEN YourField ELSE '0' END),
-- sort the strings
YourField
The last term is only a tiebreaker when either two terms are both numbers with the same value ('01', '1') or two terms are both non-numbers. For non-numbers, their first and second terms will always be 0.
More complicated, but maybe a little more safe.
Edited to add a nice comparison with the help of the guy below
create table #t
(
YourField varchar(4)
)
insert into #t(YourField) Values('1'), ('3'), ('234'), ('0'), ('00'),
('09'), ('9'), ('1a'), ('aaa'), ('aba'), ('-500')
Select YourField from #t
ORDER BY ISNUMERIC(YourField) DESC,
CONVERT(INT, CASE WHEN ISNUMERIC(YourField) = 1 THEN YourField ELSE '0' END),
YourField
drop table #t

Related

SQL query to sort data while insert , first numbers then alphabets an last symbols

I am getting trouble to write SQL sort Query,
I have table as follows
And I want to sort above data as, First should number and then alphabets and last special symbols like following table.
First numbers should sort like 1,2,3,11,22,33
then Alphabets should sort like a ,b,c,..z
and then symbols,
like following table
I have tried many ways but still not getting correct way, please help me to write query.
You can use a CASE WHEN on the ORDER BY to create some groups:
SELECT *
FROM table_name
ORDER BY
CASE WHEN LEFT(FilterParameterValue, 1) LIKE '[0-9]' OR LEFT(FilterParameterValue, 2) LIKE '-[0-9]' OR LEFT(FilterParameterValue, 2) LIKE '+[0-9]' THEN 1 ELSE 0 END DESC, -- group for numbers
CASE WHEN ISNUMERIC(FilterParameterValue) = 1 THEN CAST(FilterParameterValue AS MONEY) ELSE NULL END ASC, -- order the numeric values
CASE WHEN LEFT(FilterParameterValue, 1) LIKE '[A-Za-z]' THEN 1 ELSE 0 END DESC, -- group for chars from A to Z (capital / non capital)
colName
demo on dbfiddle.uk
Try using regex in order by clause.. For ex
ORDER BY IF(FilterParameterValue RLIKE '^[a-z]', 1, 2), FilterParameterValue
You can try to cast your Column to Numeric Type then ordered
SELECT * FROM table_name ORDER BY TRY_CAST(Column_Name as Type)

SQL statement to retrieve specific string

I'm trying to extract a number from a Notes column. I only want the number if it is preceded by a specific string of characters. The number can be 5 or 6 characters long, and may eventually go up to 7 or more in the future.
What combination of LEFT, RIGHT, CHARINDEX, or SUBSTRING do I need to utilize to accomplish this? or do I need to use something else altogether? I haven't been able to figure anything out that works.
The Notes column contains lots of different kinds of notes as well, so that's been making it difficult.
Thanks in advance.
EDIT: Sorry, here's some sample data and expected output.
EDIT2: Sorry again, I should've made the sample data a bit more clear. There are multiple numbers, but I only want the numbers that are preceded by 'Account #'.
Sample Data Output
'Account #12345' 12345
'Account #123456' 123456
'Random #12345' NULL
'Account #1234567' 1234567
'12345' NULL
'Random' NULL
This should do it.
SELECT YT.[Sample Data],
S2.[Output]
FROM YourTable YT
CROSS APPLY (SELECT 'Account #') S(S)
CROSS APPLY (SELECT CASE
WHEN CHARINDEX(S.S, [Sample Data]) > 0
THEN SUBSTRING([Sample Data], LEN(S.S) + CHARINDEX(S.S, [Sample Data]), 8000)
END) S1(StringWithNumberAtStart)
CROSS APPLY (SELECT LEFT(StringWithNumberAtStart,
PATINDEX('%[^0-9]%', StringWithNumberAtStart + 'X'))) S2([Output])
You might try this:
DECLARE #dummyTbl TABLE(YourString VARCHAR(100));
INSERT INTO #dummyTbl VALUES
('Account #12345')
,('Account #123456')
,('Random # note')
,('Account #1234567');
WITH Casted AS
(
SELECT YourString
,CAST('<x>' + REPLACE(YourString,' #','</x><x>') + '</x>' AS XML) toXML
FROM #dummyTbl
)
SELECT YourString
,toXML
,toXML.value('x[1]','nvarchar(max)') AS KeyText
,toXML.value('x[2] cast as xs:int?','int') AS NumberIfCastable
FROM Casted;
The result
YourString toXML KeyText NumberIfCastable
Account #12345 <x>Account</x><x>12345</x> Account 12345
Account #123456 <x>Account</x><x>123456</x> Account 123456
Random # note <x>Random</x><x> note</x> Random NULL
Account #1234567 <x>Account</x><x>1234567</x> Account 1234567
You can see, that I used a trick with CAST and REPLACE to transform your string to an XML which allows to address each part separately. The XPath to the first element x[1] returns Account or Random while the second x[2] returns the number.
A second trick I use is XQuerys implicit ability to try_cast a value. If the value cannot be converted to an xs:int? it will be returned as a NULL.
This ensures 5 digits after 'Account #' and ask for isnumeric when longer. This is not 100% your request but is an approach. Isnumeric function works in a special way
https://learn.microsoft.com/en-us/sql/t-sql/functions/isnumeric-transact-sql
--len('Account #') is 9
select case when Notes like 'Account #[0-9][0-9][0-9][0-9][0-9]%'
and isnumeric(right(Notes,len(Notes)-9) )=1 then right(Notes,len(Notes)-9)
else null end
from myTable
For SQLServer 2012+ use try_parse
https://learn.microsoft.com/en-us/sql/t-sql/functions/try-parse-transact-sql
select case when Notes like 'Account #[0-9][0-9][0-9][0-9][0-9]%'
and isnumeric(try_parse(right(Notes,len(Notes)-9) as bigint ))=1
then right(Notes,len(Notes)-9) else null end
from myTable
SQLFIDDLE http://sqlfiddle.com/#!6/cd617/2
I would use substring() with patindex() function to get the numeric values only
SELECT sampledata, SUBSTRING(sampledata, PATINDEX('%[1-9]%', SampleData),
case
when(LEN(sampledata)-PATINDEX('%[1-9]%', SampleData)+1) > LEN(SampleData)
then 0
else LEN(sampledata)-PATINDEX('%[1-9]%', SampleData)+1
end) numericvalues
FROM table;
EDIT:
Changing the question after you have asked it is rude. It can invalidate answers which in turn can attract down votes.
Well, i updated the answer according to the edited post.
select sampledata,
case
when patindex('%Account #%', sampledata) > 0
then SUBSTRING(sampledata, PATINDEX('%[1-9]%', sampledata), LEN(sampledata)-PATINDEX('%[1-9]%', sampledata)+1)
end [Output]
from table
Result :
Sample Data Output
'Account #12345' 12345
'Account #123456' 123456
'Random #12345' NULL
'Account #1234567' 1234567
'12345' NULL
'Random' NULL

SQL Case Decimal Between

In my database I have an entry like: 'V2.0.0.0 VersionData'
I need my case statement to look at this value, trim off from the space onwards and remove the V at the start leaving '2.0.0.0, the case statement should then return 'Pass' if its greater than 2.0.1.0 else 'Fail'
This is what I have so far but it doesnt work:
Select
CASE
When (Select
Convert(Decimal(4,4),
REPLACE(
LEFT(EntryValue,
CHARINDEX(' ', EntryValue) - 1),'v',''))
from table where entrytype = 'VERSION') BETWEEN 0 and 2.1 THEN 'Pass'
Else 'Fail'
END [V-Check]
Your problem is that
declare #EntryValue varchar(250)='V2.0.0.0 VersionData'
Select REPLACE(LEFT(#EntryValue,CHARINDEX(' ', #EntryValue) - 1),'v','')
--2.0.0.0
You do not have a string that can be intepreted like a decimal.
Without knowing how your version numbering Works, a solution can not be found,
For instance is V2.11.0.0 possible? If so, then is V2.5.0.0 a a lower or higher version?
If you convert 2.11.0.0 to the decimal 2.11 then it will be lower than 2.5
In what I think would be the default intepretation of the version number, you do not have a single number, but 4 numbers you have to compare consecutively to get a result.
One of many ways to parse out the numbers in the version would then be:
declare #EntryValue varchar(250)='V2.0.0.0 VersionData'
;with cte as
(
select charindex('.',#EntryValue) p1
,charindex('.',#EntryValue,charindex('.',#EntryValue)+1) p2
,charindex('.',#EntryValue,charindex('.',#EntryValue,charindex('.',#EntryValue)+1)+1) p3
,charindex(' ',#EntryValue) p4
)
select substring(#EntryValue,2,p1-2)+0 [MajorVersion]
,SUBSTRING(#EntryValue,p1+1,p2-p1-1)+0 [MinorVersion]
,SUBSTRING(#EntryValue,p2+1,p3-p2-1)+0 [BugFixRelease]
,SUBSTRING(#EntryValue,p3+1,p4-p3-1)+0 [Build]
from cte
If the numbers cannot be larger than 10, then you have an easy task, then you can just use a simple compare
select case when 'V2.1.0.0 VersionData'>= 'V2.0.1.0 VersionData' then 1 else 0 end
Since the lexicographical ordering of the characters would ensure that the result is correct.
-- Added in edit
If you have up to 4 numbers in your version, then you can abuse the internal function PARSENAME to get the numbers in an easier fashion:
declare #EntryValue varchar(250)='V2.0.0.0 VersionData'
select
parsename(stuff(left(#EntryValue,charindex(' ',#EntryValue)),1,1,''),4)+0 MajorVersion
, parsename(stuff(left(#EntryValue,charindex(' ',#EntryValue)),1,1,''),3)+0 MinorVersion
, parsename(stuff(left(#EntryValue,charindex(' ',#EntryValue)),1,1,''),2)+0 Release
, parsename(stuff(left(#EntryValue,charindex(' ',#EntryValue)),1,1,''),1)+0 Build
One problem is that converting a string '2.0.1.0', which contains several .-characters will not properly convert into 2010 (as you might intend).
However, if you can guarantee that you always have exactly 4 version parts and each version part is between 0..9, i.e. there is no version like, for example, 2.0.11.0' or2.0.1.0.9', then the following solution should work. It simply relies on the order of string values, which - above mentioned restriction concerning version parts guaranteed - is just as if you compared integral values:
with versions as
( select entryValue, Substring(LEFT(EntryValue,
CHARINDEX(' ', EntryValue)),2,100) as versionStr
from (values
('V2.0.0.0 some older version'),
('V2.0.1.0 sufficient version'),
('V2.0.1.9 newer version')
) as versionTable(entryValue)
)
select *, (case when versionStr >= '2.0.1.0' then 'Pass' else 'Fail' end) as "check"
from versions;

Selecting Strings With Alphabetized Characters - In SQL Server 2008 R2

This is a recreational pursuit, and is not homework. If you value academic challenges, please read on.
A radio quiz show had a segment requesting listeners to call in with words that have their characters in alphabetical order, e.g. "aim", "abbot", "celt", "deft", etc. I got these few examples by a quick Notepad++ (NPP) inspection of a Scrabble dictionary word list.
I'm looking for an elegant way in T-SQL to determine if a word qulifies for the list, i.e. all its letters are in alpha order, case insensitive.
It seemed to me that there should be some kind of T-SQL algorithm possible that will do a SELECT on a table of English words and return the complete list of all words in the Srcabble dictionary that meets the spec. I've spent considerable time looking at regex strings, but haven't hit on anything that comes even remotely close. I've thought about the obvious looping scenario, but abandoned it for now as "inelegant". I'm looking for your ideas that will obtain the qualifying word list,
preferably using
- a REGEX expression
- a tally-table-based approach
- a scalar UDF that returns 1 if the input word meets the requirement, else 0.
- Other, only limited by your creativity.
But preferably NOT using
- a looping structure
- a recursive solution
- a CLR solution
Assumptions/observations:
1. A "word" is defined here as two or more characters. My dictionary shows 55 2-character words, of which only 28 qualify.
2. No word will have more than two concecutive characters that are identical. (If you find one, please point it out.)
3. At 21 characters, "electroencephalograms" is the longest word in my Scrabble dictionary
(though why that word is in the Scrabble dictionary escapes me--the board is only a 15-by-15 grid.)
Consider 21 as the upper limit on word length.
4. All words LIKE 'Z%' can be dismissed because all you can create is {'Z','ZZ', ... , 'ZZZ...Z'}.
5. As the dictionary's words' initial character proceedes through the alphabet, fewer words will qualify.
6. As the word lengths get longer, fewer words will qualify.
7. I suspect that there will be less than 0.2% of my dictionary's 60,387 words that will qualify.
For example, I've tried NPP regex searches like "^a[a-z][b-z][b-z][c-z][c-z][d-z][d-z][e-z]" for 9-letter words starting with "a", but the character-by-character alphabetic enforcement is not handled properly. This search will return "abilities" which fails the test with the "i" that follows the "l".
There's several free Scrabble word lists available on the web, but Phil Factor gives a really interesting treatment of T-SQL/Scrabble considerations at https://www.simple-talk.com/sql/t-sql-programming/the-sql-of-scrabble-and-rapping/ which is where I got my word list.
Care to give it a shot?
Split the word into individual characters using a numbers table. Use the numbers as one set of indices. Use ROW_NUMBER to create another set. Compare the two sets of indices to see if they match for every character to see if they match. If they do, the letters in the word are in the alphabetical order.
DECLARE #Word varchar(100) = 'abbot';
WITH indexed AS (
SELECT
Index1 = n.Number,
Index2 = ROW_NUMBER() OVER (ORDER BY x.Letter, n.Number),
x.Letter
FROM
dbo.Numbers AS n
CROSS APPLY
(SELECT SUBSTRING(#Word, n.Number, 1)) AS x (Letter)
WHERE
n.Number BETWEEN 1 AND LEN(#Word)
)
SELECT
Conclusion = CASE COUNT(NULLIF(Index1, Index2))
WHEN 0 THEN 'Alphabetical'
ELSE 'Not alphabetical'
END
FROM
indexed
;
The NULLIF(Index, Index2) expression does the comparison: it returns a NULL if the the arguments are equal, otherwise it returns the value of Index1. If all indices match, all the results will be NULL and COUNT will return 0, which means the order of letters in the word was alphabetical.
I did something similar to Andriy. I created a numbers table with value 1-21. I use it to create one set of data with the individual letters order by the index and the a second set ordered alphabetically. Joined the sets to each other on the letter and numbers. I then count nulls. Anything over 0 means it is not in order.
DECLARE #word VARCHAR(21)
SET #word = 'abbot'
SELECT Count(1)
FROM (SELECT Substring(#word, number, 1) AS Letter,
Row_number() OVER ( ORDER BY number) AS letterNum
FROM numbers
WHERE number <= CONVERT(INT, Len(#word))) a
LEFT OUTER JOIN (SELECT Substring(#word, number, 1) AS letter,
Row_number() OVER ( ORDER BY Substring(#word, number, 1)) AS letterNum
FROM numbers
WHERE number <= CONVERT(INT, Len(#word))) b
ON a.letternum = b.letternum
AND a.letter = b.letter
WHERE b.letter IS NULL
Interesting idea...
Here's my take on it. This returns a list of words that are in order, but you could easily return 1 instead.
DECLARE #WORDS TABLE (VAL VARCHAR(MAX))
INSERT INTO #WORDS (VAL)
VALUES ('AIM'), ('ABBOT'), ('CELT'), ('DAVID')
;WITH CHARS
AS
(
SELECT VAL AS SOURCEWORD, UPPER(VAL) AS EVALWORD, ASCII(LEFT(UPPER(VAL),1)) AS ASCIICODE, RIGHT(VAL,LEN(UPPER(VAL))-1) AS REMAINS, 1 AS ROWID, 1 AS INORDER, LEN(VAL) AS WORDLENGTH
FROM #WORDS
UNION ALL
SELECT SOURCEWORD, REMAINS, ASCII(LEFT(REMAINS,1)), RIGHT(REMAINS,LEN(REMAINS)-1), ROWID+1, INORDER+CASE WHEN ASCII(LEFT(REMAINS,1)) >= ASCIICODE THEN 1 ELSE 0 END AS INORDER, WORDLENGTH
FROM CHARS
WHERE LEN(REMAINS)>=1
),
ONLYINORDER
AS
(
SELECT *
FROM CHARS
WHERE ROWID=WORDLENGTH AND INORDER=WORDLENGTH
)
SELECT SOURCEWORD
FROM ONLYINORDER
Here it is as a UDF:
CREATE FUNCTION dbo.AlphabetSoup (#Word VARCHAR(MAX))
RETURNS BIT
AS
BEGIN
SET #WORD = UPPER(#WORD)
DECLARE #RESULT INT
;WITH CHARS
AS
(
SELECT #WORD AS SOURCEWORD,
#WORD AS EVALWORD,
ASCII(LEFT(#WORD,1)) AS ASCIICODE,
RIGHT(#WORD,LEN(#WORD)-1) AS REMAINS,
1 AS ROWID,
1 AS INORDER,
LEN(#WORD) AS WORDLENGTH
UNION ALL
SELECT SOURCEWORD,
REMAINS,
ASCII(LEFT(REMAINS,1)),
RIGHT(REMAINS,LEN(REMAINS)-1),
ROWID+1,
INORDER+CASE WHEN ASCII(LEFT(REMAINS,1)) >= ASCIICODE THEN 1 ELSE 0 END AS INORDER,
WORDLENGTH
FROM CHARS
WHERE LEN(REMAINS)>=1
),
ONLYINORDER
AS
(
SELECT 1 AS RESULT
FROM CHARS
WHERE ROWID=WORDLENGTH AND INORDER=WORDLENGTH
UNION
SELECT 0
FROM CHARS
WHERE NOT (ROWID=WORDLENGTH AND INORDER=WORDLENGTH)
)
SELECT #RESULT = RESULT FROM ONLYINORDER
RETURN #RESULT
END

How to quickly compare many strings?

In SQL Server, I have a string column that contains numbers. Each entry I need is only one number so no parsing is needed. I need some way to find all rows that contain numbers from 400 to 450. Instead of doing:
...where my stringcolumn like '%400%' or stringcolumn like '%401%' or stringcolumn like '%402%' or ...
is there a better that can save on some typing?
There are also other values in these rows such as: '5335154', test4559#me.com', '555-555-5555'. Filtering those out will need to be taken into account.
...where stringcolumn like '4[0-4][0-9]' OR stringcolumn = '450'
You don't need the wildcard if you want to restrict to 3 digits.
Use regex to accomplish this.
...where stringcolumn like '4[0-4][0-9]' OR stringcolumn like '450'
one way
WHERE Column like '%4[0-4][09]%'
OR Column LIKE '%500%'
keep in mind that this will pick anything with the number in it, so 5000 will be returned as well
I would do the following:
select t.*
from (select t.*,
(case when charindex('4', col) > 0
then substrint(col, charindex('4', col), charindex('4', col) + 2)
end) as col4xx
from t
) t
where (case when isnumeric(col4xx) = 1
then (case when cast(col4xx as int) between 400 and 450 then 'true'
end)
end) = 'true'
I'm not a fan of having case statements in WHERE clauses. However, to ensure conversion to a number, this is needed (or the conversion could become a column in another subquery). Note that the following is not equivalent:
where col4xx between '400' and '450'
Since the string '44A' would match.