Skip to Main Content

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, ALVARO.

Asked: January 05, 2001 - 5:51 pm UTC

Last updated: July 30, 2020 - 12:31 am UTC

Version: 7.3.4.0.0

Viewed 100K+ times! This question is

You Asked

I have a trigger for recording the old and new values of the fields changed on a table.
I compare ...
If (updating and :old.field<>:new.field) Then
( Insert into auditing_tables )
End If;

if :old.field is 'JHON' and :new.field is null the insert doesn´t apply and I need to record both values.


when I compare null values the instruccion fails.
I know that I can´t compare null values ( because the result is unknow)
, so I want to know if is there any function to do this.


thanks

acueto

and Tom said...

See
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:59412348055 <code>for a method to generate triggers that do this for you but in short, you need to do a compare such as:


if ( l_new <> l_old or
(l_new is null and l_old is not NULL) or
(l_new is not null and l_old is NULL) )


You could also write a package with overloaded functions for numbers, dates and strings to do the compare as well.





Rating

  (33 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

comparing null values

A reader, June 25, 2003 - 2:39 pm UTC

Hi,

I have a table with one column
DESC t1

C1 VARCHAR2(200)

Now this column contains alphanumeric values as well
as some nulls. In my application we consider NULL is equal
to NULL.

A user gives an input and then i have to display
all records that match this value in C1, if user inputs
null then all nullls should be displayed

How can I do this in sql.
if I do SELECT * FROM T1 WHERE C1 = &USER_INPUT

if user inputs NULL then this query will return no
records

is there any way to consider NULL = NULL for
this query?


Tom Kyte
June 25, 2003 - 8:13 pm UTC

Your application is not written for relational databases, there NULL is never equal NULL, nor is it NOT equal NULL....


but, read
</code> http://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:7555175291763 <code>
and use the CBO

NULLIF

A reader, June 26, 2003 - 12:57 pm UTC

acueto:

You can NULLIF if you are in 9i.

NULLIF compares expr1 and expr2. If they are equal, then the function returns null. If they are not equal, then the function returns expr1. You cannot specify the literal NULL for expr1.

Tom Kyte
June 26, 2003 - 1:44 pm UTC



ops$tkyte@ORA920> set null *null*
ops$tkyte@ORA920> select x, y, nullif( x, y )
  2    from t;

         X          Y NULLIF(X,Y)
---------- ---------- -----------
         1          1 *null*             <<<=== the same
         1          2           1        
         1 *null*               1
*null*              2 *null*             <<<=== different
*null*     *null*     *null*             <<<=== the same

I don't think it works well when expr1 is NULL
 

Sort of a work around....

Mark J Bobak, June 26, 2003 - 1:58 pm UTC

Regarding this query:
SELECT * FROM T1 WHERE C1 = &USER_INPUT

Where the user may supply a NULL.

You should be able to do something like:
select * from t1
where decode('&user_input',null,1,0) = 1
and c1 is null
union all
select * from t1
where decode('&user_input',null,0,1) = 1
and c1 = '&user_input'


It's not pretty, and Tom's right, your data model should
represent that which can be represented by the database,
but the above might work if you're stuck and have no
alternatives.

NULLIF = CONFUSIONTHEN

A reader, June 26, 2003 - 2:01 pm UTC

Hi,
Now how does this NULLIF work.
As per Tom's example last line
*null* *null* *null*

Null is never equal to or not equal to NULL.Its result is unknown

So why does NULLIF returns null ?
I thought we can only compare nulls to nulls in decodes or
NVL or NVL2.



Tom Kyte
June 26, 2003 - 2:17 pm UTC

nullif returns null if the first parameter is null.

*null* 2 *null* <<<=== different
*null* *null* *null* <<<=== the same



HOW IS THIS

a reader, June 27, 2003 - 11:28 am UTC

CREATE TABLE T1 (C1 VARCHAR2(4));

SELECT * FROM T1 WHERE
NVL(C1,'XXXXX') = NVL(&USER_INPUT, 'XXXXX')

I replace NULL with five 'X' characters which is greater
than the maximum length of C1 i.e VARCHAR2(4). This will
return all rows for which C1 is NULL if the user inputs
NULL. Because c1 is varchar2(4), therefore there is no
chance that XXXXX will be found in c1.

Please comment on this workaround.

Thanks

Tom Kyte
June 27, 2003 - 12:12 pm UTC

you want to full scan all of the time and never use an index?

maybe if you used a function based index on nvl(c1...)


PL/SQL

A reader, June 27, 2003 - 12:01 pm UTC

acueto, if I were you, I would use:

if :new.field = :old.field then
null;
else
<< your logic here >>
end if;



Nulls

Ishaque Hussain, August 12, 2003 - 11:05 am UTC

In our table, we can't figure out what one of the column values is. The column is a varchar2(1) and it looks like it's a blank or a null. It's neither a null or one blank space. When we converted it to ascii, it converts to a zero and ascii 0 is considered a null in ascii format. Could you let me know what could have caused that and what an ascii 0 translates to? We are using Oracle version 8.1.7.4.5

Thanks

Tom Kyte
August 12, 2003 - 11:10 am UTC

ascii 0 is just that -- ascii 0

it is not a blank (that would be ascii 32).
it is not null (that would be NULL)


ops$tkyte@ORA920> create table t ( msg varchar2(10), x varchar2(1) );

Table created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> insert into t values ( 'chr(0)', chr(0) );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'null', NULL );

