How to pass a parameter into a date function

2.5k views Asked by At

I am trying to create a simple function and I cannot seem to pass in a parameter into the date function.
Here is the function:

CREATE OR REPLACE FUNCTION test(source int,days text)
RETURNS integer AS $totals$
declare
    totals integer;
BEGIN

   select 
           count(id) into totals
     from  ad
    where
       createdate::date = date(current_date - interval '$2' day) and
       source = $1;

   RETURN totals;

END;
$totals$ LANGUAGE plpgsql;
2

There are 2 answers

0
Erwin Brandstetter On BEST ANSWER

@IMSoP already shed light upon your syntax error. However, this can be simpler, faster and cleaner in multiple ways.

CREATE OR REPLACE FUNCTION test(_source int, _days int)
  RETURNS integer AS
$func$
SELECT count(*)::int
FROM   ad a
WHERE  a.source = $1
AND    a.createdate::date = current_date - $2
$func$  LANGUAGE sql STABLE;
  • First of all, to subtract days from a date, you can can just subtract an integer number. Accordingly I use an integer parameter here.

  • You don't need plpgsql for a simple function like this. Use an SQL function instead - which can be "inlined" in the context of a bigger query, and thus optimized better in certain cases.

  • The function can be STABLE:

  • You had a naming conflict built into your function. source is obviously also a column name. Try to avoid that. A common practice is to prepend variables and parameters with an underscore (which has no special meaning otherwise). You can also table-qualify column names and/or prepend parameter names with the function name (or use positional parameters) to be unambiguous. I did both here.

  • Assuming id is your PK column and thus defined NOT NULL, count(*) does the same as count(id), a bit shorter and cheaper. I cast to integer, because count() will return a bigint.

However, going out on a limb here, I suspect your inaccurately named column createdate is not actually a date but a timestamp (essential table definition is missing in the question). In that case it's much more efficient to phrase the query differently:

CREATE OR REPLACE FUNCTION test(_source int, _days int)
  RETURNS integer AS
$func$
SELECT count(*)::int
FROM   ad a
WHERE  a.source = $1
AND    a.createdate >= now() - interval '1 day' * $2
AND    a.createdate <  now() - interval '1 day' * ($2 - 1)
$func$ LANGUAGE sql STABLE;
  • This expression is sargable and thus more efficient. It can also use a plain index on (createdate), or better on (source, createdate) - important for big tables.

  • Also demonstrating an alternative way to subtract days. You can multiply the interval '1 day'. Related:

3
IMSoP On

This is invalid syntax: interval '$2' day. The variable isn't just swapped into place before the SQL is run, you need to pass the right kind of value to the function.

You probably meant to concatenate the string 'day' onto the end of the string (text) variable $2, also known as days (not the literal string '$2'). So $2 || 'day' or days || 'day'.

Since this is then not a single literal, you need an explicit cast, not just a type label, so something like CAST($2 || 'day' AS interval) or (days || 'day')::interval.