Replacing a calculated field column with a Blank when a NULL is returned? - sql

I'm doing a simple query that uses the DateDiff function to find the number of days between the dates. However, with regards to certain instances, I'd like to populate a blank field (not a null).
Something like this is what I currently have, and it seems to work fine (but it populates a null).
[Test (Years)] = CASE WHEN TYPE IN ('A','B')
THEN NULL ELSE IsNull(CONVERT(decimal(28,12),
(DATEDIFF(d,#StartDate,ExpirationDate)))/365,0) END
Now if I try something like this... which tries to convert all TYPE A and B to populate a blank, I'll get the following error message: Error converting data type varchar to numeric.
[Test (Years)] = CASE WHEN TYPE IN ('A','B')
THEN '' ELSE IsNull(CONVERT(decimal(28,12),
(DATEDIFF(d,#StartDate,ExpirationDate)))/365,0) END
Is there a simple thing I'm missing? I've tried doing the calcualtions without converting to a decimal, but it doesn't seem to work. Any ideas? Thanks

CASE is an expression that returns exactly one value and all of the branches must yield compatible types. A string (even a blank string) is not compatible with a decimal, so you need to do something like:
CASE WHEN ... THEN '' ELSE
CONVERT(VARCHAR(32), COALESCE(CONVERT(DECIMAL(23,12), ... ,0)) END
Note that this hack will only work if you are presenting the data to an end user. If you are trying to store this data in a column or use it in other calculations, it too will be tripped up by the blank string. A number can't be a blank string:
DECLARE #i INT = '';
SELECT #i;
Result:
0
So, if you don't want "empty" numerics to be interpreted as 0, stop being afraid of NULL and if you are dealing with this at presentation time, have the presentation layer present a blank string instead of NULL.

Related

IS ISNULL() specific for integers?

This has been bothering me with my coding continuously and I can't seem to google a good workaround.
I have a number of columns which are data type nvarchar(255). Pretty standard I would assume.
Anyway, I want to run:
DELETE FROM Ranks WHERE ISNULL(INST,0) = 0
where INST is nvarchar(255). I am thrown the error:
Conversion failed when converting the nvarchar value 'Un' to data type int.
which is the first non null in the column. However, I don't care for this showing me the error means it's not null? - I just want to delete the nulls!
Is there something simple I'm missing.
Any help would be fab!
An expression may only be of one type.
Expression ISNULL(INST,0) involves two source types, nvarchar(255) and int. However, no type change happens at this point, because ISNULL is documented to return the type of its first argument (nvarchar), and will convert the second argument to that type if needed, so the entire original expression is equivalent to ISNULL(INST, '0').
Next step is the comparison expression, ISNULL(INST, '0') = 0. It again has nvarchar(255) and int as the source data types, but this time nothing can stop the conversion - in fact, it must happen for the comparison operator, =, to even work. According to the data type precedence list, the int wins, and is chosen as the resulting type of the comparison expression. Hence all values from column INST must be converted to int before the comparison = 0 is made.
If you
just want to delete the nulls
, then just delete the nulls:
DELETE FROM Ranks WHERE INST IS NULL
If for some reason you absolutely have to use isnull in this fashion, which there is no real reason for, then you should have stayed in the realm of strings:
DELETE FROM Ranks WHERE ISNULL(INST, '') = ''
That would have deleted null entries and entries with empty strings (''), just like the WHERE ISNULL(INST, 0) = 0 would have deleted null entries and entries with '0's if all values in INST could have been converted to int.
With ISNULL(INST,0) you are saying: If the string INST is null, replace it with the string 0. But 0 isn't a string, so this makes no sense.
With WHERE ISNULL(INST,0) = 0 you'd access all rows where INST is either NULL or 0 (but as mentioned a string is not an integer).
So what do you want to achieve? Delete all rows where INST is null? That would be
DELETE FROM ranks WHERE inst IS NULL;

Automatically change while query "Case When" in Jasper

I have the data whose type is float. I would like to show them as numeric/integer but for just one case, it will show as a float. For the first i thought with Case When in the query, it will be solved but it didn't happen.
i put this code
CASE WHEN SUBSTRING (c.kode_hs,1,2) = '71' THEN CAST(c.brutto AS float) ELSE c.brutto END AS brutto,
CASE WHEN SUBSTRING (c.kode_hs,1,2) = '71' THEN CAST(c.netto AS float) ELSE c.netto END AS netto,
But it didn't turn out to be a float type.
i try to modify the setting of jasper, but it just work for one data type.
There's another alternative i took, i changed the type into number, so that case CASE WHEN SUBSTRING (c.kode_hs,1,2) = '71' worked out. But, unfortunately,when its value was highest (or have much character) was shown as scientific number e.x.: 2E9. That look is definitely ignored in report view. Is it any other solution for me? Thanks anyway
You could select the data from the database as it is, as a float, and do the trick in Jasper.
Set the type of c.kode_hs field in JasperReport as Float, ant the ExpresionClass of the TextBox in which you want to show the value as String. Then set the Text Field Expression as
$F{kode_hs}.toString().substring(0,2).equals("71")?new java.text.DecimalFormat("#0.00").format($F{brutto}):new java.text.DecimalFormat("##").format($F{brutto})
assuming $F{kode_hs} and $F{brutto} are the fields which hold the respective values.

Converting char to integer in INSERT using IIF and SIMILAR TO

I am using in insert statement to convert BDE table (source) to a Firebird table (destination) using IB Datapump. So the INSERT statement is fed by source table values via parameters. One of the source field parameters is alphanum (SOURCECHAR10 char(10), holds mostly integers and needs to be converted to integer in the (integer type) destination column NEWINTFLD. If SOURCECHAR10 is not numeric, I want to assign 0 to NEWINTFLD.
I use IIF and SIMILAR to to test whether the string is numeric, and assign 0 if not numeric as follows:
INSERT INTO "DEST_TABLE" (......, "NEWINTFLD",.....)
VALUES(..., IIF( :"SOURCECHAR10" SIMILAR TO '[[:DIGIT:]]*', :"SOURCECHAR10", 0),..)
For every non numeric string however, I still get conversion errors (DSQL error code = -303).
I tested with only constants in the IIF result fields like SOURCECHAR10" SIMILAR TO '[[:DIGIT:]]*', 1, 0) and that works fine so somehow the :SOURCECHAR10 in the true result field of the IIF generates the error.
Any ideas how to get around this?
When your query is executed, the parser will notice that second use of :"SOURCECHAR10" is used in a place where an integer is expected. Therefor it will always convert the contents of :SOURCECHAR10 into an integer for that position, even though it is not used if the string is non-integer.
In reality Firebird does not use :"SOURCECHAR10" as parameters, but your connection library will convert it to two separate parameter placeholders ? and the type of the second placeholder will be INTEGER. So the conversion happens before the actual query is executed.
The solution is probably (I didn't test it, might contain syntax errors) to use something like (NOTE: see second example for correct solution):
CASE
WHEN :"SOURCECHAR10" SIMILAR TO '[[:DIGIT:]]*'
THEN CAST(:"SOURCECHAR10" AS INTEGER)
ELSE 0
END
This doesn't work as this is interpreted as a cast of the parameter itself, see CAST() item 'Casting input fields'
If this does not work, you could also attempt to add an explicit cast to VARCHAR around :"SOURCECHAR10" to make sure the parameter is correctly identified as being VARCHAR:
CASE
WHEN :"SOURCECHAR10" SIMILAR TO '[[:DIGIT:]]*'
THEN CAST(CAST(:"SOURCECHAR10" AS VARCHAR(10) AS INTEGER)
ELSE 0
END
Here the inner cast is applied to the parameter itself, the outer cast is applied when the CASE expression is evaluated to true

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