1 row created.

ops$tkyte@ORA920> insert into t values ( 'chr(32)', chr(32) );

1 row created.

ops$tkyte@ORA920> insert into t values ( ''' ''', ' ' );

1 row created.

ops$tkyte@ORA920>
ops$tkyte@ORA920> column dumpx format a15
ops$tkyte@ORA920> select msg, dump(x) dumpx, x from t;

MSG        DUMPX           X
---------- --------------- -
chr(0)     Typ=1 Len=1: 0
null       NULL
chr(32)    Typ=1 Len=1: 32
' '        Typ=1 Len=1: 32




someone/program inserted ascii 0 into your table, that is all.  it is not blank, it is not null, it is chr(0) 

When is CHR(0) used in v$session.terminal?

Matt, December 02, 2003 - 5:05 pm UTC

I picked up the following query from Cary Millsaps new book.

select s.sid db_sid
,s.serial# db_serial
,p.spid
,to_char(s.logon_time,'DD/MM/YYYY HH24:MI:SS') db_logon_time
,nvl(s.username, 'SYS') db_user
,s.osuser os_user
,s.machine os_machine
,nvl(decode(instr(s.terminal,chr(0)),0,s.terminal,substr(s.terminal,1,instr(s.terminal,chr(0))-1)),'none') os_terminal
,s.program os_program
from v$session s
,v$process p
where 1=1
and s.paddr = p.addr
and s.username like upper('SYSTEM')

The line:

nvl(decode(instr(s.terminal,chr(0)),0,s.terminal,substr(s.terminal,1,instr(s.terminal,chr(0))-1)),'none') os_terminal

Looks for CHR(0). I wondered what the purpose of CHR(0) was and when it might appear in the terminal column.

Any ideas?

Tom Kyte
December 02, 2003 - 9:19 pm UTC

chr(0) is the "c" null terminator.

it can appear in the terminal name.

if it does, it could tell a c print program to "stop printing" anymore

he is just removing it.

Do you recommend using sys_op_map_nonnull()?

Kurt, December 03, 2003 - 12:55 am UTC

Tom,
Comparing 2 values, each of which is possibly null, seems to be a common problem. In a newsgroup, Jonathan Lewis suggests that the "brave at heart" can use an undocumented function named sys_op_map_nonnull(). In another posting on this site, you say that that function is:

<quote>
just an internal "hint".

"ignore that man behind the curtain" sort of stuff.
</quote>

