Bug with CASE statement in Oracle? - oracle

I have the following piece of code:
IF i.cd_ver IS NULL OR i.cd_ver = '0'
THEN
--Set value of v_cd_ver
v_cd_ver := CASE i.cd_ver
WHEN NULL
THEN '9'
WHEN '0'
THEN '10'
END;
END if;
"i" is the record found in the cursor referenced to an external table. The value is NULL or blank in the external table:
SELECT cd_ver FROM table_xtl WHERE cd_id = '123';
..This returned one row blank.
HOWEVER, v_cd_ver did not get set to '9' as expected. My work-around was to use IF THEN statements instead and it works. Why is the CASE statement not working as expected??
UPDATE:
When I tried instead the following it worked:
v_cd_ver := CASE
WHEN i.cd_ver IS NULL
THEN '9'
WHEN i.cd_ver = '0'
THEN '10'
END;
Is this a bug or there's some reason why the former did not work?

case i.cd_ver when NULL ... is logically equivalent to case when i.cd_ver = NULL ... You know full well that such a comparison results in UNKNOWN, not in TRUE, so the case expression falls through to the default value, which is NULL. (To test this for yourself, add else 'x' before end and run it again, you'll see.)
The right way to write it is case when i.cd_ver is null then '9' when i.cd_ver = '0' then '10' end.

That form of 'simple' case expression compares the value - i.cd_ver here - with each of the comparison expressions, looking for one that is equal.
In a simple CASE expression, Oracle Database searches for the first WHEN ... THEN pair for which expr is equal to comparison_expr and returns return_expr ...
The important bit there is "for which expr is equal to ...". Since nothing is ever equal to null, even itself, there is no match.
To look for nulls you would need a searched case exprssion instead:
v_cd_ver := CASE
WHEN i.cd_ver IS NULL THEN '9'
WHEN i.cd_ver = '0' THEN '10'
END;
Not related, but are you sure 0, 9 and 10 should really be treated as strings?

Try to check empty ('') value or is NULL

Related

How to test if a binding variable is null in PL/SQL? [duplicate]

I want to check if a variable is null. If it is null, then I want to set a value to that variable:
//data type of var is number
if Var = null then
var :=5;
endif
But I am geting error in it. How can I check if a variable is null?
I am using oracle data type
if var is NULL then
var :=5;
end if;
Use:
IF Var IS NULL THEN
var := 5;
END IF;
Oracle 9i+:
var = COALESCE(Var, 5)
Other alternatives:
var = NVL(var, 5)
Reference:
COALESCE
NVL
NVL2
In PL/SQL you can't use operators such as '=' or '<>' to test for NULL because all comparisons to NULL return NULL. To compare something against NULL you need to use the special operators IS NULL or IS NOT NULL which are there for precisely this purpose. Thus, instead of writing
IF var = NULL THEN...
you should write
IF VAR IS NULL THEN...
In the case you've given you also have the option of using the NVL built-in function. NVL takes two arguments, the first being a variable and the second being a value (constant or computed). NVL looks at its first argument and, if it finds that the first argument is NULL, returns the second argument. If the first argument to NVL is not NULL, the first argument is returned. So you could rewrite
IF var IS NULL THEN
var := 5;
END IF;
as
var := NVL(var, 5);
I hope this helps.
EDIT
And because it's nearly ten years since I wrote this answer, let's celebrate by expanding it just a bit.
The COALESCE function is the ANSI equivalent of Oracle's NVL. It differs from NVL in a couple of IMO good ways:
It takes any number of arguments, and returns the first one which is not NULL. If all the arguments passed to COALESCE are NULL, it returns NULL.
In contrast to NVL, COALESCE only evaluates arguments if it must, while NVL evaluates both of its arguments and then determines if the first one is NULL, etc. So COALESCE can be more efficient, because it doesn't spend time evaluating things which won't be used (and which can potentially cause unwanted side effects), but it also means that COALESCE is not a 100% straightforward drop-in replacement for NVL.
Use IS NULL
reference page
There's also the NVL function
http://www.techonthenet.com/oracle/functions/nvl.php
use if var is null
Always remember to be careful with nulls in pl/sql conditional clauses as null is never greater, smaller, equal or unequal to anything. Best way to avoid them is to use nvl.
For example
declare
i integer;
begin
if i <> 1 then
i:=1;
foobar();
end if;
end;
/
Never goes inside the if clause.
These would work.
if 1<>nvl(i,1) then
if i<> 1 or i is null then
Another way:
var := coalesce (var, 5);
COALESCE is the ANSI equivalent (more or less) of Oracle's NVL function.

Merging data with or / nvl in where clause, what benefit?

In what cases using or / nvl combo in merge update is useful and what purpose does it serve ? (If it is good at all). Typical structure bellow.
MERGE INTO data_table p
USING (SELECT pk,
col1,
col2,
col3
FROM incoming_data_table) pp
ON (p.pk = pp.pk)
WHEN MATCHED THEN
UPDATE
SET p.col1 = pp.col1,
p.col2 = pp.col2,
p.col3 = pp.col3
WHERE nvl(p.col1, '0') != nvl(pp.col1, '0') -- Line in question
OR nvl(p.col2, '0') != nvl(pp.col2, '0') -- Line in question
OR nvl(p.col3, '0') != nvl(pp.col3, '0') -- Line in question
WHEN NOT MATCHED THEN
INSERT
(p.pk,
p.col1,
p.col2,
p.col3)
VALUES
(pp.pk,
pp.col1,
pp.col2,
pp.col3);
the OR's make sure that the update is done as soon as one of the three conditions is met. With AND's all three would have to be met before the update taking place. So as soon as at least one of the col's differ the update shall take place.
then NVL make sure that null is interpreted as any other value. Because without the NVL, neither 1=null is true nor is 1!=null true. They both return false. But as the writer of the query wants an update to take place e.g. when p.col1 contains 123 and p.col2 contains null, the NVL is needed. Here are some cases for you:
select 'true' from dual where 1!=null; -- false
select 'true' from dual where 1=null; -- false
select 'true' from dual where 1=nvl(null,0); -- false
select 'true' from dual where 1!=nvl(null,0); -- true
NULL is very special when it comes to comparisons. Not even this is true:
select 'true' from dual where null=null; -- false
Have a look at table 'Nulls in Conditions' on this page for more examples: https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements005.htm

Concatenation respecting conventional null handling

The conventional handling of null in SQL, and the language specification, is that if any part of an expression is null, the whole expression is null.
However in Oracle, text concatenation converts null to a <blank>, eg:
select concat(concat('foo', null), 'bar') from dual; --> returns "foobar"
select 'foo' || null || 'bar' from dual; --> returns "foobar"
I want the conventional behaviour, where the result would be null if any term is null.
Is there a method or function provided by Oracle that concatenates text using a single expression, without recoding any term, such that if any term is null, the result is null?
Notes:
I don't want to repeat any terms, which would be required by a case etc, because the terms are very long and complex, and besides it's bad practice to repeat code
I can’t define any functions. I must use just a single SQL query using nothing but standard syntax and plain Oracle provided functions/operators
Side-stepping the no-repeat requirement by using a subquery or a CTE isn’t answering the question, it’s avoiding it: I want to know if Oracle can concatenate Strings using a single expression in the same way every other database I know does
Yes, concat and || do not work in a standard (as in SQL92 spec) way. I guess, this is because there is no distinction between an empty string and null value in Oracle DB.
You can create a user defined function and use it in SQL.
CREATE OR REPLACE FUNCTION standard_concat (
a VARCHAR2,
b VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF
a IS NULL OR b IS NULL
THEN
RETURN NULL;
ELSE
RETURN a || b;
END IF;
END;
/
Using this function gives you these results:
select standard_concat(standard_concat('foo', ''), 'bar') from dual; returns null
select standard_concat(standard_concat('foo', null), 'bar') from dual; returns null
select standard_concat(standard_concat('foo', 'foo'), 'bar') from dual; returns "foofoobar"
As you can see, empty string will be treated as null since this is the way Oracle DB treats Strings. There is no way to make a distinction here.
If this function is only needed for one query you can inline your function definition into the SQL itself as in:
WITH
FUNCTION standard_concat (
a VARCHAR2,
b VARCHAR2
) RETURN VARCHAR2
AS
BEGIN
IF
a IS NULL OR b IS NULL
THEN
RETURN NULL;
ELSE
RETURN a || b;
END IF;
END;
SELECT
standard_concat(standard_concat('foo',''),'bar')
FROM
dual;
I hope it helps.
I'm not avere of a SQL function and this NULL behavior on VARCHAR2 is posible conventional, but for sure not usual expected. The reason is that Oracle doesn't distinct betwen NULL and a string with length zero (''). For string concatenation the NULLs are considered as empty strings.
Anyway you may use subqueries to avoid repeating the expressions:
with t1 as (
select 'foo' col1, null col2, 'bar' col3 from dual union all
select null col1, null col2, null col3 from dual union all
select 'foo' col1, 'baz' col2, 'bar' col3 from dual
)
select col1,col2,col3,
case when col1 is not NULL and col2 is not NULL and col3 is not NULL then
col1||col2||col3 end as concat
from t1;
returns
COL COL COL CONCAT
--- --- --- ---------
foo bar
foo baz bar foobazbar
Alternatively you may write the predicate in teh CASE statement a bit more compact using the Group Comparison Conditions
select
case when 0 < ALL(length(col1),length(col2),length(col3)) then
col1||col2||col3 end as concat
from t1;
Unfortunately the Group Comparison Conditions doesn't allow a dierct IS NULL test, so a workaround with length must be used.
The third option is a bit ugly (as requires some special string that doesn't exists in regular strings, but probably meets best your requriements.
Simple NVL all strings before concatenation and than exclude those mappend by NVL
with t2 as
(select nvl(col1,'#§$%')||nvl(col2,'#§$%')||nvl(col3,'#§$%') as concat
from t1)
select
case when concat not like '%#§$\%%' escape'\' then concat end as concat
from t2;
There ins't a single expression that will do what you want unfortunately - there isn't a stanrd-compliant equivalent of concat() or the concatenation operator.
I'm sure this doesn't meet the criteria either, but as requested (if slightly mis-advertised), an 'XNL' (well, XMLDB anyway) workaround/hack/abomination:
set null "(null)"
select xmlquery(
'if (min((string-length($x), string-length($y), string-length($z))) = 0)
then "" else concat($x, $y, $z)'
passing 'foo' as "x", null as "y", 'bar' as "z"
returning content)
from dual;
XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
(null)
With no null expressions:
select xmlquery(
'if (min((string-length($x), string-length($y), string-length($z))) = 0)
then "" else concat($x, $y, $z)'
passing 'foo' as "x", 'bar' as "y", 'baz' as "z"
returning content)
from dual;
XMLQUERY('IF(MIN((STRING-LENGTH($X),STRING-LENGTH($Y),STRING-LENGTH($Z)))=0)THEN
--------------------------------------------------------------------------------
foobarbaz
As well as being a hack, of course, it suffers from the problem of not being general as the number of terms is fixed. I haven't come up with a way to pass any number of terms in.

Not regexp_like, find null values

Doing data checking of input data in staging tables prior to loading into the main system tables. Because I want to use the same field checking in Apex I have implemented the checks as regular expressions using 'not regexp_like' to return any field that does not match its pattern. However, for a mandatory 10 character field I expected a pattern such as:
'^.{1,10}$'
to return any field that was null with the fields that were too long, it does not, null fields are not returned.
Is there a regexp pattern that will do this?
Example:
select col
from (select null col from dual
union all
select 'A string' from dual)
where not regexp_like(col, '^.{1,10}$')
Removing the 'not' returns 1 row as expected...
The point here is to force the pattern to find the null, not to use regexp_like to find the good values and then exclude them, or to use an NVL on the field. This checking is already expensive enough!
I am assuming, that like most Oracle code involving null what is happening here is that regexp_like sees null and therefore returns null?
Does Java script and Java behave in the same way?
Thanks
Bob
Oracle has 3 states in the result of a boolean expression: TRUE, FALSE or NULL (unknown).
WHERE REGEXP_LIKE( NULL, '.' )
Will return NULL and not TRUE or FALSE.
Negating the expression to:
WHERE NOT REGEXP_LIKE( NULL, '.' )
Will also return NULL and not TRUE or FALSE and, since it is not TRUE then the row will never be returned.
Instead you need to explicitly check for a NULL value:
WHERE NOT REGEXP_LIKE( col, '^.{1,10}$' )
OR col IS NULL
or, more simply:
WHERE LENGTH( col ) > 10
OR col IS NULL
If you only need to get the strings with zero or more than 10 characters, you can simply use:
length(col) > 10 or col is null
About NULL, the only check you can rely on is is [not] null.
For example:
select length(null) from dual
gives NULL, not 0.

Convert char to number if numeric or else null in Oracle

I would like to convert a character (I know it will always be char(1)) if the character is numeric ([0-9]) and return null if it is not numeric. IOW, I would like to catch the exception and return NULL for all non-convertible characters (those outside the 0-9 range). Is there a way to do that? I tried using TO_NUMBER but that fails if any of the rows have a non-numeric character.
You may use regexp:
select case when regexp_like(your_string, '^[[:digit:]]+$') then to_number(your_string) else NULL end
from (select '1d23' your_string from dual);
For char(1)
select case when ascii(c) between 48 and 57 then to_number(c) else null end
from (select '3' c from dual);
48 is ASCII code for '0' and 57 is ASCII code for '9', ascii function return ASCII code of the argument

Resources