6.4.3. Expressions involving NULL

In SQL, NULL is not a value. It is a condition, or state, of a data item, in which its value is unknown. Because it is unknown, NULL cannot behave like a value. When you try to perform arithmetic on NULL, or involve it with values in other expressions, the result of the operation will almost always be NULL. It is not zero or blank or an “empty string” and it does not behave like any of these values.

Below are some examples of the types of surprises you will get if you try to perform calculations and comparisons with NULL.

The following expressions all return NULL:

  • 1 + 2 + 3 + `NULL`

  • not (NULL)

  • 'Home ' || 'sweet ' || NULL

You might have expected 6 from the first expression and “Home sweet” from the third, but as we just said, NULL is not like the number 0 or an empty string — it’s far more destructive!

The following expression:

  1. FirstName || ' ' || LastName

will return NULL if either FirstName or LastName is NULL. Otherwise it will nicely concatenate the two names with a space in between — even if any one of the variables is an empty string.

Think of NULL as UNKNOWN and these strange results suddenly start to make sense! If the value of Number is unknown, the outcome of ‘1 + 2 + 3 + Number’ is also unknown (and therefore NULL). If the content of MyString is unknown, then so is ‘MyString || YourString’ (even if YourString is non-NULL). Etcetera.

Now let’s examine some PSQL (Procedural SQL) examples with if-constructs:

  • Equals (‘=’)

    1. if (a = b) then
    2. MyVariable = 'Equal';
    3. else
    4. MyVariable = 'Not equal';

    After executing this code, MyVariable will be 'Not equal' if both a and b are NULL. The reason is that a = b yields NULL if at least one of them is NULL. If the test expression of an “if” statement is NULL, it behaves like false: the ‘then’ block is skipped, and the ‘else’ block executed.

    Although the expression may behave like false in this case, it’s still NULL. If you try to invert it using not(), what you get is another NULL — not “true”.

  • Not equals (‘<>’)

    1. if (a <> b) then
    2. MyVariable = 'Not equal';
    3. else
    4. MyVariable = 'Equal';

    Here, MyVariable will be 'Equal' if a is NULL and b isn’t, or vice versa. The explanation is analogous to that of the previous example.

The DISTINCT keyword comes to the rescue!

Firebird 2 and above implement a new use of the DISTINCT keyword allowing you to perform (in)equality tests that take NULL into account. The semantics are as follows:

  • Two expressions are DISTINCT if they have different values or if one is NULL and the other isn’t;

  • They are NOT DISTINCT if they have the same value or if they are both NULL.

Notice that if neither operand is NULL, DISTINCT works exactly like the “<>” operator, and NOT DISTINCT like the “=” operator.

DISTINCT and NOT DISTINCT always return true or false, never NULL.

Using DISTINCT, you can rewrite the first PSQL example as follows:

  1. if (a is not distinct from b) then
  2. MyVariable = 'Equal';
  3. else
  4. MyVariable = 'Not equal';

And the second as:

  1. if (a is distinct from b) then
  2. MyVariable = 'Not equal';
  3. else
  4. MyVariable = 'Equal';

These versions will give you the results that a normal (i.e. not SQL-brainwashed) human being would expect, whether there are NULLs involved or not.

More about NULLs

A lot more information about NULL behaviour can be found in the Firebird Null Guide, at these locations:

https://www.firebirdsql.org/file/documentation/html/en/firebirddocs/nullguide/firebird-null-guide.html (HTML)
https://www.firebirdsql.org/file/documentation/pdf/en/firebirddocs/nullguide/firebird-null-guide.pdf (PDF)