I find that function to be great as an elegant method for comparing 2 values, but it has limitations:
- it's undocumented, and therefore poses a risk of going away or changing functionality
- it's a SQL-only function that is unrecognized by PL/SQL

For the benefit of other readers, here's how it *could* be used, if you dare.

v9.2.0.1.0> select sys_op_map_nonnull(null) from dual;

SY
--
FF

v9.2.0.1.0> select sys_op_map_nonnull('abc') from dual;

SYS_OP_M
--------
61626300

v9.2.0.1.0> select sys_op_map_nonnull(1234) from dual;

SYS_OP_M
--------
C20D2300

v9.2.0.1.0> select sys_op_map_nonnull(sysdate) from dual;

SYS_OP_MAP_NONNU
----------------
78670C0301251B00

v9.2.0.1.0> select * from dual where sys_op_map_nonnull(null) = sys_op_map_nonnull('a');

no rows selected

v9.2.0.1.0> select * from dual where sys_op_map_nonnull(null) = sys_op_map_nonnull(null);

D
-
X

v9.2.0.1.0> select * from dual where sys_op_map_nonnull('a') = sys_op_map_nonnull('b');

no rows selected

v9.2.0.1.0> select * from dual where sys_op_map_nonnull('a') = sys_op_map_nonnull('a');

D
-
X

v9.2.0.1.0> var x varchar2(10)

-- This works because of the context switch to SQL
v9.2.0.1.0> exec select case when sys_op_map_nonnull(null)=sys_op_map_nonnull(null) then 'Y' else 'N' end into :x from dual;

PL/SQL procedure successfully completed.


X
--------------------------------
Y

-- This fails because PL/SQL doesn't support it - yet?!
v9.2.0.1.0> exec :x := sys_op_map_nonnull('a');
BEGIN :x := sys_op_map_nonnull('a'); END;

*
ERROR at line 1:
ORA-06550: line 1, column 13:
PLS-00201: identifier 'SYS_OP_MAP_NONNULL' must be declared
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored

The original post was asking for the best way to perform comparisons in PL/SQL (a trigger), so this function cannot be used there. But it can be used in SQL when needed.

Do you have any further thoughts on it?

Thanks, Kurt

Tom Kyte
December 03, 2003 - 6:56 am UTC


- it's undocumented, and therefore poses a risk of going away or changing
functionality


that is enough said that should make people just "stop reading"

go there and you might be really mad in the next release.


the only CORRECT way is:

where (a = b or (a is null and b is null))


period. that is my thoughts on it. I would not consider using sys_op_map_nonnull, ignore that man behind the curtain.

the day it appears in the sql reference guide -- go for it (in all releases).

For exmaple, i never recommended alter session set current_schema UNTIL it was documented in 8i. then I said "use it all over the place"



YUK to 'SYS_OP_MAP_NONNULL'

Gary, December 03, 2003 - 1:25 am UTC

You can use DUMP to the same effect, and at least that returns a VARCHAR2 instead of a RAW and it is documented as well.
Another 'benefit' of using DUMP to compare values is that if the datatype is different, the match fails, so you are compelled to do explicit datatype conversion.

Still doesn't work in PL/SQL



DUMP -my mistake

Gary, December 03, 2003 - 1:30 am UTC

Realised that DUMP does return a NULL. I've just got my own function replaceing itthat does an NVL(DUMP(...),'NULL').



Tom Kyte
December 03, 2003 - 7:01 am UTC

that aside, you don't need dump to do that compare:

