Order by string returns wrong order - sql-server

I am using a column which is named ItemCode. ItemCode is Varchar(50) type.
Here is my query
Select * from Inventory order by ItemCode
So, now my result is looks like
ItemCode-1
ItemCode-10
ItemCode-2
ItemCode-20
And so on.
How can I order my string as the example below?
ItemCode-1
ItemCode-2
ItemCode-10
ItemCode-20
Should I convert my column as number? Also I mention that I have some fields that contain no number.

You could order by the numbers as
SELECT Str
FROM
(
VALUES
('ItemCode-1'),
('ItemCode-10'),
('ItemCode-2'),
('ItemCode-20')
) T(Str)
ORDER BY CAST(RIGHT(Str, LEN(Str) - CHARINDEX('-', Str)) AS INT)
Note: Since you tagged your Q with SQL Server 2008 tag, you should upgrade as soon as possible because it's out of support.
UPDATE:
Since you don't provide a good sample data, I'm just guessing.
Here is another way may feet your requirements
SELECT Str
FROM
(
VALUES
('ItemCode-1'),
('ItemCode-10'),
('ItemCode-2'),
('ItemCode-20'),
('Item-Code')
) T(Str)
ORDER BY CASE WHEN Str LIKE '%[0-9]' THEN CAST(RIGHT(Str, LEN(Str) - CHARINDEX('-', Str)) AS INT) ELSE 0 END

This is an expected behavior, based on this:
that is lexicographic sorting which means basically the language
treats the variables as strings and compares character by character
You need to use something like this:
ORDER BY
CASE WHEN ItemCode like '%[0-9]%'
THEN Replicate('0', 100 - Len(ItemCode)) + ItemCode
ELSE ItemCode END

Related

String Manipulation in SQL Server (STRING_SPLIT, REVERSE)

