msgbartop
News, views, tips and tricks on Oracle and other fun stuff
msgbarbottom

Cool Undocumented SQL Function SYS_OP_MAP_NONNULL and Some Alternatives

In Oracle, NULL does not equal NULL:

  EDDIE@XE> SELECT 'hi there'
    2    FROM DUAL
    3   WHERE NULL = NULL;

  no rows selected

But sometimes, if not most of the times, you do want the condition NULL = NULL to return true. For example:

  EDDIE@XE> CREATE TABLE t (
    2  col1  VARCHAR2(10),
    3  col2  VARCHAR2(10));

  Table created.

  EDDIE@XE> INSERT INTO t VALUES ('monkey', NULL);

  1 row created.

  EDDIE@XE> INSERT INTO t VALUES ('monkey', 'chicken');

  1 row created.

  EDDIE@XE> INSERT INTO t VALUES ('monkey', 'monkey');

  1 row created.

  EDDIE@XE> INSERT INTO t VALUES (NULL, 'monkey');

  1 row created.

  EDDIE@XE> INSERT INTO t VALUES (NULL, NULL);

  1 row created.

  EDDIE@XE> COMMIT;

  Commit complete.

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t;

  COL1       COL2
  ---------- ----------
  monkey     I am null
  monkey     chicken
  monkey     monkey
  I am null  monkey
  I am null  I am null

Now, I want to select all the records from table t where col1 equals col2:

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t
    4   WHERE col1 = col2;

  COL1       COL2
  ---------- ----------
  monkey     monkey

As expected, the row that has NULL in both col1 and col2 was not returned. You want this record returned. Here is how you can do it:

Using the undocumented function SYS_OP_MAP_NONNULL:

This function has been around for a while, but is still undocumented as of the latest Oracle database release (10gR2). Being undocumented means that it should not be used in production code, but it does not hurt to know that it exists and explore how it works. This function makes it possible to have NULL = NULL:

  EDDIE@XE> SELECT sys_op_map_nonnull (NULL)
    2    FROM DUAL;

  SY
  --
  FF

  EDDIE@XE> SELECT 'hi there'
    2    FROM DUAL
    3   WHERE sys_op_map_nonnull (NULL) = sys_op_map_nonnull (NULL);

  'HITHERE
  --------
  hi there

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t
    4   WHERE sys_op_map_nonnull(col1) = sys_op_map_nonnull(col2);

  COL1       COL2
  ---------- ----------
  monkey     monkey
  I am null  I am null

Using simple logical operators:

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t
    4   WHERE (col1 = col2 OR (col1 IS NULL AND col2 IS NULL));

  COL1       COL2
  ---------- ----------
  monkey     monkey
  I am null  I am null

Using DECODE:

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t
    4   WHERE DECODE (col1, col2, 'match', 'no match') = 'match';

  COL1       COL2
  ---------- ----------
  monkey     monkey
  I am null  I am null

Using NVL:

  EDDIE@XE> SELECT NVL (col1, 'I am null') col1,
    2         NVL (col2, 'I am null') col2
    3    FROM t
    4   WHERE NVL (col1, 'impossible value') = NVL (col2, 'impossible value');

  COL1       COL2
  ---------- ----------
  monkey     monkey
  I am null  I am null

Of course, in this case, you have to be absolutely, positively 100 % sure that col1 and col2 will never have a value of ‘impossible value’ in them. Moreover, instead of NVL, you can also use similar functions.

Sources and Resources:

Filed in Oracle, Tips with 8 Comments | Tags: , , ,


One more reason to upgrade your Oracle 8i

On my Oracle database 8i (8.1.7.4) instance, I ran the following:

SQL> SELECT 1 my_number
  2    FROM DUAL
  3  UNION
  4  SELECT NULL my_number
  5    FROM DUAL
  6  /
SELECT 1 my_number
       *
ERROR at line 1:
ORA-01790: expression must have same datatype as corresponding expression


SQL> SELECT SYSDATE my_date
  2    FROM DUAL
  3  UNION
  4  SELECT NULL my_date
  5    FROM DUAL
  6  /
SELECT SYSDATE my_date
       *
ERROR at line 1:
ORA-01790: expression must have same datatype as corresponding expression

What’s up with the ORA-01790 error?

As you know, NULL doesn’t have a data type, and NULL can be used in place of a value of any data type. If you purposely select NULL as a column value in a union query, Oracle no longer has two datatypes to compare to see whether the union queries are compatible. As you can see from the above queries, this is an issue for the DATE and NUMBER datatypes in Oracle 8i . However, Oracle does not have a problem with character datatypes:

SQL> SELECT 'C' my_char
  2    FROM DUAL
  3  UNION
  4  SELECT NULL my_char
  5    FROM DUAL
  6  /

M
-
C

To solve this issue, you have two options:

Option 1: Explicitly convert NULL to the right datatype:

SQL> SELECT 1 my_number
  2    FROM DUAL
  3  UNION
  4  SELECT TO_NUMBER (NULL) my_number
  5    FROM DUAL
  6  /

 MY_NUMBER
----------
         1


SQL> SELECT SYSDATE my_date
  2    FROM DUAL
  3  UNION
  4  SELECT TO_DATE (NULL) my_date
  5    FROM DUAL
  6  /

MY_DATE
---------
15-JUN-06

Option 2: Upgrade your Oracle database to 9i or above. Oracle 9i, and later releases, are “smart enough” to know which flavor of NULL to use. I executed the following in 9.2.0.6:

SQL> SELECT 1 my_number
  2    FROM DUAL
  3  UNION
  4  SELECT NULL my_number
  5    FROM DUAL
  6  /

 MY_NUMBER
----------
         1


SQL> SELECT SYSDATE my_date
  2    FROM DUAL
  3  UNION
  4  SELECT NULL my_date
  5    FROM DUAL
  6  /

MY_DATE
---------
15-JUN-06

Go for option 2, if you have not done so already.

Filed in Oracle, Tips with 2 Comments | Tags: ,


A binary flag with three values

While troubleshooting a problem in the Order entry module of Oracle Applications (11.0.3), I query the column open_flag in the order header table so_headers_all for the order number in question. The column open_flag is supposed to be a Y/N flag that tells me whether the order is open or closed. Instead, for this order, the open_flag is NULL.

Come on! how am I supposed to know the meaning of NULL here? Does null mean ‘Y’ (order open) or ‘N’ (order closed). There should have been a NOT NULL and a CHECK constraint on the open_flag column.

In my definition, a flag is supposed to have two, and only two, values, Y/N, 0/1… Why allow a flag to have a third value, NULL? It only adds to the confusion and is definitely a bad design.

Relying on a NULL to give a meaning to a column is a bad practice.

Filed in Oracle with 5 Comments | Tags: ,


NVL, NVL2 or COALESCE?

In Oracle, as in other RDBMSs, nulls should be given a special treatment. In Oracle, there are three (maybe more?) functions that deal with nulls, NVL, NVL2 and COALESCE. I would like to ask you a question, but first, let me quickly refresh my memory:

(more…)

Filed in Oracle with 12 Comments | Tags: , ,


NVL2 in Oracle

Did you know that in addition to the function NVL, Oracle also has the function NVL2? which is quite different than its cousin NVL. So, what does NVL2 do? (more…)

Filed in Oracle, Tips with 3 Comments | Tags: ,