ops$tkyte@ORA9IR2> select a, b, decode( a, b, 'Equal', 'Not Equal' )
  2    from t;
 
         A          B DECODE(A,
---------- ---------- ---------
         1          1 Equal
         1          2 Not Equal
         1            Not Equal
                    2 Not Equal
                      Equal


decode itself will do that.  not in PLSQL tho...

In plsql, just write an "is_equal" function that does the right thing 

is there a big difference between this and nvl?

pete, December 04, 2003 - 9:42 am UTC

what is the difference between using:

where (a = b or (a is null and b is null))

and:

where nvl(a, '!') = nvl(b, '!')

- providing that '!' is something which will never ever appear in your columns?

thanks,
pete

Tom Kyte
December 04, 2003 - 10:00 am UTC

providing you are 10000% sure that '!' will *never* appear (which almost always turns out to be a false assumption at some point in time)


it is OK. I have no problems with

where (a = b or (a is null and b is null))

though (it really isn't that much more typing)


one advantage that nvl() may have is you can index it and search using the index.

Alvaro - Another way

Alton, December 04, 2003 - 10:29 am UTC

I do this in my audit triggers:
IF new.colA || '!' <> old.colA || '!' THEN
do_audit...
END IF;

Here's an actual trigger...
BEGIN
<<audit>>
DECLARE
v_event_id NUMBER;
v_table VARCHAR2(30) := 'AUTHORIZATION_CODES';
v_operation VARCHAR2(15);
v_recid VARCHAR2(60);
v_module VARCHAR2(64);
--
v_first BOOLEAN := TRUE;
--
PROCEDURE audit_detail(
pi_column VARCHAR2,
pi_before VARCHAR2,
pi_after VARCHAR2
) IS
BEGIN
IF pi_before || '!' <> pi_after || '!' THEN
IF v_first THEN
SELECT program
INTO v_module
FROM v$session
WHERE sid = (SELECT sid FROM v$mystat WHERE rownum = 1 );
--
-- Insert the log record
INSERT INTO auditlog (event_id, table_name, operation, record_id, module_name)
VALUES (spsseq.NEXTVAL, v_table, v_operation, v_recid, v_module)
RETURNING event_id INTO v_event_id;
--
v_first := FALSE;
END IF;
INSERT INTO audit_detail(event_id, column_name, before_data, after_data)
VALUES (v_event_id, pi_column, pi_before, pi_after);
--
END IF;
END audit_detail;
--
BEGIN
IF INSERTING THEN
v_operation := 'INSERT';
v_recid := :new.CODE;
ELSIF UPDATING THEN
v_operation := 'UPDATE';
v_recid := :new.CODE;
ELSE
v_operation := 'DELETE';
v_recid := :old.CODE;
END IF;
--
-- Insert audit details
--
audit_detail('CODE', :old.CODE, :new.CODE);
--
audit_detail('DESCRIPTION', :old.DESCRIPTION, :new.DESCRIPTION);
--
audit_detail('ABBREVIATION', :old.ABBREVIATION, :new.ABBREVIATION);
--
audit_detail('DENIED_GROUP', :old.DENIED_GROUP, :new.DENIED_GROUP);
END audit;
EXCEPTION
WHEN OTHERS THEN
DECLARE
err_number NUMBER := SQLCODE;
err_message VARCHAR2(2000) := SUBSTR(SQLERRM, 1, 2000);
BEGIN
INSERT INTO activitylog (activity_id, message_type, program, error_number, description)
VALUES(-20000, 'E', 'authorization_codes_audit', err_number, err_message);
END;
END;


Tom Kyte
December 04, 2003 - 10:41 am UTC

I think its worth the tiny bit of typing it takes.

declare
nullstr1 varchar2(20);
nullstr2 varchar2(20);
notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
notnullnum1 number := 1;
notnullnum2 number := 1;
notnullnum3 number := 2;
begin
for i in 1 .. 500000
loop
if ( nullstr1 = nullstr2 or (nullstr1 is null and nullstr2 is null) ) then null; end if;
if ( notnullstr1 = notnullstr2 or (notnullstr1 is null and notnullstr2 is null) ) then null; end if;
if ( notnullstr1 = notnullstr3 or (notnullstr1 is null and notnullstr3 is null) ) then null; end if;
if ( notnullnum1 = notnullnum2 or (notnullnum1 is null and notnullnum2 is null) ) then null; end if;
if ( notnullnum1 = notnullnum3 or (notnullnum1 is null and notnullnum3 is null) ) then null; end if;
end loop;
end;

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.66 0.66 0 0 0 1
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 0.67 0.67 0 0 0 1
********************************************************************************
declare
nullstr1 varchar2(20);
nullstr2 varchar2(20);
notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
notnullnum1 number := 1;
notnullnum2 number := 1;
notnullnum3 number := 2;
begin
for i in 1 .. 500000
loop
if ( nullstr1||'!' = nullstr2||'!' ) then null; end if;
if ( notnullstr1||'!' = notnullstr2||'!' ) then null; end if;
if ( notnullstr1||'!' = notnullstr3||'!' ) then null; end if;
if ( notnullnum1||'!' = notnullnum2||'!' ) then null; end if;
if ( notnullnum1||'!' = notnullnum3||'!' ) then null; end if;
end loop;
end;

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 0 0 0 0
Execute 1 2.81 2.75 0 0 0 1
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 2.82 2.77 0 0 0 1
********************************************************************************
declare
nullstr1 varchar2(20);
nullstr2 varchar2(20);
notnullstr1 varchar2(20) := 'xxxxxxxxxxx';
notnullstr2 varchar2(20) := 'xxxxxxxxxxx';
notnullstr3 varchar2(20) := 'xxxxxxxxxxxy';
notnullnum1 number := 1;
notnullnum2 number := 1;
notnullnum3 number := 2;
begin
for i in 1 .. 500000
loop
if nvl(nullstr1,'!') = nvl( nullstr2,'!' ) then null; end if;
if nvl(notnullstr1,'!') = nvl( notnullstr2,'!' ) then null; end if;
if nvl(notnullstr1,'!') = nvl( notnullstr3,'!' ) then null; end if;
if nvl(notnullnum1,0) = nvl( notnullnum2,0 ) then null; end if;
if nvl(notnullnum1,0) = nvl( notnullnum3,0 ) then null; end if;
end loop;
end;

call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.09 0 0 0 0
Execute 1 7.06 6.93 0 0 0 1
Fetch 0 0.00 0.00 0 0 0 0
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 2 7.07 7.03 0 0 0 1

Re: Alvaro - Another way

Alton, December 04, 2003 - 11:56 am UTC

Tom,
I don't understand your response... I'm not using NVL anywhere.

Tom Kyte
December 04, 2003 - 12:27 pm UTC

i know

i tested

a) my way
b) your way
c) their way (see above, the NVL() ideas)