I want to reverse the sub-strings in NVARCHAR column, that are separated by one character like '-', for example:
Input column cl:
a1-b1-c1-d1
Required output:
d1-c1-b1-a1
I tried REVERSE(cl), the result was 1d-1c-1b-1a!
The best approach I think is using:
STRING_SPLIT(cl,'-')
And then looking for reversing the resulted sub strings and rejoining them, but since we don't know how many delimited sub strings, it is still difficult to handle.
How can we achieve this?
Thank you in advance
REVERSE isn't what you are after here. What you are after is a string splitter that supports (is aware of) ordinal positions; STRING_SPLIT is documented that it explicitly "doesn't care" about the ordinal positions of values in a delimited string.
One function that is aware of ordinal positions is DelimitedSplit8k_LEAD. You can then use that, along with STRING_AGG to recreate your delimited string:
SELECT STRING_AGG(DS.item,'-') WITHIN GROUP (ORDER BY DS.ItemNumber DESC) AS R
FROM (VALUES('a1-b1-c1-d1'))V(S)
CROSS APPLY dbo.DelimitedSplit8K_LEAD(V.S,'-') DS;
Of course, the real solution here is to stop storing delimited data in your database in the first place.
If using an UDF is not an option, you may try a JSON-based approach. You need to transform the input values into a valid JSON array (a1-b1-c1-d1 into ["a1", "b1","c1","d1"]) and parse this array with OPENJSON():
CREATE TABLE Data (ColumnData nvarchar(max))
INSERT INTo Data (ColumnData) VALUES (N'a1-b1-c1-d1')
UPDATE Data
SET ColumnData = (
SELECT STRING_AGG([value], N'-') WITHIN GROUP (ORDER BY CONVERT(int, [key]) DESC)
FROM OPENJSON(CONCAT(N'["', REPLACE(STRING_ESCAPE(ColumnData, 'json'), N'-', '","'), '"]'))
)
Result:
ColumnData
-----------
d1-c1-b1-a1
If you have a modern version of SQL Server (2016 or higher), you can do it as you said: you split the string with string_split, you reverse its order, and aggregate the result with string_agg.
with cte as (
select value,
row_number() over (order by (select null)) as number
from string_split('a1-b1-c1-d1', '-')
)
select string_agg(value, '-') within group (order by number desc)
from cte
This splits the string while preserving the ordinal position without using a custom splitter. To preserve the ordering of words the CHARINDEX function is used as per this.
declare
#string nvarchar(4000)='a1-b1-c1-d1',
#added_delimitter CHAR(1)= '-';
;with ndx_split_cte(split_val, split_ndx) as (
select
sp.[value],
CHARINDEX(#added_delimitter + sp.[value] + #added_delimitter, #added_delimitter + #string + #added_delimitter)
from
string_split(#string, '-') sp)
select string_agg(split_val, '-') within group (order by split_ndx desc) rev_split_str
from ndx_split_cte;
Results
rev_split_str
d1-c1-b1-a1

!IsNullOrEmpty equivalent in SQL server

I'm creating an SSRS report and during writing the query I look up the code logic for the data that needs to be retrieved in query. There is lots of usage of !String.IsNullOrEmpty method so I want to know what is the shortest and best way to do the equivalent check in SQL server?
WHERE t.Name IS NOT NULL OR T.Name != ''
or....
WHERE LEN(t.Name) > 0
which one is correct? Or is there any other alternative?
There is no built-in equivalent of IsNullOrEmpty() in T-SQL, largely due to the fact that trailing blanks are ignored when comparing strings. One of your options:
where len(t.Name) > 0
would be enough as len() ignores trailing spaces too. Unfortunately it can make the expression non-SARGable, and any index on the Name column might not be used. However, this one should do the trick:
where t.Name > ''
P.S. For the sake of completeness, the datalength() function takes all characters into account; keep in mind however that it returns the number of bytes, not characters, so for any nvarchar value the result will be at least double of what you might expect (and with supplementary characters / surrogate pairs the number should be even higher, if my memory serves).
If the desired result is the simplest possible one-liner then:
WHERE NULLIF(Name, '') IS NOT NULL
However, from a performance point of view following alternative is SARGable, therefore indexes potentially can be used to spot and filter out records with such values
WHERE Name IS NOT NULL AND Name != ''
An example:
;WITH cte AS (
SELECT 1 AS ID, '' AS Name UNION ALL
SELECT 2, ' ' UNION ALL
SELECT 3, NULL UNION ALL
SELECT 4, 'abc'
)
SELECT * FROM cte
WHERE Name IS NOT NULL AND Name != ''
Results to:
ID Name
---------
4 abc
Yes, you can use WHERE LEN(t.Name)>0.
You can also verify as below:
-- Count the Total number of records
SELECT COUNT(1) FROM tblName as t
-- Count the Total number of 'NULL' or 'Blank' records
SELECT COUNT(1) FROM tblName as t WHERE ISNULL(t.Name,'')= ''
-- Count the Total number of 'NOT NULL' records
SELECT COUNT(1) FROM tblName as t WHERE LEN(t.Name)>0
Thanks.

T SQL - splitting column into two columns after first '-' [duplicate]

This question already has answers here:
T-SQL split string based on delimiter
(9 answers)
Closed 6 years ago.
Got dbo where i need to split it into two, after first '-' character. Working on SSMS 2014
example in spreadsheet:
example
PartNumber holds data which needs to be break up.
Part - Need to have all characters before first '-'
Number - need to have all characters after first '-'
Any help appreciated
thanks
You need LEFT and RIGHT. And to find the location where you want to split to LEFT and RIGHT, us CHARINDEX.
Maybe something like this?
SELECT parts.PartID as ID,
Part = (SELECT TOP 1 value FROM STRING_SPLIT(parts.PartNumber, '-')),
Number = (SELECT value FROM STRING_SPLIT(parts.PartNumber, '-') LIMIT 1 OFFSET 1),
FROM dbo.PartsTable parts
You could try this.
SELECT
PartNum
, REPLACE(LEFT(PartNum, CHARINDEX('-', PartNum)),'-', '') as 'PartNum First'
, REPLACE(SUBSTRING(PartNum,CHARINDEX('-', PartNum), LEN(PartNum)), '-','') as 'PartNum Second'
FROM Part
The query above splits the PartNum string when it finds '-', it then replaces it with a blank space so you have the result you expected.
I tried it and it works. Hope it's useful to you.
Declare #YourTable table (PartNumber varchar(50))
Insert Into #YourTable values
('HHY-12-1800-2'),
('FC-P-41-4')
Select PartNumber
,Part = Left(PartNumber,CharIndex('-',PartNumber)-1)
,Number = Substring(PartNumber,CharIndex('-',PartNumber)+1,Len(PartNumber))
From #YourTable
Returns
PartNumber Part Number
HHY-12-1800-2 HHY 12-1800-2
FC-P-41-4 FC P-41-4

SQL Server ORDER BY Multiple values in case statement

I am trying to do an order by with several different columns. The first column has several conditions.
The base order was something like
SELECT *
FROM Table T
ORDER BY T.A, T.B, T.C
Most of the time column A is an int. Sometimes A is an int with a letter appended at the end. I want the order by to be my the number portion. I was able to achieve that by modifying the query to the following which has been working for months now.
SELECT *
FROM Table T
ORDER BY
CASE WHEN ISNUMERIC(a.[HUDLine]) = 0 THEN CAST(SUBSTRING(T.A,1, PATINDEX('%[^0-9]%',T.A - 1) AS INT)
ELSE CAST (T.A AS INT) end
, T.B, T.C
Recently a new requirement came up which allows the value of "OFFLINE" to exist in column A.
I want to modify the ORDER BY to keep the same logic as before with the exception of all records with "OFFLINE" are at the end.
First, I am very sorry about your requirements, it makes absolutely no sense that bizzarely mixed values are being stored in a varchar column.
Second try this
CASE WHEN a.[HUDLine] = 'OFFLINE'
THEN 2147483647 --This is the maximum signed int value
ELSE
(CASE WHEN ISNUMERIC(a.[HUDLine]) = 0
THEN CAST(SUBSTRING(T.A,1, PATINDEX('%[^0-9]%',T.A - 1) AS INT)
ELSE CAST (T.A AS INT)
END)
END
Show that to whoever is in charge of your datamodel, and politely ask them store meanigful numeric data in a numeric column.

ORDER BY not putting SELECT statement in numerical order

I am working on a SELECT statement.
USE SCRUMAPI2
DECLARE #userParam VARCHAR(100)
,#statusParam VARCHAR(100)
SET #userParam = '%'
SET #statusParam = '%'
SELECT ROW_NUMBER() OVER (
ORDER BY PDT.[Name] DESC
) AS 'RowNumber'
,PDT.[Name] AS Project
,(
CASE WHEN (
STY.KanBanProductId IS NOT NULL
AND STY.SprintId IS NULL
) THEN 'KanBan' WHEN (
STY.KanBanProductId IS NULL
AND STY.SprintId IS NOT NULL
) THEN 'Sprint' END
) AS ProjectType
,STY.[Number] StoryNumber
,STY.Title AS StoryTitle
,TSK.[Name] AS Task
,CONVERT(VARCHAR(20), STY.Effort) AS Effort
,CONVERT(VARCHAR(20), TSK.OriginalEstimateHours) AS OriginalEstimateHours
,TSK.STATUS AS STATUS
FROM Task TSK
LEFT JOIN Story STY ON TSK.StoryId = STY.PK_Story
LEFT JOIN Sprint SPT ON STY.SprintId = SPT.PK_Sprint
LEFT JOIN Product PDT ON STY.ProductId = PDT.PK_Product
WHERE TSK.PointPerson LIKE #userParam
AND TSK.STATUS LIKE #statusParam
GROUP BY STY.[Number]
,TSK.STATUS
,STY.Title
,PDT.[Name]
,TSK.CreateDate
,TSK.[Name]
,STY.KanBanProductId
,STY.SprintId
,TSK.OriginalEstimateHours
,STY.Effort
My issue that that although I have the ORDER BY sorting by story number first it is not returning as expected (below is column STY.[Number]):
As you can see it foes from 33 to 4 to 42, I want it in numerical order so that 4 would be between 3 and 5 not 33 and 42. How do I achieve this?
Given the structure of your data (with a constant prefix), probably the easiest way to get what you want is:
order by len(STY.[Number]), STY.[Number]
This orders first by the length and then by the number itself.
Those are strings. Do you really expect SQL Server to be able to identify that there is a number at character 6 in every single row in the result, and instead of ordering by character 6, they pretend that, say, SUPP-5 is actually SUPP-05? If that worked for you, people who expect the opposite behavior (to treat the whole string as a string) would be complaining. The real fix is to store this information in two separate columns, since it is clearly two separate pieces of data.
In the meantime, you can hack something, like:
ORDER BY LEFT(col, 4), CONVERT(INT, SUBSTRING(col, 6, 255)));
As Martin explained, this should be on the outer query, not just used to generate a ROW_NUMBER() - generating a row number alone doesn't guarantee the results will be ordered by that value. And this will only work with additional checks to ensure that every single row has a value following the dash that can be converted to an int. As soon as you have SUPP-5X this will break.
It's sorting by the string in lexicography order. To get numerical ordering you need to extract the number from the string (with substring()) and cast it to integer.

Resources