Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I would like to create a PostgreSQL function that does something like the following:

CREATE FUNCTION avg_purchases( IN last_names text[] DEFAULT '{}' )
  RETURNS TABLE(last_name text[], avg_purchase_size double precision)
AS
$BODY$
DECLARE
  qry text;
BEGIN
qry := 'SELECT last_name, AVG(purchase_size) 
          FROM purchases
          WHERE last_name = ANY($1)
          GROUP BY last_name'
RETURN QUERY EXECUTE qry USING last_names;
END;
$BODY$

but I see two problems here:

  1. It is not clear to me that array type is the most useful type of input.
  2. This is currently returning zero rows when I do:

    SELECT avg_purchases($${'Brown','Smith','Jones'}$$)

What am I missing?

share|improve this question
    
The use of text[] in the return type was a typo. I leave it here because the accepted answer references that mistake. –  Brian B Jul 31 '13 at 20:10
    
It turns out that another big mistake was that the calling syntax (after correcting the problem according to the post from @Erwin) needed to be SELECT avg_purchases($${Brown,Smith,Jones}$$) or SELECT avg_purchases('{Brown,Smith,Jones}') –  Brian B Jul 31 '13 at 20:13

1 Answer 1

up vote 3 down vote accepted

This works:

CREATE OR REPLACE FUNCTION avg_purchases(last_names text[] = '{}')
  RETURNS TABLE(last_name text, avg_purchase_size float8)
AS
$func$
   SELECT last_name, AVG(purchase_size)::float8
   FROM   purchases
   WHERE  last_name = ANY($1)
   GROUP  BY last_name
$func$ LANGUAGE sql;

Call:

SELECT * FROM avg_purchases('{foo,Bar,baz,"}weird_name''$$"}');

Or (update - example with dollar-quoting):

SELECT * FROM avg_purchases($x${foo,Bar,baz,"}weird_name'$$"}$x$);
  • More about how to quote string literals:
    Insert varchar with single quotes in postgresql

  • You don't need dynamic SQL here.

  • While you can wrap it into a plpgsql function (which may be useful), a simple SQL function is doing the job just fine.

  • You have type mismatches.

    • the result of avg() may be numeric to hold a precise result. I cast to float8 to make it work, which is just an alias for double precision (you can use either). If you need perfect precision, use numeric instead.
    • Since you GROUP BY last_name you want a plain text OUT parameter instead of text[].

VARIADIC

An array is a useful type of input. If it's easier for your client you can also use a VARIADIC input parameter:

CREATE OR REPLACE FUNCTION avg_purchases(VARIADIC last_names text[] = '{}')
  RETURNS TABLE(last_name text, avg_purchase_size float8)
AS
$func$
   SELECT last_name, AVG(purchase_size)::float8
   FROM   purchases
   JOIN  (SELECT unnest($1)) t(last_name) USING (last_name)
   GROUP  BY last_name
$func$ LANGUAGE sql

Call:

SELECT * FROM avg_purchases('foo', 'Bar', 'baz', '"}weird_name''$$"}');

Or (with dollar-quoting):

SELECT * FROM avg_purchases('foo', 'Bar', 'baz', $y$'"}weird_name'$$"}$y$);

Be aware that standard Postgres only allows a maximum of 100 elements. This is determined at compile time by the preset option:

max_function_args (integer)

Reports the maximum number of function arguments. It is determined by the value of FUNC_MAX_ARGS when building the server. The default value is 100 arguments.

You can still call it with array notation when prefixed with the keyword VARIADIC:

SELECT * FROM avg_purchases(VARIADIC '{1,2,3, ... 99,100,101}');

For bigger arrays (100+), I would also use unnest() in a subquery and JOIN to it, which tends to scale better.

share|improve this answer
    
Actually these will be big arrays in practice, so I am particularly keen on your unnest() suggestion. –  Brian B Jul 31 '13 at 19:33

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.