just to give an overview of "the ways" and their respective performance.

Sorry... Didn't see my way.

A reader, December 04, 2003 - 12:36 pm UTC

I think I'll be changing some code soon. Your way is fastest. Thanks.

How about this (9i and above)?

Dominic, April 08, 2005 - 4:58 pm UTC

coalesce(nullif(l_old,l_new),nullif(l_new,l_old))

Returns a non-null result if they are different,
a null result if they are "the same" (assuming you want NULLs to be the same as NULLs)

:-)

Tom Kyte
April 08, 2005 - 5:34 pm UTC

decode( l_old, l_new, 1, 0 )

too... in SQL...

Anders, April 09, 2005 - 6:32 am UTC

Any chance we will see eg. "v1

==

Anders, April 09, 2005 - 6:34 am UTC

(Something went very wrong above, sorry)
Any chance we will see eg. "v1 == v2" or some similair operator as a short hand (well, sort of, sinse it would only evaluate its arguments once) for "(v1=v2 or (v1 is null and v2 is null))". It's one of the many little things that would make Oracle easier to use.

Tom Kyte
April 09, 2005 - 7:54 am UTC

all enhancement requests -- go through metalink.

Not that I'm aware of.

SYS_OP_MAP_NONNULL

Sean Mac GC, August 09, 2006 - 12:03 pm UTC

Hello Tom,

Fully respecting your severe reservations in relation to the undocumented SYS_OP_MAP_NONNULL operator, however... this is a function that Oracle has been using for fast Materialized View refreshes since at least version 8i (insofar as I am aware, and apologies for coming late to the thread).

