Thread: Using null or not null in function arguments

Using null or not null in function arguments

From
Igor Katson
Date:
I have a row search function, smth like

CREATE OR REPLACE FUNCTION user_func.search_users
(i_city_id int, i_edu_id int, i_first_name text, i_last_name text,
limit_ int, offset_ int) RETURNS SETOF user.user AS $$
..... SELECT * FROM user WHERE
      city_id = i_city_id
...
$$ language plpgsql;

How do I write a function without complex logic, which will do:
a) If the input argument is NULL, then the corresponding select
statement will change from

column = arg
to
column IS NULL

maybe there is some built-in function for that?

b) If the input argument is NULL, then the corresponding select
statement will be removed, so if it was not written.

I think, this is a common problem.

Thanks in advance and regards,
Igor Katson.

Re: Using null or not null in function arguments

From
Raymond O'Donnell
Date:
On 23/01/2009 11:16, Igor Katson wrote:

> How do I write a function without complex logic, which will do:
> a) If the input argument is NULL, then the corresponding select
> statement will change from
>
> column = arg
> to
> column IS NULL

You could build your statement dynamically as a string, then execute it
using EXECUTE:

http://www.postgresql.org/docs/8.3/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN


> b) If the input argument is NULL, then the corresponding select
> statement will be removed, so if it was not written.

Likewise - something like

  if i_city_id is null then
    ... build statement...
    ... execute statement ...
  end if;

HTH,

Ray.


------------------------------------------------------------------
Raymond O'Donnell, Director of Music, Galway Cathedral, Ireland
rod@iol.ie
Galway Cathedral Recitals: http://www.galwaycathedral.org/recitals
------------------------------------------------------------------

Re: Using null or not null in function arguments

From
Igor Katson
Date:
Raymond O'Donnell wrote:
> On 23/01/2009 11:16, Igor Katson wrote:
>
>
>> How do I write a function without complex logic, which will do:
>> a) If the input argument is NULL, then the corresponding select
>> statement will change from
>>
>> column = arg
>> to
>> column IS NULL
>>
>
> You could build your statement dynamically as a string, then execute it
> using EXECUTE:
>
> http://www.postgresql.org/docs/8.3/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
>
>
>
>> b) If the input argument is NULL, then the corresponding select
>> statement will be removed, so if it was not written.
>>
>
> Likewise - something like
>
>   if i_city_id is null then
>     ... build statement...
>     ... execute statement ...
>   end if;
>
> HTH,
>
> Ray.
>
Thanks, Ray, but I see now, that I didn't explain the exact problem
correctly. The one is, that this kind of functions (search ones) can
have tens of arguments, and the more the amount of arguments is, the
more combinations of IF ... THEN conditionals will be present to build
the logic. If I have a couple of them, this can be easily handled
through IF THEN, or dynamic statements, but what if I have tens of 'em?

Resp.: Using null or not null in function arguments

From
Osvaldo Kussama
Date:
2009/1/23, Igor Katson <descentspb@gmail.com>:
> I have a row search function, smth like
>
> CREATE OR REPLACE FUNCTION user_func.search_users
> (i_city_id int, i_edu_id int, i_first_name text, i_last_name text,
> limit_ int, offset_ int) RETURNS SETOF user.user AS $$
> ..... SELECT * FROM user WHERE
>       city_id = i_city_id
> ...
> $$ language plpgsql;
>
> How do I write a function without complex logic, which will do:
> a) If the input argument is NULL, then the corresponding select
> statement will change from
>
> column = arg
> to
> column IS NULL
>
> maybe there is some built-in function for that?
>
> b) If the input argument is NULL, then the corresponding select
> statement will be removed, so if it was not written.
>
> I think, this is a common problem.
>


Try:

SET transform_null_equals ON;
at function's begining.
http://www.postgresql.org/docs/current/interactive/runtime-config-compatible.html#RUNTIME-CONFIG-COMPATIBLE-VERSION
18.12.2. Platform and Client Compatibility

Osvaldo

Re: Using null or not null in function arguments

