Search code examples
sqlpostgresqlnullrowplpgsql

IS NOT NULL test for a record does not return TRUE when variable is set


Using a plpgsql procedure to extract a record if it exists, and then if it does, do something with it.

The variable is a rowtype:

my_var my_table%rowtype;

I populate it with a SQL statement:

select * from my_table where owner_id = 6 into my_var;

I know it definitely has the row:

raise notice 'my_var is %', my_var;

Returns:

NOTICE:  my_var is (383,6,10)

But now I want to test that it got the record and BOTH of these if conditions fail:

if my_var is null then
  raise notice 'IT IS NULL';
end if;
if my_var is not null then
  raise notice 'IT IS NOT NULL';
end if;

Neither of these raises appear in my messages log - it just never enters the blocks. What's the correct way to test if you received a row from a SELECT * INTO?


Solution

  • I see two possible reasons, why ...

    Neither of these raises appear in my messages log

    Not logged

    Firstly, a NOTICE is not normally written to the database log with default settings. The manual:

    log_min_messages (enum)

    Controls which message levels are written to the server log. Valid values are DEBUG5, DEBUG4, DEBUG3, DEBUG2, DEBUG1, INFO, NOTICE, WARNING, ERROR, LOG, FATAL, and PANIC. (...)
    The default is WARNING. Note that LOG has a different rank here than in client_min_messages.

    Bold emphasis mine. Also note the different default (NOTICE) for client_min_messages (preceding item in the manual).

    Invalid test

    Secondly, consider how a row expression is evaluated. A test row_variable IS NULL returns TRUE if (and only if) every single element is NULL. Consider:

    SELECT (1, NULL) IS NULL     AS a  -- FALSE
         , (1, NULL) IS NOT NULL AS b  -- also FALSE!
    

    Both expressions return FALSE. In other words, a row (or record) variable (1, NULL) is neither NULL, nor is it NOT NULL. Therefore, both of your tests fail.

    sqlfiddle with more details

    Related:

    You can even assign a record variable with NULL (rec := NULL), which results in every element being NULL - if the type is a well-known row type. Otherwise, we are dealing with an anonymous record and the structure is undefined and you cannot access elements to begin with. But that's not the case with a rowtype like in your example (which is always well-known).

    Solution: FOUND

    What's the correct way to test if you received a row from a SELECT * INTO?

    You have to consider that the row could be NULL, even if it was assigned. The query could very well have returned a bunch of NULL values (if the table definition in your query allows NULL values).

    There is a simple and secure approach. Use GET DIAGNOSTICS or, where applicable, the special variable FOUND:

    SELECT * FROM my_table WHERE owner_id = 6 INTO my_var;
    
    IF NOT FOUND THEN
       RAISE NOTICE 'Query did not return a row!';
    END IF;
    

    Details in the manual.