There must be reasons as to why it is, as yet, undocumented, but isn't it a very risky strategy to employ an operator that is 'unsafe' for public consumption, yet safe enough to use with summary data? I.e., if there are any doubts whatsoever about the safety/accuracy/dependability of this operator, should it be used on any data operations, given that the data it will be operating on are entirely at the customer's discretion, and all that that could conceivably entail?

Great site and great work.

Tom Kyte
August 09, 2006 - 1:08 pm UTC

that is OK - that they use it internally.

for they are free to let "bugs" live if they are not affected by them...

for they are free to change drastically the behaviour of the function, because they own it, it is theirs.


It is not RISKY to employ an internal, undocumented function. They "own" it and can it do whatever they need it to do. A patch set could radically change the way it functions - in a way that doesn't affect them, but does affect you.

Caveat Emptor.

SYS_OP_MAP_NONNULL

Tim, August 09, 2006 - 1:44 pm UTC

Sean,
The part I would disagree with you and agree with Tom - would be on your assumption that this is not documented due to "...doubts whatsoever about the safety/accuracy/dependability..."
If this function was not working well for them (Oracle internal developers) - I fully trust that the Oracle QA team would not let them use it. (Obviously anything can have a bug - but you know what I get at.) There can be many reasons for not documenting something. Once something is documented and you know that Oracle developers are now using it - this means that Oracle must now maintain backward compatability forever (or risk backlash) on this function. That has the potential to limit flexibility on future enhancements. Also - Oracle would now have to be concerned with all the possible data combinations that developers could be throwing at it - while currently they are well aware of exactly how it is being used.

So - just because it is not documented does not mean it is not doing an excellent job at the internal function. However - likewise - just because it is doing an excellent job at the internal function - does not necessarily mean it will work well for you in your development now - or in the future.

On the other hand - if you test well - are confident of the data you are sending to it falls well within your test cases and you are planning on retesting when the Oracle binaries change - perhaps it is reasonable to decide if this is a risk level you are comfortable with.

Biggest thing is to be sure you are making a well informed decision.

SYS_OP_MAP_NONNULL

Sean Mac GC, August 09, 2006 - 3:10 pm UTC

Fair points Tim, PA, wouldn't disagree with you on those (or Tom's).

Re Oracle's use of them with MV fast refreshes: they're used with MERGE operations, on the criteria matching specifically, and that's where I would consider using them myself, with appropriate circumspection!

<> operator

Priya, September 25, 2006 - 5:24 pm UTC

Hi Tom,
I have a query that says "Select * from emp where emplid <> '123'"
How come this query doesn't return rows that contain a null value for emplid? does the <> operator behave like a not in clause?

Appreciate your valuable advice as always.

Thanks,
Priya.

Tom Kyte
September 26, 2006 - 2:26 am UTC

It is not "known" if emplid <> NULL

tri valued logic


ops$tkyte%ORA10GR2> select * from dual where null = null;

no rows selected

ops$tkyte%ORA10GR2> select * from dual where null <> null;

no rows selected

ops$tkyte%ORA10GR2> select * from dual where null is null;

D
-
X



"where emplid <> 123 OR emplid is null" 

null and !=

A reader, January 16, 2007 - 9:34 am UTC

Hi

I am counting emp table those which dont have a specifici commision such as

select * from emp
where comm != 300

It returns 4 rows, I wonder what has happened wiith nulls? Isnt null != 300?

TIA

PinguSan

NUlls and Zero-length strings

Mark, August 09, 2007 - 9:28 am UTC

Hi Tom,

Long Time No Post!

Here is strange behavior:
DECLARE
BEGIN
DBMS_OUTPUT.enable;

IF UPPER(TRIM(NULL)) = UPPER(TRIM(NULL))
THEN
DBMS_OUTPUT.put_line(' NULLS EQUAL');
ELSE
DBMS_OUTPUT.put_line(' NULLS NOT EQUAL');
END IF;

