Numeric comparison of a varchar column to a decimal column - sql

I have the following tables
productinfo:
ID|productname|productarea|productcost|productid
sales:
ID|salesid|productid|
salesdata:
ID|productid|productname|salestotal
Where I am having trouble is: salesdata.salestotal is a varchar column and may have nulls
Comparing the salesdata.saletotal to the productinfo.productcost columns.
I can do a
cast(salesdata.saletotal as float(7)) > X
and it works. I would like to do is
cast(salesdata.saletotal as float(7)) > productinfo.productcost
where
sales.productid = salesdata.productid and
productinfo.productid = salesdata.productid
However when I do that I get an error:
Error when converting type varchar to float
I found this post which was similar but am unable to get any other columns with it. I can not change the current db structure.

You can add a IsNumeric() to your where clause to check that salesdata.saletotal can be parsed to float
SELECT
cast(salesdata.saletotal as float(7)) > productinfo.productcost
FROM Sales,Salesdata
WHERE
sales.productid = salesdata.productid and
productinfo.productid = salesdata.productid and
IsNumeric(salesdata.saletotal) =1
Or you can use Not Like (best than IsNumeric)
salesdata.saletotal NOT LIKE '%[^0-9]%'

if you are using sql server version 2012 and above, you can use try_cast() or try_parse()
try_cast(salesdata.saletotal as float)>productinfo.productcost
or
try_parse(salesdata.saletotal as float)>productinfo.productcost
Prior to 2012, I would use patindex('%[^0-9.-]%',salesdata.saletotal)=0 to determine if something is numeric, because isnumeric() is somewhat broken.
e.g. rextester: http://rextester.com/UZE48454
case when patindex('%[^0-9.-]%',salesdata.saletotal)>0
then null
when isnull(cast(salesdata.saletotal as float(7)),0.0)
> isnull(cast(productinfo.productcost as float(7)),0)
then 1
else 0
end

Related

SQL CASE statement needs to handle Text

