SQL Use Text and Decimal in the same column - sql

I have a requirement to fill in all NULL fields with DEL. I don't want to modify the database tables, I would just like to do it in on column. Below is what I'm trying to do right now. Basically if the value is not null then I want 132.00. If it is null then I want to see DEL. I'm working on SQL 2008 R2. Thank you for your help its greatly appreciated.
Current thought
CASE WHEN [Sales] IS NOT NULL
THEN CAST([Sales] AS decimal(10,0))
ELSE 'DEL'
END AS Sales
Error
Error converting data type varchar to numeric.
Warning: Null value is eliminated by an aggregate or other SET operation.

CASE WHEN [Sales] IS NOT NULL
THEN CAST(CAST([Sales] AS decimal(10,0)) AS NVARCHAR(1000))
ELSE CAST(DEL AS NVARCHAR(1000))
END AS Sales
In a case statement in every ease the returned data type must be the same.
EDIT
To return string DEL if sales is null you can do
CASE WHEN [Sales] IS NOT NULL
THEN CAST(CAST([Sales] AS decimal(10,0)) AS NVARCHAR(1000))
ELSE 'DEL'
END AS Sales

You can't store the 'DEL' text in a column defined to hold decimals. It's just not possible. What is more, you shouldn't store decimal values in a columned defined to hold strings. In other words, this requirement is backwards and wrong.
What you can do is continue to store NULL and decimal data as you have before, and just return string data for display:
CASE WHEN [sales] IS NULL THEN 'DEL' ELSE Convert(varchar(13), [sales]) END
However, this is also less than the ideal, as it forces your database to do the conversion work, and means your client code starts out working with strings rather than numbers. The best option here is to have the database continue to work with the raw data as it is, and make the adjustment to change NULLs to 'DEL' in your client code.

Related

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.

Return Character from Numeric Field using Cast & Coalesce

Using SQL Server 2008 R2
Relatively basic SQL user, apologies if this is a simple question but need a little help to tidy up the output of a fairly complex script I have been given.
I have a number of columns being returned for which where there is a NULL, I want to replace all NULL's with a standard set of characters, currently "---". Using ISNULL works for most columns. However, for some columns we are looking at 2 tables to find a value so have after doing some research on here, I have modified a line I am having trouble with as follows:
Previous
isnull (ff.ff_sales,aa.ff_sales) as 'Total Revenue'
Latest
cast(coalesce(ff.ff_sales,aa.ff_sales,'') as FLOAT) as 'Total Revenue'
The initial line returned 'NULL' if both ff.ff_sales & aa.ff_sales were empty, now with the latest line using cast & coalesce I get '0'. However, I am trying to achieve a situation where I get '---' as per all other fields where a NULL exists. I don't want it to return '0' for a Sales field as this is misleading. I have tried using VARCHAR instead of FLOAT but am unsure if this is the right thing to do at this stage?
1st column is using ISNULL, 2nd column current output with cast & coalesce, 3rd column is what I want to get to:
Total Revenue Total Revenue Total Revenue
67755 67755 67755
6.123 6.123 6.123
494.75 494.75 494.75
0 0 0
1139909 1139909 1139909
12346.45 12346.45 12346.45
129.866 129.866 129.866
NULL 0 ---
NULL 0 ---
554 554 554
Thanks for your help!
You can use case to decide what should be output in this scenario, like so:
select
case
when isnull (ff.ff_sales,aa.ff_sales) is null then '---'
else cast(isnull (ff.ff_sales,aa.ff_sales) as varchar)
end as 'Total Revenue'
This will force the output to be returned as varchar though, because you cannot cast '---' as a numeric type.
Alternatively, you could just let the value be NULL in DB, and in your presentation layer, replace a DBNull or equivalent value with '---'. This will let you keep total revenue as a numeric field at the DB level.

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).