IF UPPER(TRIM('')) = UPPER(TRIM(''))
THEN
DBMS_OUTPUT.put_line(' ZLS EQUAL');
ELSE
DBMS_OUTPUT.put_line(' ZLS NOT EQUAL');
END IF;

IF UPPER(TRIM('')) = UPPER(TRIM(NULL))
THEN
DBMS_OUTPUT.put_line(' ZLS/NULL EQUAL');
ELSE
DBMS_OUTPUT.put_line(' ZLS/NULL NOT EQUAL');
END IF;
END;
/

NULLS NOT EQUAL
ZLS NOT EQUAL
ZLS/NULL NOT EQUAL

I understand the first test would be NOT EQUAL because you need to use IS or IS NOT when comparing NULL. But the second Zero-Length String compare does not make sense to me. The third case makes sense to me.

I give you this because we use the UPPER(TRIM()) for string comparisons in some of our code, and I am doing a QA check on it, and it did not return what I expected it to.


Tom Kyte
August 14, 2007 - 10:47 am UTC

ops$tkyte%ORA10GR2> select * from dual where '' = '';

no rows selected

ops$tkyte%ORA10GR2> select * from dual where '' is null;

D
-
X



zero length strings are null

Zero length strings...

Duke Ganote, August 14, 2007 - 11:11 am UTC

Using 'decode'

Peter, April 07, 2012 - 11:06 am UTC

In the docs of the decode function you can read:

"In a DECODE function, Oracle considers two nulls to be equivalent."

Now this is exactly what the original poster cas use:

If (updating and decode(:old.field,:new.field,1,0)=0) Then
( Insert into auditing_tables )
End If;


Tom Kyte
April 08, 2012 - 5:46 pm UTC

did you actually type that in and test it before you posted?

or are you guessing it would probably work?

I know what the answer to my question is.... Now... check your work :)

In short, give a full example - demonstrating that what you say is probably true - sort of like I do all of the time....

A reader, April 10, 2012 - 10:32 pm UTC

Quick test of NULL in decode

scott@DEV> l
  1  select decode (NULL, NULL,'NULL equiv2 NULL in Decode',
  2*             'NULL not equiv2 NULL in Decode') from dual
scott@DEV> /