From
Sam Mason
Date:
On Fri, Jan 23, 2009 at 02:16:34PM +0300, Igor Katson wrote:
> a) If the input argument is NULL, then the corresponding select
> statement will change from
>
> column = arg
> to
> column IS NULL

I think you want to use the IS [NOT] DISTINCT FROM operator.  It
works like the = and <> operators. i.e. the following expressions are
equivalent:

  x IS NOT DISTINCT FROM y

and

  CASE WHEN x IS NULL THEN y IS NULL
       ELSE COALESCE(x = y, FALSE) END


> b) If the input argument is NULL, then the corresponding select
> statement will be removed, so if it was not written.

not sure what you mean here, but maybe one of the existing suggestions
may help or the "RETURNS NULL ON NULL INPUT" flag when you're creating
the function may be what you're looking for.


--
  Sam  http://samason.me.uk/

Re: Using null or not null in function arguments

From
Igor Katson
Date:
Sam Mason wrote:
> On Fri, Jan 23, 2009 at 02:16:34PM +0300, Igor Katson wrote:
>
>> a) If the input argument is NULL, then the corresponding select
>> statement will change from
>>
>> column = arg
>> to
>> column IS NULL
>>
>
> I think you want to use the IS [NOT] DISTINCT FROM operator.  It
> works like the = and <> operators. i.e. the following expressions are
> equivalent:
>
>   x IS NOT DISTINCT FROM y
>
> and
>
>   CASE WHEN x IS NULL THEN y IS NULL
>        ELSE COALESCE(x = y, FALSE) END
>
>
>
>> b) If the input argument is NULL, then the corresponding select
>> statement will be removed, so if it was not written.
>>
>
> not sure what you mean here, but maybe one of the existing suggestions
> may help or the "RETURNS NULL ON NULL INPUT" flag when you're creating
> the function may be what you're looking for.
>
>
>
That one is awesome, thanks, I completely forgot about CASE statement.
The search func now looks as follows, and works perfectly:

CREATE OR REPLACE FUNCTION isocial_user_func.search_users
(i_city_id int, i_edu_id int, i_firstname text, i_lastname text,
limit_ int, offset_ int) RETURNS SETOF isocial_user.user AS $$
    DECLARE
       rec isocial_user.user;
    BEGIN
       FOR rec IN SELECT * FROM isocial_user.user
                  WHERE
                  CASE
                      WHEN i_city_id IS NULL THEN TRUE
                      ELSE city_id = i_city_id
                  END AND
                  CASE
                      WHEN i_edu_id IS NULL THEN TRUE
                      ELSE edu_id = i_edu_id
                  END AND
                  CASE
                      WHEN i_firstname IS NULL THEN TRUE
                      ELSE upper(firstname) ~ upper(i_firstname)
                  END AND
                  CASE
                      WHEN i_lastname IS NULL THEN TRUE
                      ELSE upper(lastname) ~ upper(i_lastname)
                  END
                  LIMIT limit_
                  OFFSET offset_
       LOOP
           RETURN NEXT rec;
       END LOOP;
       RETURN;
    END;
$$ language plpgsql;


Re: Using null or not null in function arguments

From
Michael Glaesemann
Date:
On Jan 23, 2009, at 10:11 , Igor Katson wrote:

> That one is awesome, thanks, I completely forgot about CASE
> statement. The search func now looks as follows, and works perfectly:
>
> CREATE OR REPLACE FUNCTION isocial_user_func.search_users
> (i_city_id int, i_edu_id int, i_firstname text, i_lastname text,
> limit_ int, offset_ int) RETURNS SETOF isocial_user.user AS $$
>   DECLARE
>      rec isocial_user.user;
>   BEGIN
>      FOR rec IN SELECT * FROM isocial_user.user
>                 WHERE
>                 CASE
>                     WHEN i_city_id IS NULL THEN TRUE
>                     ELSE city_id = i_city_id
>                 END AND
>                 CASE
>                     WHEN i_edu_id IS NULL THEN TRUE
>                     ELSE edu_id = i_edu_id
>                 END AND
>                 CASE
>                     WHEN i_firstname IS NULL THEN TRUE
>                     ELSE upper(firstname) ~ upper(i_firstname)
>                 END AND
>                 CASE
>                     WHEN i_lastname IS NULL THEN TRUE
>                     ELSE upper(lastname) ~ upper(i_lastname)
>                 END
>                 LIMIT limit_
>                 OFFSET offset_
>      LOOP
>          RETURN NEXT rec;
>      END LOOP;
>      RETURN;
>   END;
> $$ language plpgsql;

