Return rows matching elements of input array in plpgsql function

不羁的心 提交于 2019-11-26 07:49:20

问题


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?


回答1:


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 text 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 that allows to pass the array as a list of elements:

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:

  • Optimizing a Postgres query with a large IN


来源:https://stackoverflow.com/questions/17978310/return-rows-matching-elements-of-input-array-in-plpgsql-function

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!