I have a function that I need to create that reads in the value of a column in a table and output a text value into a new column in the same table. The column in question (confidence_score) will have either a numeric value, a letter, or null value.
If confidence_score(nvarchar(2)) is a number and is less than or equal to 14, I need the computed column to have 'High' in it, otherwise 'Low'. If confidence_score is not a number and has a value of 'H', I need the computed column to have 'High' in it, otherwise 'Low'.
Here is the code I am using:
CREATE FUNCTION dbo.avm_confidence_level(#score nvarchar)
RETURNS nvarchar(5) as
BEGIN
DECLARE #conversion as nvarchar(5)
IF isnumeric(#score) = 1 BEGIN
IF cast(#score as int) <= 14 BEGIN
Set #conversion = 'High'
END
ELSE BEGIN
Set #conversion = 'Low'
END
END
ELSE BEGIN
IF #score = 'H' BEGIN
Set #conversion = 'High'
END
ELSE BEGIN
Set #conversion = 'Low'
END
END
RETURN #conversion
END
I am getting results using this code that are half right. The first check (isnumeric) seems to be working fine. I am getting High and Low values where I'm expecting them when the value is not a number. The issue seems to be within the isnumeric being true section. I am getting a value of 'High' regardless of what numeric value is actually in the confidence_score column. I'm not sure what I'm doing wrong here.
I appreciate any help anyone can offer.
Thank you.
It's because you defined the function parameter as nvarchar instead of nvarchar(2). It's truncating anything over one character, so all your ints above 9 are becoming 1. Try changing the definition to:
CREATE FUNCTION dbo.avm_confidence_level(#score nvarchar(2))
Related
I have a table with a bigint column that I'm attempting to get an insert working for. We've been having issues where data that can't be converted to a numeric comes in and the insert fails. This is mostly things like spaces or line returns in the data i.e. " 123", "1 365".
Since I don't have access to the software that is attempting to insert this bad data, I thought that creating an instead of trigger and using a function to strip out all non-numeric characters would fix the issue.
This is a basic idea of what the trigger is doing.
TRIGGER [dbo].[Delivery_Before_TRG]
ON [dbo].[Delivery]
instead of INSERT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[Delivery]
(....,[pin],....)
select ....
,[dbo].[udf_GetNumericOnly](inserted.pin)
,....
from inserted;
END
And this is the udf_GetNumberOnly function.
FUNCTION [dbo].[udf_GetNumericOnly]
(
#Value varchar(500)
)
RETURNS bigint
AS
BEGIN
Declare
#Pos tinyint,
#Char char(1)
Set #Value = REPLACE(#Value, ' ', '') -- Strip all spaces
Set #Pos = LEN(#Value) -- Give some non-zero value
While #Pos > 0
Begin
Set #Pos = PATINDEX('%[^0-9]%', #Value) )
If #Pos > 0
Begin
Set #Char = SUBSTRING(#Value, #Pos, 1) -- Non numeric character
Set #Value = REPLACE(#Value, #Char, '')
End
End
Set #Value = RTrim(LTrim(#Value))
Return convert(bigint,#Value)
END
I can run the function and it will strip all non-numeric characters for anything that I pass it, however, when I attempt to run an insert statement into my table I get a Msg 8114, Level 16, State 5, Line 4
Error converting data type varchar to bigint. error.
From what I can tell the problem is something to do with sql server checking that the fields I'm attempting to to insert match the destination table column datatypes before my trigger gets a hold of the data to convert it. I know this because I had modified the trigger to insert a number directly into the pin field and would still get this error.
Additionally, I know it isn't the function failing because I can write an insert that will fail and then change that insert to call the function, and it will work.
--This fails
INSERT INTO (....,pin,...)
VALUES(....,'1a23',....)
--This works
INSERT INTO (....,pin,...)
VALUES(....,udf_GetNumericOnly('1a23'),....)
Yeah, the algebrizer looks at the data types in your query right after the parser makes sure you've written valid sql. The INSTEAD OF INSERT trigger fires for each row that would be inserted, which an invalid cast wouldn't be.
I have a DateSurragate Key column and i need a end of week column for that DateSurragate Key ,,,
I created a user defined function
CREATE FUNCTION dbo.fn (#dt date)
RETURNS date
AS
BEGIN
DECLARE #result date
select #result = dateadd(week, datediff(week, 0, #dt), 4)
RETURN #result;
END;
GO
This function works with date but when values like -1 and -2 are passed it gives NULL
PLEASE let me know how i can change this function so that it can handle integer values I am new to tsql
If my understanding is not wrong(to determine whether input value is valid date or not?)
USE ISDATE(expression) function:
EX:
DECLARE #datestring varchar(8)
SET #datestring = '12/21/98'
SELECT ISDATE(#datestring)
Similarly check for your input parameter within Function in the beginning.
Raise error or change the flow in func if ISDATE() return 0 in your case.
Situation:
I often have (no comments on database, it is history :)) columns where values are separated.
So let's say we have a column params which contains for example 1,2,3,4,5.
If I need the values table based I split them with a dbo.fn_Split() function (several version can be found on the net - but out of scope here). So I get my virtual table.
Today I ran into a situation where I have a column where I have this content 110&mode=tree.
As I needed the ID only I said to myself... like usual, split and get the first value only:
SELECT TOP 1 [value] FROM dbo._fnSplit('110&mode=tree','&')
As I needed the value as int I added a CAST() around it.
CAST((SELECT TOP 1 [value] FROM dbo._fnSplit('110&mode=tree','&')) AS int)
Now after execution I got:
mode=tree cannot be converted to a type int
Huch? I get only the first value which is an Id (I double checked the results) but it still try to convert the 2nd value as well. So it seems that SQL Server handles it differently in execution.
Question:
So is it generally possible to control how the execution plans handle this behaviour?
Sidenote:
I solved it for now doing this:
[...] WHERE id = CAST((CASE WHEN k.param LIKE '%&%' THEN LEFT(k.swindowparam,PATINDEX('%&%',k.param)-1) ELSE k.swindowparam END) AS int))
Check if string contains & and if so so a LEFT with length found out by PATINDEX.
EDIT:
Here is the split function:
CREATE FUNCTION dbo.fn_Split(#text nvarchar(4000), #delimiter char(1) = ',')
RETURNS #Strings TABLE
(
position int IDENTITY PRIMARY KEY,
value nvarchar(4000)
)
AS
BEGIN
DECLARE #index int
SET #index = -1
SET #text = RTRIM(LTRIM(#text))
WHILE (LEN(#text) > 0)
BEGIN
SET #index = CHARINDEX(#delimiter , #text)
IF (#index = 0) AND (LEN(#text) > 0)
BEGIN
INSERT INTO #Strings VALUES (#text)
BREAK
END
IF (#index > 1)
BEGIN
INSERT INTO #Strings VALUES (LEFT(#text, #index - 1))
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
ELSE
SET #text = RIGHT(#text, (LEN(#text) - #index))
END
RETURN
END
It sounds like your split function is returning the data in an unspecified order. To solve the problem I would change that function so it returns the results in the expect order every time.
Depending on how the function is implemented you may be able to correct this behavior by adding an ORDER BY statement. SQL Server doesn't guarantee that a set will be returned in any specific order by default.
I would like to know what the reasoning could be for inconsistent way sql server handles type overflowing. And what would be the proper way to prevent the silent data corruption SQL Server inflicts on us.
The well behaved int
An int for example gives a proper overflow exception
declare #b int = 123456789000
Msg 8115, Level 16, State 2, Line 3
Arithmetic overflow error converting expression to data type int.
The Silent bit
A bit value will be zero for zero and one for everything else you try to put in (with a little extra fun on an empty string).
declare #a bit = 123
, #b bit = ''
select a = #a, b = #b
output:
a b
----- -----
1 0
create table #bit_type(a bit)
insert into #bit_type values (123), ('')
select * from #bit_type
output:
a
-
1
0
declare #bit_type table (a bit)
insert into #bit_type values (123), ('')
select * from #bit_type
output:
a
-
1
0
This behavior is often the cause of very hard to debug ETL problems (invalid value in an input file is silently converted to a 1 or a zero)
The schizophrenic varchar
Varchar (and char/nchar/nvarchar) is another highly annoying datatype
declare #c varchar(5)
select #c = '123456789'
print #c
The result is silently truncated.
12345
create table #varchar_type(a varchar(5))
insert into #varchar_type values ('123456789')
Here we get a proper overflow error.
Msg 8152, Level 16, State 14, Line 1
String or binary data would be truncated.
The statement has been terminated.
declare #varchar_type table(a varchar(5))
insert into #varchar_type values ('123456789')
Here we get a proper overflow error as well.
Msg 8152, Level 16, State 14, Line 2
String or binary data would be truncated.
The statement has been terminated.
BIT
SQL is assuming that ANY numeric value is 'TRUE', while an empty string or 0 is 'FALSE'.
The best way to handle this is through filtering in your code, i.e.:
DECLARE #b BIT
DECLARE #v char(4) = '1234'
SET #b = (CASE WHEN #v = '1' THEN 1 ELSE 0 END)
SELECT #b
Varchar
SQL doesn't consider #variables as mission-critical as tables. The overflow error on tables can be disabled as well by using the SET ANSI WARNINGS OFF command.
I think the designers also assumed that if you are declaring a variable with a length, you will handle checking the length of that variable yourself. Other reasons could include:
You are setting the length of the variable (it's not automatic for the most part)
You are specifying the input
Ergo, you should know if your input will possibly overflow, and you will check for it if necessary
This is different for a string than an int since a string can still be useful when truncated, while truncation fundamentally changes the nature and value of a number.
The recommended procedure is similar to that for bit:
DECLARE #v varchar(10)
DECLARE #str varchar(25) = 'This is a longer string'
IF LEN(#str) <= 10
SET #v = #Str
ELSE RAISERROR('Invalid string length!', 0, 1) WITH NOWAIT
Getting very annoyed with this simple query...
I need to add an offset to a varchar, if it's a number and do nothing is it is not.
For this reason I've created the following function in SQL-server.
I then extract the answer with:
select dbo.OffsetKPL("100",200)
However this does not work, I get the error
Msg 207, Level 16, State 1, Line 1
Invalid column name '100'.
The code for the function is as follows...
ALTER FUNCTION [dbo].[OffsetKPL](
#kpl varchar(20)
,#offset int = 0
)
RETURNS varchar(20)
AS
BEGIN
DECLARE #uitkomst varchar(20);
set #uitkomst = #kpl;
if not(#offset = 0) begin
if (IsNumeric(#uitkomst) = 1) begin
set #uitkomst = cast((cast(#kpl as int) + #offset) as varchar);
end;
end;
RETURN #uitkomst;
END
What's wrong? nowhere does it state that IsNumeric does not accept a variable.
Use single quotes for strings!
select dbo.OffsetKPL('100',200)
If you have QUOTED_IDENTIFIER on (the default) things in double quotes are expected to be object names.
isnumeric may not be what you need though as all kinds of unexpected things return 1 for this.
SELECT ISNUMERIC('$'), ISNUMERIC('.'),
ISNUMERIC('12d5'), ISNUMERIC(','), ISNUMERIC('1e1')
See IsNumeric() Broken? Only up to a point for some discussion on this point.