Here's an alternate formulation that eliminates the CASE statements
which I find hard to read:

CREATE OR REPLACE FUNCTION isocial_user_func.search_users
(i_city_id int, i_edu_id int, i_firstname text, i_lastname text,
limit_ int, offset_ int) RETURNS SETOF isocial_user.user AS $$
   DECLARE
      rec isocial_user.user;
   BEGIN
      FOR rec IN
        SELECT *
          FROM isocial_user.user
          WHERE (i_city_id IS NULL OR city_id = i_city_id)
                AND (i_edu_id IS NULL OR edu_id = i_edu_id)
                AND (i_firstname IS NULL OR upper(firstname) ~
upper(i_firstname))
                AND (i_lastname IS NULL OR upper(lastname) ~
upper(i_lastname))
          LIMIT limit_
          OFFSET offset_
      LOOP
          RETURN NEXT rec;
      END LOOP;
      RETURN;
   END;
$$ language plpgsql;

And you really don't even need to use PL/pgSQL: an SQL function would
work just as well.

CREATE OR REPLACE FUNCTION
isocial_user_func.search_users (i_city_id int, i_edu_id int,
                                 i_firstname text, i_lastname text,
                                 limit_ int, offset_ int,
                                 <OUT columns>)
RETURNS SETOF RECORD
LANGUAGE SQL AS $$
   SELECT *
     FROM isocial_user.user
     WHERE ($1 IS NULL OR city_id = i_city_id)
           AND ($2 IS NULL OR edu_id = i_edu_id)
           AND ($3 IS NULL OR upper(firstname) ~ upper(i_firstname))
           AND ($4 IS NULL OR upper(lastname) ~ upper(i_lastname))
     LIMIT $5
     OFFSET $6
$$;

Michael Glaesemann
grzm seespotcode net




Re: Using null or not null in function arguments