Apologies if this has been asked before - I've spent a couple of hours searching but not found anything that's helped.
It's quite simple really - I've been asked to create a query which includes a field that when it was set up (not by me) was created as a VARCHAR instead of an INT.
I need to do some calculations on this field, however some users have been entering text into it, so the calculations fail as it can't convert the data to an INT.
Is there anything I can add to a CASE statement to handle where there's text?
I was thinking something like the below, but don't know what the actual code is:
CASE
WHEN [Field1] IS TEXT THEN 1 ;
ELSE [Field2] as [Chose name]
END
Edit: Note that this is in MS SQL Server.
Thanks.
In SQL Server, you can use try_convert() and isnull() for this:
isnull(try_convert(int, field), 1)
try_convert() attempts you cast field to an int. When that fails, null is returned; you can trap that with isnull() and turn the result to 1 instead.
Note that this only works as long as field is not null (otherwise, you would get 1 as a result).
In SQL Server
Declare #Salary varchar(100);
Set #Salary = 50000;
Select Case when ISNUMERIC(#Salary) = 1 then 1
else 0 end as [Check]
May be this will be Helpful.

Error converting data type nvarchar to numeric - SQL Server

I am trying to take an average of a column in my database. The column is AMOUNT and it is stored as NVARCHAR(300),null.
When I try to convert it to a numeric value I get the following error:
Msg 8114, Level 16, State 5, Line 1
Error converting datatype NVARCHAR to NUMBER
Here is what I have right now.
SELECT AVG(CAST(Reimbursement AS DECIMAL(18,2)) AS Amount
FROM Database
WHERE ISNUMERIC(Reimbursement) = 1
AND Reimbursement IS NOT NULL
You would think that your code would work. However, SQL Server does not guarantee that the WHERE clause filters the database before the conversion for the SELECT takes place. In my opinion this is a bug. In Microsoft's opinion, this is an optimization feature.
Hence, your WHERE is not guaranteed to work. Even using a CTE doesn't fix the problem.
The best solution is TRY_CONVERT() available in SQL Server 2012+:
SELECT AVG(TRY_CONVERT(DECIMAL(18,2), Reimbursement)) AS Amount
FROM Database
WHERE ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL;
In earlier versions, you can use CASE. The CASE does guarantee the sequential ordering of the clauses, so:
SELECT AVG(CASE WHEN ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL
THEN CONVERT(DECIMAL(18,2), Reimbursement))
END)
FROM Database;
Because AVG() ignores NULL values, the WHERE is not necessary, but you can include it if you like.
Finally, you could simplify your code by using a computed column:
alter database add Reimbursement_Value as
(CASE WHEN ISNUMERIC(Reimbursement) = 1 AND Reimbursement IS NOT NULL
THEN CONVERT(DECIMAL(18,2), Reimbursement))
END);
Then you could write the code as:
select avg(Reimbursement_Value)
from database
where Reimbursement_Value is not null;
Quote from MSDN...
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
select isnumeric('+')---1
select isnumeric('$')---1
so try to add to avoid non numeric numbers messing with your ouput..
WHERE Reimbursement NOT LIKE '%[^0-9]%'
If you are on SQLServer 2012,you could try using TRY_Convert which outputs null for conversion failures..
SELECT AVG(try_convert( DECIMAL(18,2),Reimbursement))
from
table
I am guessing that since it is Nvarchar you are going to find some values in there with a '$','.', or a (,). I would run a query likt this:
SELECT Amount
FROM database
WHERE Amount LIKE '%$%' OR
Amount LIKE '%.%' OR
Amount LIKE '%,%'
See what you get and my guess you will get some rows returned and then update those rows and try it again.
Currently your query would pull all numbers that are not all numeric which is a reason why it is failing too. Instead try running this:
SELECT AVG(CAST(Reimbursement AS DECIMAL(18,2)) AS Amount
FROM Database
--Changed ISNUMERIC() = to 0 for true so it will only pull numeric numbers.
WHERE ISNUMERIC(Reimbursement) = 0 and Reimbursement IS NOT NULL

Updating with case in SQL Server 2008 R2

I want to update a column according to another column value.
for example, In Value column i have numbers between 0 to 1.
I want to check values and if:
Values < 0.45 set ValueStatus=Bad
Values >=0.45 and values<0.55 set ValueStatus =SoSo
Values >= 0.55 set ValueStatus=Good
I wrote the query like this:
update table
set ValueStatus=(case
when Values<'0.45' then 'Bad'
when (Values>='0.45' and Values<'0.55') then 'SoSo'
when Values>='0.55' then 'Good'
else Values
end)
But i get this error :
Error converting data type varchar to float.
Type of Values is Float and ValueStatus is Nvarchar(50)
Thanks
try this (you were adding ' to the numbers and SQL takes them as varchar) :
update table
set ValueStatus=(case when Values<0.45 then 'Bad'
when Values>=0.45 and Values<0.55 then 'SoSo' when Values>=0.55
then 'Good' else Values end )
I believe your problem is based on how the case statement determines the return type. You can read about it here and here.
The numeric types have a higher precedence than the string types. With the else values, you have four clauses in the `case. Three return strings; one returns a number. The number trumps the types so it tries to turn everything into a number.
You can mimic this problem with:
select (case when 1=1 then 'abc' else 12.3 end)
Happily, you can fix this by removing the else clause which is not needed in this case.

How does one filter based on whether a field can be converted to a numeric?

I've got a report that has been in use quite a while - in fact, the company's invoice system rests in a large part upon this report (Disclaimer: I didn't write it). The filtering is based upon whether a field of type VarChar(50) falls between two numeric values passed in by the user.
The problem is that the field the data is being filtered on now not only has simple non-numeric values such as '/A', 'TEST' and a slew of other non-numeric data, but also has numeric values that seem to be defying any type of numeric conversion I can think of.
The following (simplified) test query demonstrates the failure:
Declare #StartSummary Int,
#EndSummary Int
Select #StartSummary = 166285,
#EndSummary = 166289
Select SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And IsNumeric(SummaryInvoice) = 1
And Convert(int, SummaryInvoice) Between #StartSummary And #EndSummary
I've also attempted conversions using bigint, real and float and all give me similar errors:
Msg 8115, Level 16, State 2, Line 7
Arithmetic overflow error converting
expression to data type int.
I've tried other larger numeric datatypes such as BigInt with the same error. I've also tried using sub-queries to sidestep the conversion issue by only extracting fields that have numeric data and then converting those in the wrapper query, but then I get other errors which are all variations on a theme indicating that the value stored in the SummaryInvoice field can't be converted to the relevant data type.
Short of extracting only those records with numeric SummaryInvoice fields to a temporary table and then querying against the temporary table, is there any one-step solution that would solve this problem?
Edit: Here's the field data that I suspect is causing the problem:
SummaryInvoice
11111111111111111111111111
IsNumeric states that this field is numeric - which it is. But attempting to convert it to BigInt causes an arithmetic overflow. Any ideas? It doesn't appear to be an isolated incident, there seems to have been a number of records populated with data that causes this issue.
It seems that you are gonna have problems with the ISNUMERIC function, since it returns 1 if can be cast to any number type (including ., ,, e0, etc). If you have numbers longer than 2^63-1, you can use DECIMAL or NUMERIC. I'm not sure if you can use PATINDEX to perform an regex look on SummaryInvoice, but if you can, then you should try this:
SELECT SummaryInvoice
FROM Invoice
WHERE ISNULL(SummaryInvoice, '') <> ''
AND CASE WHEN PATINDEX('%[^0-9]%',SummaryInvoice) > 0 THEN CONVERT(DECIMAL(30,0), SummaryInvoice) ELSE -1 END
BETWEEN #StartSummary And #EndSummary
You can't guarantee what order the WHERE clause filters will be applied.
One ugly option to decouple inner and outer.
SELECT
*
FROM
(
Select TOP 2000000000
SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And IsNumeric(SummaryInvoice) = 1
ORDER BY SummaryInvoice
) foo
WHERE
Convert(int, SummaryInvoice) Between #StartSummary And #EndSummary
Another using CASE
Select SummaryInvoice
From Invoice
Where IsNull(SummaryInvoice, '') <> ''
And
CASE WHEN IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE -1 END
Between #StartSummary And #EndSummary
YMMV
Edit: after question update
use decimal(38,0) not int
Change ISNUMERIC(SummaryInvoice) to ISNUMERIC(SummaryInvoice + '0e0')
AND with IsNumeric(SummaryInvoice) = 1, will not short circuit in SQL Server.
But may be you can use
AND (CASE IsNumeric(SummaryInvoice) = 1 THEN Convert(int, SummaryInvoice) ELSE 0 END)
Between #StartSummary And #EndSummary
Your first issue is to fix your database structure so bad data cannot get into the field. You are putting a band-aid on a wound that needs stitches and wondering why it doesn't heal.
Database refactoring is not fun, but it needs to be done when there is a data integrity problem. I assume you aren't really invoicing someone for 11,111,111,111,111,111,111,111,111 or 'test'. So don't allow those values to ever get entered (if you can't change the structure to the correct data type, consider a trigger to prevent bad data from going in) and delete the ones you do have that are bad.

Conditionally branching in SQL based on the type of a variable

I'm selecting a value out of a table that can either be an integer or a nvarchar. It's stored as nvarchar. I want to conditionally call a function that will convert this value if it is an integer (that is, if it can be converted into an integer), otherwise I want to select the nvarchar with no conversion.
This is hitting a SQL Server 2005 database.
select case
when T.Value (is integer) then SomeConversionFunction(T.Value)
else T.Value
end as SomeAlias
from SomeTable T
Note that it is the "(is integer)" part that I'm having trouble with. Thanks in advance.
UPDATE
Check the comment on Ian's answer. It explains the why and the what a little better. Thanks to everyone for their thoughts.
select case
when ISNUMERIC(T.Value) then T.Value
else SomeConversionFunction(T.Value)
end as SomeAlias
Also, have you considered using the sql_variant data type?
The result set can only have one type associated with it for each column, you will get an error if the first row converts to an integer and there are strings that follow:
Msg 245, Level 16, State 1, Line 1
Conversion failed when converting the nvarchar value 'word' to data type int.
try this to see:
create table testing
(
strangevalue nvarchar(10)
)
insert into testing values (1)
insert into testing values ('word')
select * from testing
select
case
when ISNUMERIC(strangevalue)=1 THEN CONVERT(int,strangevalue)
ELSE strangevalue
END
FROM testing
best bet is to return two columns:
select
case
when ISNUMERIC(strangevalue)=1 THEN CONVERT(int,strangevalue)
ELSE NULL
END AS StrangvalueINT
,case
when ISNUMERIC(strangevalue)=1 THEN NULL
ELSE strangevalue
END AS StrangvalueString
FROM testing
or your application can test for numeric and do your special processing.
You can't have a column that is sometimes an integer and sometimes a string. Return the string and check it using int.TryParse() in the client code.
ISNUMERIC. However, this accepts +, - and decimals so more work is needed.
However, you can't have the columns as both datatypes in one go: you'll need 2 columns.
I'd suggest that you deal with this in your client or use an ISNUMERIC replacement
IsNumeric will get you part of the way there. You can then add some further code to check whether it is an integer
for example:
select top 10
case
when isnumeric(mycolumn) = 1 then
case
when convert(int, mycolumn) = mycolumn then
'integer'
else
'number but not an integer'
end
else
'not a number'
end
from mytable
To clarify some other answers, your SQL statement can't return different data types in one column (it looks like the other answers are saying you can't store different data types in one column - yours are all strign represenations).
Therefore, if you use ISNUMERIC or another function, the value will be cast as a string in the table that is returned anyway if there are other strigns being selected.
If you are selecting only one value then it could return a string or a number, however your front end code will need to be able to return the different data types.
Just to add to some of the other comments about not being able to return different data types in the same column... Database columns should know what datatype they are holding. If they don't then that should be a BIG red flag that you have a design problem somewhere, which almost guarantees future headaches (like this one).