SQL Server casting from a table - sql-server

In SQL Server I have a query that looks like this (part of the WHERE clause of a larger query)
SELECT 1
WHERE TPR.GRDE_PK IN
(
SELECT CAST(String AS INT)
FROM dbo.Split_New(#GRADES, ',')
)
#Grades is equal to '14,15' and dbo.Split_New is a function that returns a table with a single column called String that will contains '14' and '15'. TPR.GRDE_PK is of type INT. I get a conversion error when I try to execute this line, can anyone tell me how to fix it?
Here is that the Split_New function looks like (Written by someone more skilled than me, so I don't understand all of it):
function [dbo].[Split_New] (
#StringToSplit nvarchar(4000),
#Separator varchar(128))
returns table as return
with indices as
(
select 0 S, 1 E
union all
select E, charindex(#Separator, #StringToSplit, E) + len(#Separator)
from indices
where E > S
)
select substring(#StringToSplit,S,
case when E > len(#Separator) then e-s-len(#Separator) else len(#StringToSplit) - s + 1 end) String
--,S StartIndex
from indices where S >0

The problem is your TPR.GRDE_PK value is an Integer, cast it as a VARCHAR():
SELECT 1
WHERE CAST(TPR.GRDE_PK AS VARCHAR(25)) IN
(
SELECT *
FROM dbo.Split_New(#GRADES, ',')
)
The function works fine, it returns the expected table of results given your string.
Alternatively, you can avoid using the function at all with LIKE:
WHERE ','+CAST(TPR.GRDE_PK AS VARCHAR(25))+',' LIKE '%,'+#GRADES+',%'

It is difficult to say exactly what it is without looking at the function.
First see if you get the correct results from the function:
SELECT String FROM dbo.Split_New(#GRADES, ',')
String may have leading/trailing spaces. Try to trim them before converting/casting using LTRIM() and RTRIM() function
SELECT CONVERT(INT, LTRIM(RTRIM(String))) FROM dbo.Split_New(#GRADES, ',')
ISNUMERIC() function is not ideal to filter and convert as it returns 1 for some characters that are not numbers.

Related

"Create sql function , select english characters?"

I am looking for a function that selects English numbers and letters only:
Example:
TEKA תנור ביל דין in HLB-840 P-WH לבן
I want to run a function and get the following result:
TEKA HLB-840 P-WH
I'm using MS SQL Server 2012
What you really need here is regex replacement, which SQL Server does not support. Broadly speaking, you would want to find [^A-Za-z0-9 -]+\s* and then replace with empty string. Here is a demo showing that this works as expected:
Demo
This would output TEKA in HLB-840 P-WH for the input you provided. You might be able to do this in SQL Server using a regex package or UDF. Or, you could do this replacement outside of SQL using any number of tools which support regex (e.g. C#).
SQL-Server is not the right tool for this.
The following might work for you, but there is no guarantee:
declare #yourString NVARCHAR(MAX)=N'TEKA תנור ביל דין in HLB-840 P-WH לבן';
SELECT REPLACE(REPLACE(REPLACE(REPLACE(CAST(#yourString AS VARCHAR(MAX)),'?',''),' ','|~'),'~|',''),'|~',' ');
The idea in short:
A cast of NVARCHAR to VARCHAR will return all characters in your string, which are not known in the given collation, as question marks. The rest is replacements of question marks and multi-blanks.
If your string can include a questionmark, you can replace it first to a non-used character, which you re-replace at the end.
If you string might include either | or ~ you should use other characters for the replacements of multi-blanks.
You can influence this approach by specifying a specific collation, if some characters pass by...
there is no build in function for such purpose, but you can create your own function, should be something like this:
--create function (split string, and concatenate required)
CREATE FUNCTION dbo.CleanStringZZZ ( #string VARCHAR(100))
RETURNS VARCHAR(100)
BEGIN
DECLARE #B VARCHAR(100) = '';
WITH t --recursive part to create sequence 1,2,3... but will better to use existing table with index
AS
(
SELECT n = 1
UNION ALL
SELECT n = n+1 --
FROM t
WHERE n <= LEN(#string)
)
SELECT #B = #B+SUBSTRING(#string, t.n, 1)
FROM t
WHERE SUBSTRING(#string, t.n, 1) != '?' --this is just an example...
--WHERE ASCII(SUBSTRING(#string, t.n, 1)) BETWEEN 32 AND 127 --you can use something like this
ORDER BY t.n;
RETURN #B;
END;
and then you can use this function in your select statement:
SELECT dbo.CleanStringZZZ('TEKA תנור ביל דין in HLB-840 P-WH לבן');
create function dbo.AlphaNumericOnly(#string varchar(max))
returns varchar(max)
begin
While PatIndex('%[^a-z0-9]%', #string) > 0
Set #string = Stuff(#string, PatIndex('%[^a-z0-9]%', #string), 1, '')
return #string
end

need patindex to match a-zA-Z0-9./-#' and space

I am cleaning up some data and would like to create a patindex that would reject any string contains any character(s) except for A-Za-z0-9./'-# and a space.
This rejects the special chars which should be allowed:
patindex ( '%[^A-Z0-9a-z./'-# ]%',stringtobetested )
Should I be masking the special chars? The bad and/or good chars can appear multiple times in a given string.
So where stringtobetested is abc#D-EF should pass but abc*def should fail.
This should work... it just uses replace to get around your escaping problems.
declare #stringtobetested1 varchar(64) = 'abc#D-EF'
declare #stringtobetested2 varchar(64) = 'abc*def '
select
#stringtobetested1 string1
,replace(replace(replace(replace(#stringtobetested1,'''','#'),' ','#'),'/','#'),'.','#') string1changed
,#stringtobetested2 string2
,replace(replace(replace(replace(#stringtobetested2,'''','#'),' ','#'),'/','#'),'.','#') string2changed
,patindex('%[^A-Z0-9a-z#-]%',replace(replace(replace(replace(#stringtobetested1,'''','#'),' ','#'),'/','#'),'.','#'))
,patindex('%[^A-Z0-9a-z#-]%',replace(replace(replace(replace(#stringtobetested2,'''','#'),' ','#'),'/','#'),'.','#'))
This can all be done with a PATINDEX, you just need some syntax help:
WITH test AS (
SELECT val
FROM (VALUES ('abc#D-EF./ -'''), ('abc*def')) AS t (val)
)
SELECT
input = t.val,
result = IIF(PATINDEX('%[^A-Z0-9a-z./''# -]%', t.val) > 0, 'fails', 'passes')
FROM test t;
First of all, the pattern itself is a string, and strings in T-SQL escape ' by doubling it to ''. Secondly, inside a [^ ] wildcard in a pattern, the - is used to define a character range when it occurs between two characters. By moving it to an end of the wildcard pattern, it is treated literally.
Other escape sequences specific to pattern wildcards can be found in this docs page: Pattern Matching in Search Conditions
Please run more testing for my query, and adjust max string length to fit your requirement.
DECLARE #TestString varchar(64) = 'abc#D-E/*F'
, #MaxStringLen INT = 20
;WITH cte AS(SELECT 1 number
UNION ALL
SELECT number + 1
FROM cte
WHERE number < #MaxStringLen
)
SELECT #TestString AS OriginalString
, CAST(CAST((SELECT SUBSTRING(#TestString, Number, 1)
FROM cte
WHERE Number <= LEN(#TestString) AND
SUBSTRING(#TestString, Number, 1) LIKE '%[A-Z0-9a-z-# ./'']%' FOR XML Path(''))
AS xml) AS varchar(MAX)) AS ConvertedString
, CASE WHEN #TestString = CAST(CAST((SELECT SUBSTRING(#TestString, Number, 1)
FROM cte
WHERE Number <= LEN(#TestString) AND
SUBSTRING(#TestString, Number, 1) LIKE '%[A-Z0-9a-z-# ./'']%' FOR XML Path(''))
AS xml) AS varchar(MAX))
THEN 1 ELSE 0 END IsAllowed

SQL: Filter on Alpha and numeric on column of Type TEXT

I've got a column of type Text. In the column are numeric values such as4, 8, 3.2, etc... and also values such as 'Negative', 'Positive', '27A', '2pos 1neg'.
The user needs to be able to say: "Give me all the values between 10 and 30, and also the values that are 'Negative'. The WHERE clause would need to do something along the lines of:
WHERE Tbl.Col > 10
AND Tbl.Col < 30
AND Tbl.Col = 'Negative'
This is problematic for obvious reasons. I've tried using the ISNUMERIC function to alleviate the issue but can't seem to get exactly what i need. I can either get all the alpha values in the column, or all the numeric values in the column as floats but cant seem to filter on both at the same time. To grab all the Numeric values I've been using this:
SELECT Num.Val FROM
(SELECT Val = CASE ISNUMERIC(CAST(TBL.COL AS VARCHAR)) WHEN 1
THEN CAST(CAST(TBL.COL AS VARCHAR) AS FLOAT) ELSE NULL END
FROM Table TBL
WHERE TBL.COL IS NOT NULL ) as Num
WHERE Num.val IS NOT NULL
AND Num.val > 10
If I understand the issue correctly something like this should get you close.
with MyNumbers as
(
select t.Col
from Tbl t
--where ISNUMERIC(t.Col) = 1
where t.Col NOT LIKE '%[^0-9.]%'
)
, MyAlpha as
(
select t.Col
from Tbl t
where ISNUMERIC(t.Col) = 0
)
select Col
from MyNumbers
where Col > 10
and Col < 30
union all
select Col
from MyAlpha
where ColorMatch = ' Negative'
First I would go slap the person who designed the table (hopefully it isn't you) :>
Go to here and get the split table function. I would then convert the text column (like you have in example above) into varchar(max) and supply it as the parameter to the split function. Then you could select from the table results of the split function using the user supplied parameters.
I have found the answer to my problem:
SELECT
al_Value = Table.Column
FROM Table
WHERE (
ISNUMERIC(CAST(Table.Column AS VARCHAR)) = 1 AND
CONVERT(FLOAT, CAST(Table.Column AS VARCHAR)) > 1.0 AND
CONVERT(FLOAT, CAST(Table.Column AS VARCHAR)) < 10.0
)
OR (
CAST(Table.Column AS VARCHAR) IN ('negative', 'no bueno')
)
This will return one column named 'al_Value' that filters on Table.Column (which is of Datatype TEXT) and apply the filters in the WHERE clause above.
Thanks everyone for trying to help me with this issue.

TSQL - A Better INT Conversion Function

I'm wondering if there is a better way to 'parse' a Varchar to an Int in TSQL / SQL Server. I say 'parse' because I need something more robust than the CAST/CONVERT system funcs; it's particularly useful to return NULL when the parse fails, or even a 'default' value.
So here's the function I'm using now, originally obtained from someone's SQL blog (can't even remember specifically who)...
ALTER FUNCTION [dbo].[udf_ToNumber]
(
#Str varchar(max)
)
RETURNS int
AS
BEGIN
DECLARE #Result int
SET #Str = LTRIM(RTRIM(#Str))
IF (#Str='' OR #Str IS NULL
OR ISNUMERIC(#Str)=0
OR #Str LIKE '%[^-+ 0-9]%'
OR #Str IN ('.', '-', '+', '^')
)
SET #Result = NULL
ELSE
IF (CAST(#Str AS NUMERIC(38,0)) NOT BETWEEN -2147483648. AND 2147483647.)
SET #Result = NULL
ELSE
SET #Result = CAST(#Str AS int)
RETURN #Result
END
(And you could add a line before the end, like "if #Result is null, set #Result = ", or something like that).
It's not very efficient, because using it in a JOIN or WHERE-IN-SELECT -- where say the LEFT column is INT and the RIGHT is VARCHAR, and I try to parse the RIGHT -- on any significantly large data-set, takes a lot longer than if I CAST the LEFT (INT) column to a VARCHAR first and then do the JOIN.
Anyway, I know 'ideally' that I shouldn't need to do this kind of thing in the first place if my tables/data-types are created & populated appropriately, but we all know the ideal world is very far from reality sometimes, so humor me. Thanks!
EDIT: SQL Server versions 2005 & 2008; boxes running 2005 will be upgraded soon so 2008-specific answers are fine.
In my experience, scalar udf's don't perform well on larger data sets; as a workaround you can try one of two options (and I'm not sure either of them will work particularly well):
Embed the logic of the function in the join itself, like so:
SELECT columnlist
FROM a JOIN b ON a.INT = (SELECT CASE WHEN ( b.varchar= ''
OR b.varchar IS NULL
OR ISNUMERIC(b.varchar) = 0
OR b.varchar LIKE '%[^-+ 0-9]%'
OR b.varchar IN ( '.', '-', '+', '^' )
) THEN NULL
WHEN CAST(b.varchar AS NUMERIC(38, 0)) NOT BETWEEN -2147483648.
AND 2147483647.
THEN NULL
ELSE CAST (b.varchar AS INT)
END)
Change your user-defined function to be a inline table-valued function and use the CROSS APPLY syntax:
CREATE FUNCTION udf_ToInt
(
#str VARCHAR(MAX)
)
RETURNS TABLE
AS
RETURN
(
SELECT CASE WHEN ( #Str = ''
OR #Str IS NULL
OR ISNUMERIC(#Str) = 0
OR #Str LIKE '%[^-+ 0-9]%'
OR #Str IN ( '.', '-', '+', '^' )
) THEN NULL
WHEN CAST(#Str AS NUMERIC(38, 0)) NOT BETWEEN -2147483648.
AND 2147483647.
THEN NULL
ELSE CAST (#Str AS INT) as IntVal
END
)
GO
SELECT columnlist
FROM b
CROSS APPLY udf_ToInt(b.varchar) t
JOIN a ON t.IntVal = a.Int
Probably easier to just convert to VARCHAR and compare :)

How do I extract part of a string in t-sql

If I have the following nvarchar variable - BTA200, how can I extract just the BTA from it?
Also, if I have varying lengths such as BTA50, BTA030, how can I extract just the numeric part?
I would recommend a combination of PatIndex and Left. Carefully constructed, you can write a query that always works, no matter what your data looks like.
Ex:
Declare #Temp Table(Data VarChar(20))
Insert Into #Temp Values('BTA200')
Insert Into #Temp Values('BTA50')
Insert Into #Temp Values('BTA030')
Insert Into #Temp Values('BTA')
Insert Into #Temp Values('123')
Insert Into #Temp Values('X999')
Select Data, Left(Data, PatIndex('%[0-9]%', Data + '1') - 1)
From #Temp
PatIndex will look for the first character that falls in the range of 0-9, and return it's character position, which you can use with the LEFT function to extract the correct data. Note that PatIndex is actually using Data + '1'. This protects us from data where there are no numbers found. If there are no numbers, PatIndex would return 0. In this case, the LEFT function would error because we are using Left(Data, PatIndex - 1). When PatIndex returns 0, we would end up with Left(Data, -1) which returns an error.
There are still ways this can fail. For a full explanation, I encourage you to read:
Extracting numbers with SQL Server
That article shows how to get numbers out of a string. In your case, you want to get alpha characters instead. However, the process is similar enough that you can probably learn something useful out of it.
substring(field, 1,3) will work on your examples.
select substring(field, 1,3) from table
Also, if the alphabetic part is of variable length, you can do this to extract the alphabetic part:
select substring(field, 1, PATINDEX('%[1234567890]%', field) -1)
from table
where PATINDEX('%[1234567890]%', field) > 0
LEFT ('BTA200', 3) will work for the examples you have given, as in :
SELECT LEFT(MyField, 3)
FROM MyTable
To extract the numeric part, you can use this code
SELECT RIGHT(MyField, LEN(MyField) - 3)
FROM MyTable
WHERE MyField LIKE 'BTA%'
--Only have this test if your data does not always start with BTA.
declare #data as varchar(50)
set #data='ciao335'
--get text
Select Left(#Data, PatIndex('%[0-9]%', #Data + '1') - 1) ---->>ciao
--get numeric
Select right(#Data, len(#data) - (PatIndex('%[0-9]%', #Data )-1) ) ---->>335

Resources