From
Igor Katson
Date:
Michael Glaesemann wrote:
>
> On Jan 23, 2009, at 10:11 , Igor Katson wrote:
>
>> That one is awesome, thanks, I completely forgot about CASE
>> statement. The search func now looks as follows, and works perfectly:
>>
>> CREATE OR REPLACE FUNCTION isocial_user_func.search_users
>> (i_city_id int, i_edu_id int, i_firstname text, i_lastname text,
>> limit_ int, offset_ int) RETURNS SETOF isocial_user.user AS $$
>>   DECLARE
>>      rec isocial_user.user;
>>   BEGIN
>>      FOR rec IN SELECT * FROM isocial_user.user
>>                 WHERE
>>                 CASE
>>                     WHEN i_city_id IS NULL THEN TRUE
>>                     ELSE city_id = i_city_id
>>                 END AND
>>                 CASE
>>                     WHEN i_edu_id IS NULL THEN TRUE
>>                     ELSE edu_id = i_edu_id
>>                 END AND
>>                 CASE
>>                     WHEN i_firstname IS NULL THEN TRUE
>>                     ELSE upper(firstname) ~ upper(i_firstname)
>>                 END AND
>>                 CASE
>>                     WHEN i_lastname IS NULL THEN TRUE
>>                     ELSE upper(lastname) ~ upper(i_lastname)
>>                 END
>>                 LIMIT limit_
>>                 OFFSET offset_
>>      LOOP
>>          RETURN NEXT rec;
>>      END LOOP;
>>      RETURN;
>>   END;
>> $$ language plpgsql;
>
> Here's an alternate formulation that eliminates the CASE statements
> which I find hard to read:
>
> CREATE OR REPLACE FUNCTION isocial_user_func.search_users
> (i_city_id int, i_edu_id int, i_firstname text, i_lastname text,
> limit_ int, offset_ int) RETURNS SETOF isocial_user.user AS $$
>   DECLARE
>      rec isocial_user.user;
>   BEGIN
>      FOR rec IN
>        SELECT *
>          FROM isocial_user.user
>          WHERE (i_city_id IS NULL OR city_id = i_city_id)
>                AND (i_edu_id IS NULL OR edu_id = i_edu_id)
>                AND (i_firstname IS NULL OR upper(firstname) ~
> upper(i_firstname))
>                AND (i_lastname IS NULL OR upper(lastname) ~
> upper(i_lastname))
>          LIMIT limit_
>          OFFSET offset_
>      LOOP
>          RETURN NEXT rec;
>      END LOOP;
>      RETURN;
>   END;
> $$ language plpgsql;
>
> And you really don't even need to use PL/pgSQL: an SQL function would
> work just as well.
>
> CREATE OR REPLACE FUNCTION
> isocial_user_func.search_users (i_city_id int, i_edu_id int,
>                                 i_firstname text, i_lastname text,
>                                 limit_ int, offset_ int,
>                                 <OUT columns>)
> RETURNS SETOF RECORD
> LANGUAGE SQL AS $$
>   SELECT *
>     FROM isocial_user.user
>     WHERE ($1 IS NULL OR city_id = i_city_id)
>           AND ($2 IS NULL OR edu_id = i_edu_id)
>           AND ($3 IS NULL OR upper(firstname) ~ upper(i_firstname))
>           AND ($4 IS NULL OR upper(lastname) ~ upper(i_lastname))
>     LIMIT $5
>     OFFSET $6
> $$;
>
> Michael Glaesemann
> grzm seespotcode net
>
>
>
Thank you, Michael, that one looks prettier.
Sam, I'm not sure if this is correct to do that, as you I don't want to
remember what will happen, if you use NULL = NULL or upper(NULL) etc.:

  WHERE
    COALESCE(city_id = i_city_id, TRUE) AND
    COALESCE(edu_id  = i_edu_id,  TRUE) AND
    COALESCE(upper(firstname) ~ upper(i_firstname), TRUE) AND
    COALESCE(upper(lastname)  ~ upper(i_lastname),  TRUE)




Re: Using null or not null in function arguments

From
Sam Mason
Date:
On Fri, Jan 23, 2009 at 06:32:17PM +0300, Igor Katson wrote:
> Sam, I'm not sure if this is correct to do that, as you I don't want to
> remember what will happen, if you use NULL = NULL or upper(NULL) etc.:
>
>  WHERE
>    COALESCE(city_id = i_city_id, TRUE) AND
>    COALESCE(edu_id  = i_edu_id,  TRUE) AND
>    COALESCE(upper(firstname) ~ upper(i_firstname), TRUE) AND
>    COALESCE(upper(lastname)  ~ upper(i_lastname),  TRUE)

I'm not quite sure what you mean when you say "I don't want to remember
what will happen".  Here is a state table of the various options you've
presented:

  param  tbl   Sam's  Your's/Michael's
  NULL   NULL  TRUE   NULL
  NULL   0     TRUE   TRUE
  NULL   1     TRUE   TRUE
  0      NULL  TRUE   NULL
  0      0     TRUE   TRUE
  0      1     FALSE  FALSE
  1      NULL  TRUE   NULL
  1      0     FALSE  FALSE
  1      1     TRUE   TRUE

The "tbl" column is the value for, say, edu_id; the "param" is the
matching parameter value, say i_edu_id. "Sam's" is the output of doing a
COALESCE and the "Your's/Michael's" column is the output of doing your
original CASE statement or Michael's OR variant.  The thing to note are
the extra NULLs in your variant as these will cause any row with a NULL
value in the table to never get returned.  This may, or may not, be what
you want!


--
  Sam  http://samason.me.uk/