Max Value in split XML using SQL Server - sql-server

Anyone able to advice why does my max value return smaller value?
DECLARE #SalesYear as nvarchar(max),
#SalesPeriod as nvarchar(max)
SET #SalesYear = 2020
SET #SalesPeriod = '5,6,7,8,10'
BEGIN
DECLARE #Split char(1)=',',
#X xml
SELECT #X = CONVERT(xml, ' <root> <myvalue>' +
REPLACE(#SalesPeriod,#Split,'</myvalue> <myvalue>') + '</myvalue> </root>')
IF (OBJECT_ID('tempdb..#breakdown') IS NOT NULL)
BEGIN
DROP TABLE #breakdown
END
SELECT T.c.value('.','varchar(20)') breakdown
INTO #breakdown
FROM #X.nodes('/root/myvalue') T(c)
END
SELECT MAX(breakdown)
FROM #breakdown
It returns max value as '8' instead of '10'. Anything wrong with my code?

I would change the type INT instead of varchar as SalesPeriod seems have a numerical values :
SELECT T.c.value('.','INT') AS breakdown INTO #breakdown
FROM #X.nodes('/root/myvalue') T(c)
So, string value comparisons 8 will be higher than 10, You can check :
select case when '8' > '10' then 1 else 0 end
If you change the type (remove quotes), you will see right flagging. So, i would recommend to use appropriate datatype.

Related

TSQL: How insert separator between each character in a string

I have a string like this:
Apple
I want to include a separator after each character so the end result will turn out like this:
A,p,p,l,e
In C#, we have one liner method to achieve the above with Regex.Replace('Apple', ".{1}", "$0,");
I can only think of looping each character with charindex to append the separator but seems a little complicated. Is there any elegant way and simpler way to achieve this?
Thanks HABO for the suggestions. I'm able to generate the result that I want using the code but takes a little bit of time to really understand how the code work.
After some searching, I manage to found one useful article to insert empty spaces between each character and it's easier for me to understand.
I modify the code a little to define and include desire separator instead of fixing it to space as the separator:
DECLARE #pos INT = 2 -- location where we want first space
DECLARE #result VARCHAR(100) = 'Apple'
DECLARE #separator nvarchar(5) = ','
WHILE #pos < LEN(#result)+1
BEGIN
SET #result = STUFF(#result, #pos, 0, #separator);
SET #pos = #pos+2;
END
select #result; -- Output: A,p,p,l,e
Reference
In following SQL scripts, I get each character using SUBSTRING() function using with a number table (basically I used spt_values view here for simplicity) and then I concatenate them via two different methods, you can choose one
If you are using SQL Server 2017, we have a new SQL string aggregation function
First script uses string_agg function
declare #str nvarchar(max) = 'Apple'
SELECT
string_agg( substring(#str,number,1) , ',') Within Group (Order By number)
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
If you are working with a previous version, you can use string concatenation using FOR XML Path and SQL Stuff function as follows
declare #str nvarchar(max) = 'Apple'
; with cte as (
SELECT
number,
substring(#str,number,1) as L
FROM master..spt_values n
WHERE
Type = 'P' and
Number between 1 and len(#str)
)
SELECT
STUFF(
(
SELECT
',' + L
FROM cte
order by number
FOR XML PATH('')
), 1, 1, ''
)
Both solution yields the same result, I hope it helps
If you have SQL Server 2017 and a copy of ngrams8k it's ultra simple:
declare #word varchar(100) = 'apple';
select newString = string_agg(token, ',') within group (order by position)
from dbo.ngrams8k(#word,1);
For pre-2017 systems it's almost as simple:
declare #word varchar(100) = 'apple';
select newstring =
( select token + case len(#word)+1-position when 1 then '' else ',' end
from dbo.ngrams8k(#word,1)
order by position
for xml path(''))
One ugly way to do it is to split the string into characters, ideally using a numbers table, and reassemble it with the desired separator.
A less efficient implementation uses recursion in a CTE to split the characters and insert the separator between pairs of characters as it goes:
declare #Sample as VarChar(20) = 'Apple';
declare #Separator as Char = ',';
with Characters as (
select 1 as Position, Substring( #Sample, 1, 1 ) as Character
union all
select Position + 1,
case when Position & 1 = 1 then #Separator else Substring( #Sample, Position / 2 + 1, 1 ) end
from Characters
where Position < 2 * Len( #Sample ) - 1 )
select Stuff( ( select Character + '' from Characters order by Position for XML Path( '' ) ), 1, 0, '' ) as Result;
You can replace the select Stuff... line with select * from Characters; to see what's going on.
Try this
declare #var varchar(50) ='Apple'
;WITH CTE
AS
(
SELECT
SeqNo = 1,
MyStr = #var,
OpStr = CAST('' AS VARCHAR(50))
UNION ALL
SELECT
SeqNo = SeqNo+1,
MyStr = MyStR,
OpStr = CAST(ISNULL(OpStr,'')+SUBSTRING(MyStR,SeqNo,1)+',' AS VARCHAR(50))
FROM CTE
WHERE SeqNo <= LEN(#var)
)
SELECT
OpStr = LEFT(OpStr,LEN(OpStr)-1)
FROM CTE
WHERE SeqNo = LEN(#Var)+1

Issue with IsNumeric in t-sql?

Below is some test SQL. IsNumeric is saying the substring of 05031, is numeric, but fails when it tries to convert it to an actual number because of the ,. Is this a bug in SQL?
DECLARE #Str NVARCHAR(20)
SET #Str = 'XYZ 505031, some text'
SELECT CASE WHEN ISNUMERIC(SUBSTRING(#Str,6,7)) = 1 THEN CONVERT(int,SUBSTRING(#Str,6,7)) ELSE 0 END, SUBSTRING(#Str,6,7)
you should use try_cast instead, because ISNUMERIC evaluates to 1 for 5031,
ISNUMERIC returns 1 for some characters that are not numbers, such as plus (+), minus (-), and valid currency symbols such as the dollar sign ($). For a complete list of currency symbols, see money and smallmoney (Transact-SQL).
DECLARE #Str NVARCHAR(20)
SET #Str = 'XYZ 505031, some text'
SELECT
CASE
WHEN TRY_CAST(SUBSTRING(#Str,6,7) AS DECIMAL(10,2)) IS NOT NULL
THEN CONVERT(int,SUBSTRING(#Str,6,7))
ELSE 0
END,
SUBSTRING(#Str,6,7)
If 2012+, you can use Try_Convert(). We use MONEY becase the this will convert the leading zero, and then we convert to int
Example
DECLARE #Str NVARCHAR(20)
SET #Str = 'XYZ 505031, some text'
SELECT AsInt = IsNull(try_convert(int,try_convert(money,SUBSTRING(#Str,6,7))),0)
, AsStr = SUBSTRING(#Str,6,7)
Returns
AsInt AsStr
5031 05031,
This is because your substring is also pulling a comma in the end.
SELECT SUBSTRING(#Str, 6, 7);
is resulting:
05031,
Whereas,
SELECT SUBSTRING(#Str, 6, 5);
is resulting:
05031
And
SELECT CASE WHEN ISNUMERIC(SUBSTRING(#Str,6,5)) = 1 THEN CONVERT(int,SUBSTRING(#Str,6,5)) ELSE 0 END, SUBSTRING(#Str,6,5)
is resulting:
5031 | 05031

SQL Server - error converting data type nvarchar to float

I am inserting table A to table B. The problematic column looks like -$25.2. I first replaced the $ and tried insert. Got this error
Error converting data type nvarchar to float.
I then checked by
SELECT *
FROM B
WHERE ISNUMERIC([Col Name]) <> 1
and no results were returned.
This is odd. It is supposed to return something.
What should I check next?
I also tried something like
CAST(REPLACE([Col Name], '-$', '') AS FLOAT)
Try using this
DECLARE #Text nvarchar(100)
SET #Text = '-$1234.567'
SET #Text = Replace(#Text,'$', '')
Select CONVERT(float, #Text) AS ColumnValue
Select ABS(CONVERT(float, #Text)) AS ColumnValue
While the 'money' data type isn't great for doing calculations, in this scenario you can use it as an intermediary.
declare #a nvarchar(10)
set #a = '-$25.2'
select
#a,
cast(cast(#a as money) as float)
Only use this though if your data only goes to a max of 4 decimal places, otherwise you will lose precision in the conversion.

Retrieving numerical values from a Nvarchar column

I have a table with a nvarchar column called Custom#5 which has the following data.
row number Custom#5
1 267.5
2 tbc
3
4 34
I want to be able to clean this data up so always returns a numerical value.
row number Custom#5
1 267.5
2 0
3 0
4 34
My current query is;
SELECT CASE
WHEN BomHeaders_1.Custom#5 NOT LIKE '%[^0-9]%'
THEN 0
WHEN BomHeaders_1.Custom#5 IS NULL
THEN 0
ELSE BomHeaders_1.Custom#5
END AS Custom5
FROM [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_1
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomComponents AS BomComponents_1 ON BomHeaders_1.ID = BomComponents_1.HeaderID
INNER JOIN [FS25-W2K8\SQLEXPRESS].sagel50_46772.dbo.BomHeaders AS BomHeaders_2 ON BomComponents_1.StockCode = BomHeaders_2.BomReference
INNER JOIN manu_STOCK ON BomHeaders_1.BomReference = manu_STOCK.STOCK_CODE
WHERE (BomComponents_1.StockCode LIKE N'21%')
The current error i'm getting with this is
"Conversion failed when converting the nvarchar value '267.5' to data type int."
If you are using 2012 or greater, I would use the tryparse function
select coalesce(TRY_PARSE ( [Custom#5] AS decimal(18,2)),0)
declare #t table (R INT,C varchar(10))
inSERT INTO #t(R,c)values (1,'267.5'),(2,'tbc'),(3,''),(4,'34')
select R,CASE WHEN C LIKE '%[^a-zA-Z]%' THEN C ELSE CAST(0 AS VARCHAR) END from #t
If your using SQL Server < 2012 and have no option in using try_parse, you can use this:
DECLARE #string nvarchar(255)
SET #string = 'Hali891236.5€hHalo'
SELECT Substring(
#string,
PATINDEX('%[0-9.]%',#string),
PATINDEX('%[^0-9.]%',
Substring(
#string,
PATINDEX(
'%[0-9.]%',
#string
),
LEN(#string)
)
)-1
)
GO

How to get MAX value of numeric values in varchar column

I have a table with a nvarchar column. This column has values for example:
983
294
a343
a3546f
and so on.
I would like to take MAX of this values, but not as text but like from numerics. So in this example numerics are:
983
294
343
3546
And the MAX value is the last one - 3546. How to do this in TSQL on Microsoft SQL?
First install a regular expression function. This article has code you can cut/paste.
Then with RegexReplace (from that article) you can extract digits from a string:
dbo.RegexReplace( '.*?(\d+).*', myField, '$1' )
Then convert this string to a number:
CAST( dbo.RegexReplace( '.*?(\d+).*', myField, '$1' ) AS INT )
Then use this expression inside a MAX() function in a SELECT.
You can try to keep it simple without using Regular Expression
Here is the source
create table #t ( val varchar(100) )
insert #t select 983
insert #t select 294
insert #t select 'a343'
insert #t select 'a3546f';
GO
;with ValueRange as (
select val,
[from] = patindex('%[0-9]%', val),
[to] = case patindex('%[a-z]', val)
when 0 then len(val)
else patindex('%[a-z]', val) - patindex('%[0-9]%', val)
end
from #t
)
select substring(val, [from], [to]) as val
from ValueRange VR
order by cast(substring(val, [from], [to]) as int) desc
CAST() would do the trick, probably.
SELECT MAX(CAST(yourColumn AS int)) AS maxColumns FROM yourTable
Edit.
I didn't read the whole question, as it seems...
– Function to strip out non-numeric chars
ALTER FUNCTION dbo.UDF_ParseNumericChars
(
#string VARCHAR(8000)
)
RETURNS VARCHAR(8000)
AS
BEGIN
DECLARE #IncorrectCharLoc SMALLINT
–SET #IncorrectCharLoc = PATINDEX(’%[^0-9A-Za-z]%’, #string)
SET #IncorrectCharLoc = PATINDEX(’%[^0-9.]%’, #string)
WHILE #IncorrectCharLoc > 0
BEGIN
SET #string = STUFF(#string, #IncorrectCharLoc, 1, ”)
SET #IncorrectCharLoc = PATINDEX(’%[^0-9.]%’, #string)
END
SET #string = #string
RETURN #string
END
GO
I picked it from here. (I voted up the reg exp answer though)
you can write a function something like
create FUNCTION [dbo].[getFirstNumeric](
#s VARCHAR(50)
)
RETURNS int AS
BEGIN
set #s = substring(#s,patindex('%[0-9]%',#s),len(#s)-patindex('%[0-9]%',#s) + 1)
if patindex('%[^0-9]%',#s) = 0
return #s
set #s = substring(#s,1,patindex('%[^0-9]%',#s)-1)
return cast(#s as int)
end
and then call
select max(dbo.getFirstNumeric(yourColumn)) from yourTable
if you are using SQL Server 2005 or never you can also use the solution posted by Sung Meister
As far as I know you would need to create a process (or user defined function) to scrub the column, so that you can actually convert it to an INT or other appropriate datatype, then you can take the max of that.
By using user defined function parse the value to an int and then run the select.
SELECT MAX(dbo.parseVarcharToInt(column)) FROM table
SELECT dbo.RegexReplace('[^0-9]', '','a5453b',1, 1)
and RegexReplace installation like Jason Cohen said
This is an old question, I know - but to add to the knowledge base for others...
Assuming all your values have at least 1 number in them:
Select max(convert(int, SubString(VarName, PATINDEX('%[0-9]%',VarName), Len(VarName))))
from ATable
This is my simple answer. You can try it. But it works for fixed removable string value.
select max(cast(SUBSTRING(T.column,3,len(T.column)) as int)) from tablename T

Resources