Does SQL Server really evaluate every 'then' clause in a case expression? - sql

I'm working with an ItemNumber field in a legacy system that is 99% numbers, but there are a few records that contain letters. The numbers are all padded with leading zeros so I thought I would just cast them as bigint's to solve this problem, but of course it throws an error when it gets to the records with letters in them.
I thought the following case statement would have worked, but it still throws the error. Why in the world is SQL Server evaluating the cast if the isnumeric(itemnumber) = 1 condition isn't true?
select case when isnumeric(itemnumber) = 1
then cast(itemnumber as bigint)
else itemnumber
end ItemNumber
from items
And what's the best workaround?

Your expression tries to convert a VARCHAR value into a BIGINT if it's numeric and leave the value as is if it's not.
Since you are mixing datatypes in the CASE statement, SQL Server tries to cast them all into BIGINT but fails on non-numeric values.
If you just want to omit non-numeric values, get rid of the ELSE clause:
SELECT CASE ISNUMERIC(itemnumber)
WHEN 1 THEN
CAST(itemnumber AS BIGINT)
END
FROM items

Maybe because:
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).
http://msdn.microsoft.com/en-us/library/ms186272.aspx

The problem is that you are still mixing your types:
select case when isnumeric(itemnumber) = 1
then cast(itemnumber as bigint) --bigint
else itemnumber --varchar or whatever
end ItemNumber --????
from items
You need two columns
select case when isnumeric(itemnumber) = 1
then cast(itemnumber as bigint) --bigint
else -1
end NumericItemNumber
from items
select case when isnumeric(itemnumber) = 1
then ''
else itemnumber
end StringItemNumber
from items
Then you need to build a query that takes both ints and varchars

Related

SQL - How to sort numbers in a VARCHAR column with empty strings as entries

I have a postgres column which is like so:
It only has numbers or empty string.
I want to be able to sort the numbers by the numbers but as I go to cast the column to a float, it will give me the following error:
ERROR: invalid input syntax for type double precision: ""
Is there a way I can do this sort, and having the empty strings be treated as 0?
This is my query that's failing:
SELECT C.content
FROM row R
LEFT JOIN cell C ON C.row_id = R.row_id
WHERE R.database_id = 'd1c39d3a-0205-4ee3-b0e3-89eda54c8ad2'
AND C.column_id = '57833374-8b2f-43f3-bdf5-369efcfedeed'
ORDER BY cast(C.content as float)
when its an empty string you need to either treat it as null or 0 and then it will work, try putting a case statement like so in the order by
ORDER BY
case when C.content = '' then 0
else cast(C.content as float)
end
If it's sure this column will never have negative values, a simple option is just adding a leading zero.
If the column is NULL or has an empty string, it will be sorted as 0.
Otherwise, the value will be sorted as it is because adding a leading zero doesn't change anything.
SELECT yourcolumn
FROM yourtable
ORDER BY CAST(CONCAT('0',yourcolumn) AS FLOAT);
If negative values can appear, this would fail, so I would then use CASE WHEN.
But I propose to also take 0 for NULL values, not only for empty strings:
SELECT yourcolumn
FROM yourtable
ORDER BY
CASE WHEN yourcolumn = '' OR yourcolumn IS NULL
THEN 0
ELSE CAST(yourcolumn AS FLOAT)
END;
Otherwise, NULL values would be sorted as highest number which is likely not intended.
And yes, I know you wrote there are numbers and empy strings only in your table, but maybe this can change (unless the column is not nullable). So adding this condition doesn't hurt.

When using cast - error: invalid input syntax for type numeric: "" (postgreSQL)

Duration column in the table is given with 'varchar' data type. It contains decimal values. So I am trying to cast varchar to float/numeric/decimal/double/double precision. But none of those works. why is it not working?
select runner_id,
sum(case when cast(duration as decimal) <> '' then 1
else 0 end) as delivered, count(order_id) as total_orders
from t_runner_orders
group by runner_id
The reason it's not working is because your duration column contains values which cannot be cast to a numeric type. The specific value throwing the error is an empty string. Also, you shouldn't be comparing a numeric type to an empty string.
Also, if you're comparing a varchar column to a character value in your CASE statement, why are you trying to cast it to a numeric type at all?
For what you're doing here, I would just write it as CASE WHEN duration <> '' THEN 1 ELSE 0 END
And if you do need to cast it to a numeric type at some point, the way to do that would be something like CASE WHEN duration = '' THEN NULL ELSE cast(duration AS DECIMAL) END (asuming that empty strings are the only values in your column which cannot be cast to decimal)
The problem is you are doing the CAST before the <> ''. The cast fails as there are empty strings in the field. You have several choices:
Use NULL instead of '' in field.
Do duration <> ''
Last and probably the best long term solution change the column type to numeric.
You can translate '' to null using NULLIF in the cast.
cast(nullif(duration,'') as decimal) is not null
However this will not solve you basic problem which is "varchar' data type. It contains decimal values" NO it does not it contains a string which you hope are decimal values, but nothing prohibits putting 'zero.zero' into it - distinctly not a decimal value. I will go #AdrianKlaver one step further.
3. The only long term solution change the column type to numeric.

how to show empty value for a int datatype in SQL?

how to show empty value for a int datatype in SQL?
I have a case statement on an int datatype column to show empty for the values less than or equal to 0.
case when [TotalValue] <= 0 Then ''
when [TotalValue] > 0 Then [TotalValue]
End as [TotalValue]
Right now, case statement is returning 0 for any values less than or equal to 0. I expect to have them as Empty. Having 0 instead of negative value is not a correct result.
How to convert the record to show only empty?
The problem of your code is that Then '' is automatically converted to int value, which happens to be 0 for empty strings (try select CAST('' as int) to check).The data type for ambiguously defined column (like yours) is determined from the data type precedence rules.
Unambiguously defining the data type of the column would resolve the issue.
I recommend trying to return NULL from the database, like this:
case when [TotalValue] <= 0 Then NULL
when [TotalValue] > 0 Then [TotalValue]
End as [TotalValue]
Most likely, your report engine will convert NULL to something like an empty string. In addition, you may be getting some benefits of ability to manipulate numeric values, if your report engine supports those (e.g. calculate average over selection).
Alternatively, try casting the values to string in SQL:
case when [TotalValue] <= 0 Then ''
when [TotalValue] > 0 Then CAST([TotalValue] as varchar)
End as [TotalValue]
I think the simplest construct is:
(case when TotalValue > 0 Then TotalValue
end) as TotalValue
You can always CAST the number to a VARCHAR (string) and then set it to an empty string when NULL:
ISNULL(CAST(TotalValue as varchar(10)),'') as TotalValue
Empty string is a concept that doesn't make sense for integer datatype.
Generally you should return the results to the application as integer datatype and use NULL for this and have your application display null as an empty string if desired.
If you do need to do this in SQL you are now dealing with strings rather than integers.
One way of converting to string and performing your desired formatting is with the FORMAT function.
SELECT FORMAT(TotalValue, '0;"";""')
FROM
(VALUES (1),
(0),
(-123),
(123456))T(TotalValue)
Returns
1
123456

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

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.