views:

1730

answers:

9

I wish to search a database table on a nullable column. Sometimes the value I'm search for is itself NULL. Since Null is equal to nothing, even NULL, saying

where MYCOLUMN=SEARCHVALUE

will fail. Right now I have to resort to

where ((MYCOLUMN=SEARCHVALUE) OR (MYCOLUMN is NULL and SEARCHVALUE is NULL))

Is there a simpler way of saying that?

(I'm using Oracle if that matters)

+1  A: 

Try WHERE NVL(mycolumn,'NULL') = NVL(searchvalue,'NULL')

DCookie
what happens if mycolumn is null but searchvalue is 'NULL'; or vice versa?
dlamblin
The resolution of this comment is left as an exercise to the reader ;-)
DCookie
+5  A: 

Use NVL to replace null with some dummy value on both sides, as in:

WHERE NVL(MYCOLUMN,0) = NVL(SEARCHVALUE,0)
JosephStyons
what happens if mycolumn is null but searchvalue is 0; or vice versa?
dlamblin
If 0 is something that can occur in your data or in the search value, then you need to use a different dummy value. If you can't come up with a dummy value that is appropriate, you can't use this approach.
Dave Costa
You have no need to use this approach at all. There's no reason to compare to dummy values when you can compare directly to actual NULL.
Andy Lester
Null is not equal to null.
JosephStyons
+8  A: 
Chris Shaffer
You got there just before me...
Galghamon
The equivalent Oracle function is NVL. Same syntax.
JosephStyons
Just as an aside, you could do the same thing with Coalesce(MyColumn, -1) = Coalesce(SearchValue, -1).
Craig
I'm not entirely comfortable with the proposed criteria for the selection of the NULL replacement value. Is the criteria "not likely to be found" sufficient? What happens when an 'unlikely' value appears on either side of the equality test. Is it now acceptable to match a NULL with a an 'unlikely' value?
spencer7593
This falls apart if either value could legitimately be -1, and then you'll be pulling out your hair to find the bug.
Andy Lester
+28  A: 

You can do the IsNull or NVL stuff, but it's just going to make the engine do more work. You'll be calling functions to do column conversions which then have to have the results compared.

Use what you have

where ((MYCOLUMN=SEARCHVALUE) OR (MYCOLUMN is NULL and SEARCHVALUE is NULL))
Andy Lester
A: 

I would think that what you have is OK. You could maybe use:

where NVL(MYCOLUMN, '') = NVL(SEARCHVALUE, '')
Carl
This won't work because the empty string is equivalent to NULL in Oracle, which brings us back to the same NULL == NULL comparison.
Erick B
Damnit, I remember that little bit of stupidity now! I think that I'll leave this answer as a reminder to anyone else
Carl
+4  A: 

Another alternative, which is probably optimal from the executed query point of view, and will be useful only if you are doing some kind of query generation is to generate the exact query you need based on the search value.

Pseudocode follows.

if (SEARCHVALUE IS NULL) {
    condition = 'MYCOLUMN IS NULL'
} else {
    condition = 'MYCOLUMN=SEARCHVALUE'
}
runQuery(query,condition)
Vinko Vrsalovic
except that you should use a bind variable!
Dave Costa
Of course, that was just pseudocode ;)
Vinko Vrsalovic
+16  A: 

@Andy Lester asserts that the original form of the query is more efficient than using NVL. I decided to test that assertion:

    SQL> DECLARE
      2    CURSOR B IS
      3       SELECT batch_id, equipment_id
      4         FROM batch;
      5    v_t1  NUMBER;
      6    v_t2  NUMBER;
      7    v_c1  NUMBER;
      8    v_c2  NUMBER;
      9    v_b   INTEGER;
     10  BEGIN
     11  -- Form 1 of the where clause
     12    v_t1 := dbms_utility.get_time;
     13    v_c1 := dbms_utility.get_cpu_time;
     14    FOR R IN B LOOP
     15       SELECT COUNT(*)
     16         INTO v_b
     17         FROM batch
     18        WHERE equipment_id = R.equipment_id OR (equipment_id IS NULL AND R.equipment_id IS NULL);
     19    END LOOP;
     20    v_t2 := dbms_utility.get_time;
     21    v_c2 := dbms_utility.get_cpu_time;
     22    dbms_output.put_line('For clause: WHERE equipment_id = R.equipment_id OR (equipment_id IS NULL AND R.equipment_id IS NULL)');
     23    dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
     24    dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);
     25  
     26  -- Form 2 of the where clause
     27    v_t1 := dbms_utility.get_time;
     28    v_c1 := dbms_utility.get_cpu_time;
     29    FOR R IN B LOOP
     30       SELECT COUNT(*)
     31         INTO v_b
     32         FROM batch
     33        WHERE NVL(equipment_id,'xxxx') = NVL(R.equipment_id,'xxxx');
     34    END LOOP;
     35    v_t2 := dbms_utility.get_time;
     36    v_c2 := dbms_utility.get_cpu_time;
     37    dbms_output.put_line('For clause: WHERE NVL(equipment_id,''xxxx'') = NVL(R.equipment_id,''xxxx'')');
     38    dbms_output.put_line('CPU seconds used: '||(v_c2 - v_c1)/100);
     39    dbms_output.put_line('Elapsed time: '||(v_t2 - v_t1)/100);
     40  END;
     41  /


    For clause: WHERE equipment_id = R.equipment_id OR (equipment_id IS NULL AND R.equipment_id IS NULL)
    CPU seconds used: 84.69
    Elapsed time: 84.8
    For clause: WHERE NVL(equipment_id,'xxxx') = NVL(R.equipment_id,'xxxx')
    CPU seconds used: 124
    Elapsed time: 124.01

    PL/SQL procedure successfully completed

    SQL> select count(*) from batch;

  COUNT(*)
----------
     20903

SQL>

I was kind of surprised to find out just how correct Andy is. It costs nearly 50% more to do the NVL solution. So, even though one piece of code might not look as tidy or elegant as another, it could be significantly more efficient. I ran this procedure multiple times, and the results were nearly the same each time. Kudos to Andy...

DCookie
Nicely done... I may have to borrow some of that, just for the benchmark framework.
James Curran
Also consider that doing those conversions means that the engine cannot use an index if there's one on the column.For example, doing this: where ? = foo + 1cannot use an index on foo, but where ? - 1 = foocan use the index. You'll run into this with date indexes a lot.
Andy Lester
A: 

If an out-of-band value is possible:

where coalesce(mycolumn, 'out-of-band') 
    = coalesce(searchvalue, 'out-of-band')
Ted
A: 

This can also do the job

WHERE MYCOLUMN || 'X'  = SEARCHVALUE || 'X'

There are some situations where it beats the IS NULL test with the OR

EvilTeach
In standard SQL, concatenation with a NULL yields NULL, so you would end up with NULL = NULL, which isn't true. Does Oracle allow that to work?
Jonathan Leffler
Ya, I've used it in oracle successfully.
EvilTeach