DECODE(NULL,NULL,'NULLEQUI
--------------------------
NULL equiv2 NULL in Decode


Tom Kyte
April 11, 2012 - 11:28 am UTC

no, I mean, show us in plsql please.

show us this actually working:

If (updating and decode(:old.field,:new.field,1,0)=0) Then
 (  Insert into auditing_tables )
End If;


you cannot

ops$tkyte%ORA11GR2> exec dbms_output.put_line( decode (NULL, NULL,'NULL equiv2 NULL in Decode', 'NULL not equiv2 NULL in Decode') ); 
BEGIN dbms_output.put_line( decode (NULL, NULL,'NULL equiv2 NULL in Decode', 'NULL not equiv2 NULL in Decode') ); END;

                            *
ERROR at line 1:
ORA-06550: line 1, column 29:
PLS-00204: function or pseudo-column 'DECODE' may be used inside a SQL
statement only
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored




that was my point directly above. I'm well aware that decode treats null=null - but it doesn't help us here.

My favorite elegant way

xelay, June 18, 2013 - 4:48 am UTC

The shortest way i prefer to use is:

if a||'!' <> b||'!' then /*your audit*/ end if;

it works well with different data types:

if 'a'||'!' <> null||'!' then /*your audit*/ end if;
if sysdate||'!' <> null||'!' then /*your audit*/ end if;
if 1||'!' <> 2||'!' then /*your audit*/ end if;
if 1||'!' <> 2||'!' then /*your audit*/ end if;
if null||'!' = null||'!' then /*your audit*/ end if;
if sysdate||'!' = sysdate||'!' then /*your audit*/ end if;
if '1'||'!' = 1||'!' then /*your audit*/ end if;

My favorite elegant way

xelay, June 18, 2013 - 5:41 am UTC

... and more shortest:

if '0'||1 = null||1 then /* your audit */ end if;

but remember that '0.1'||1 = 0.1||1 is false.
delimiter may be converted in various ways, leading 0 may be omitted

NVL & SYSTIMESTAMP

Manny, October 17, 2013 - 4:41 pm UTC

We have a query of the following format-

SELECT <<column1>>
, <<column2>>
, <<column2>>
, .
, .
, .
, .
, .
, SYSTIMESTAMP
FROM table1
, table2
WHERE NVL(table1.insert_timestamp, SYSTIMESTAMP) = NVL(table2.insert_timestamp, SYSTIMESTAMP)
.
.
.
.
AND NVL(table1.update_timestamp, SYSTIMESTAMP) = NVL(table2.update_timestamp, SYSTIMESTAMP)
.
.
.

AND NVL(table1.work_timestamp, SYSTIMESTAMP) = NVL(table2.work_timestamp, SYSTIMESTAMP);

In this case will the SYSTIMESTAMP function be called multiple times or only once?? If multiple times then I suppose using a Constant in place of SYSTIMESTAMP may help in terms of Performance.

Also is this the correct approach for comparing NULLable values.
Tom Kyte
November 01, 2013 - 8:14 pm UTC

sysdate/systimestamp are statement level deterministic.

they are deterministic within that single sql statement - they'll always return the same value in an execution of that single sql statement.



Be careful with ||'!' <> ...

Falco, April 09, 2014 - 8:54 am UTC

Followup to the trick with appending '!'

This will trick you if using Dates with time-part...

SELECT * FROM DUAL WHERE (sysdate+0.1)||'!' = (sysdate)||'!';

Because without explicit conversion to_char... you lose all precision behind days (or even variying precision depending on locale setting? So your compares would be true sometimes and false other times for the same values!? nightmare :-X)

I am now writing a bunch of overloaded functions for all data-types: compare(x,y) which return true/false for the comparision including NULL = NULL

Revisit SYS_OP_MAP_NONNULL

Aaron, October 12, 2015 - 4:50 pm UTC

Hi,

Some time has passed. Now SYS_OP_MAP_NONNULL has a mention in the 12c documentation but not full inclusion in the language reference.

At what point would you feel better about using it?
Chris Saxon
October 13, 2015 - 1:46 am UTC

Well, if you are (planning on) using SYS_OP_MAP_NONNULL, then we are talking about using in SQL (not PLSQL).

So perhaps maybe be safe than sorry, use decode - its even more succinct.

SQL> with t as (
  2    select 1 x,    1    y from dual union all
  3    select 1 x,    2    y from dual union all
  4    select 1 x,    null y from dual union all
  5    select null x, null y from dual )
  6  select x,y,decode(x,y,'Y') pseudo_equal from t;

         X          Y P
---------- ---------- -
         1          1 Y
         1          2
         1
                      Y


But yes, I would like to see SYS_OP_MAP_NONNULL supported and in the documentation as well.

SYS_OP_MAP_NONNULL not longer required

A reader, July 29, 2020 - 4:33 pm UTC

Hi,

It seems that SYS_OP_MAP_NONNULL is no longer required.
With 19c, I didn't check it on another version, DECODE is converted in a join operation depending on the column definition.

CREATE TABLE t1 (c_not_n NUMBER NOT NULL, c_null NUMBER);

SELECT *
FROM t1 a, t1 b
WHERE decode(a.c_null,b.c_null,1)=1;

=> access(SYS_OP_MAP_NONNULL(A.C_NULL)=SYS_OP_MAP_NONNULL(B.C_NULL))

SELECT *
FROM t1 a, t1 b
WHERE decode(a.c_not_n,b.c_null,1)=1;

=> access(A.C_NOT_N=B.C_NULL)

Regards Maurice
Connor McDonald
July 30, 2020 - 12:31 am UTC

Nice to know - thanks for posting this.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library