allow variadic unknowns in function arguments - postgresql

I want to create a small helper function that will allow me to stop repeating this code 100s of times:
SELECT
jsonb_strip_nulls(
jsonb_build_object(
'hello', 'world',
'value', 5,
'error', null
)
);
-- returns {"hello": "world","value":5}
However, when I try to wrap this in a function I get errors because the string literals are technically unknowns:
CREATE OR REPLACE FUNCTION jsonb_build_nullless_object(VARIADIC anyarray) RETURNS jsonb AS $$
SELECT
jsonb_strip_nulls(
jsonb_build_object(
VARIADIC $1
)
)
$$ LANGUAGE sql IMMUTABLE;
SELECT jsonb_build_nullless_object(
'hello', 'world',
'value', 5,
'error', null
);
-- ERROR: invalid input syntax for integer: "hello" has type unknown
Since the plain jsonb_build_object handles un-explicitly-typed string literals just fine, I assume there is a function decorator that would allow me to do the same?

There is no reason to use the pseudo-type anyarray as the arguments always are texts:
CREATE OR REPLACE FUNCTION jsonb_build_nullless_object(VARIADIC text[])
...
Db<>fiddle.
Note that arguments on odd positions are texts, so the whole array of arguments has to be text[]. SQL functions cannot have arguments of type "any" in contrast to some built-in functions like jsonb_build_object(VARIADIC "any").

Related

I have the following string '3,45,543,6,89'. Need output like table thorugh function. Pl help me get output through postgresql function

CREATE OR REPLACE FUNCTION public.SplitToTable(
iv_datalist varchar,iv_Separator varchar)
RETURNS TABLE(out_param varchar)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
BEGIN
RETURN query select (select regexp_split_to_table(iv_datalist, iv_Separator) as out_param );
END
$BODY$;
When I run
select * from splitToTable('3,34,4,545,35,3',',');
I get this error:
ERROR: structure of query does not match function result type
DETAIL: Returned type text does not match expected type character varying in column 1.
CONTEXT: PL/pgSQL function splittotable1(character varying,character varying) line 4 at RETURN QUERY
SQL state: 42804
You'd have to declare the function as RETURNS text rather than RETURNS varchar. Use text everywhere, as that is the preferred string type in PostgreSQL. That way, you have no problems with type conversions.
The function could be written simpler as
CREATE OR REPLACE FUNCTION public.SplitToTable(
iv_datalist text,
iv_Separator text
) RETURNS TABLE(out_param text)
LANGUAGE sql
IMMUTABLE
AS
$BODY$SELECT * FROM regexp_split_to_table(iv_datalist, iv_Separator);$BODY$;
Essentially, the function is useless; you could just use regexp_split_to_table directly.

What is the type of a list in PostgreSQL?

I want to declare a list of strings in a plpgsql script and I don't know which type is correct. I tried this:
do $$
declare
my_list text[] = ('a', 'b', 'c');
begin
...
end
$$ language plpgsql
It gives the error:
Array value must start with "{" or dimension information.
The type text[] is an array that I should define with { and } symbols. But what is the type that suits ( and ) definition as in my example?
An array is the correct type to use:
my_list text[] := array['a', 'b', 'c'];
This can be used for an "IN type" condition, you just need to use the ANY operator.
....
where a = any(my_list)
That is effectively the same as where a in ('a','b','c') - in fact the IN operator gets re-written to an ANY operator by the query optimimzer.

No function matches, error is not obvious for anyelement

When try to use the function below with SELECT * FROM t WHERE checkrange(3,r), returns an error "No function matches"
CREATE FUNCTION checkRange(
p_val anyelement, p_range anyarray
) RETURNS boolean AS $f$
SELECT bool_or(p_val <# r) FROM unnest(p_range) t(r);
$f$ LANGUAGE SQL IMMUTABLE;
Suppose
CREATE TABLE t (id serial, r int4range[]);
INSERT INTO t (r) VALUES
('{"[2,5]","[100,200]"}'::int4range[]),
('{"[6,9]","[201,300]"}'::int4range[]);
PS: I remember that we need to do some workwaround with PostgreSQL's anyelement, but not what... The error message is not obvious.
I think that's why:
from
https://www.postgresql.org/docs/current/static/extend-type-system.html#extend-types-polymorphic
Each position (either argument or return value) declared as anyelement
is allowed to have any specific actual data type, but in any given
call they must all be the same actual type. Each position declared as
anyarray can have any array data type, but similarly they must all be
the same type. And similarly, positions declared as anyrange must all
be the same range type. Furthermore, if there are positions declared
anyarray and others declared anyelement, the actual array type in the
anyarray positions must be an array whose elements are the same type
appearing in the anyelement positions.
So this will be ok:
select * from checkRange('[100,200]'::int4range, '{"[6,9]","[201,300]"}'::int4range[])
but that won't:
select * from checkRange(1, '{"[6,9]","[201,300]"}'::int4range[])
If you want your function to work with this anyarray just define first of it as integer to keep polymorphic arguments tied:
CREATE FUNCTION checkRange(
p_val bigint, p_range anyarray
) RETURNS boolean AS $f$
SELECT bool_or(p_val <# r) FROM unnest(p_range) t(r);
$f$ LANGUAGE SQL IMMUTABLE;

Optional argument in PL/pgSQL function

I am trying to write a PL/pgSQL function with optional arguments. It performs a query based on a filtered set of records (if specified), otherwise performs a query on the entire data set in a table.
For example (PSEUDO CODE):
CREATE OR REPLACE FUNCTION foofunc(param1 integer, param2 date, param2 date, optional_list_of_ids=[]) RETURNS SETOF RECORD AS $$
IF len(optional_list_of_ids) > 0 THEN
RETURN QUERY (SELECT * from foobar where f1=param1 AND f2=param2 AND id in optional_list_of_ids);
ELSE
RETURN QUERY (SELECT * from foobar where f1=param1 AND f2=param2);
ENDIF
$$ LANGUAGE SQL;
What would be the correct way to implement this function?
As an aside, I would like to know how I could call such a function in another outer function. This is how I would do it - is it correct, or is there a better way?
CREATE FUNCTION foofuncwrapper(param1 integer, param2 date, param2 date) RETURNS SETOF RECORD AS $$
BEGIN
CREATE TABLE ids AS SELECT id from foobar where id < 100;
RETURN QUERY (SELECT * FROM foofunc(param1, param2, ids));
END
$$ LANGUAGE SQL
Since PostgreSQL 8.4 (which you seem to be running), there are default values for function parameters. If you put your parameter last and provide a default, you can simply omit it from the call:
CREATE OR REPLACE FUNCTION foofunc(_param1 integer
, _param2 date
, _ids int[] DEFAULT '{}')
RETURNS SETOF foobar -- declare return type!
LANGUAGE plpgsql AS
$func$
BEGIN -- required for plpgsql
IF _ids <> '{}'::int[] THEN -- exclude empty array and NULL
RETURN QUERY
SELECT *
FROM foobar
WHERE f1 = _param1
AND f2 = _param2
AND id = ANY(_ids); -- "IN" is not proper syntax for arrays
ELSE
RETURN QUERY
SELECT *
FROM foobar
WHERE f1 = _param1
AND f2 = _param2;
END IF;
END -- required for plpgsql
$func$;
Major points:
The keyword DEFAULT is used to declare parameter defaults. Short alternative: =.
I removed the redundant param1 from the messy example.
Since you return SELECT * FROM foobar, declare the return type as RETURNS SETOF foobar instead of RETURNS SETOF record. The latter form with anonymous records is very unwieldy, you'd have to provide a column definition list with every call.
I use an array of integer (int[]) as function parameter. Adapted the IF expression and the WHERE clause accordingly.
IF statements are not available in plain SQL. Has to be LANGUAGE plpgsql for that.
Call with or without _ids:
SELECT * FROM foofunc(1, '2012-1-1'::date);
Effectively the same:
SELECT * FROM foofunc(1, '2012-1-1'::date, '{}'::int[]);
You have to make sure the call is unambiguous. If you have another function of the same name and two parameters, Postgres might not know which to pick. Explicit casting (like I demonstrate) narrows it down. Else, untyped string literals work, too, but being explicit never hurts.
Call from within another function:
CREATE FUNCTION foofuncwrapper(_param1 integer, _param2 date)
RETURNS SETOF foobar
LANGUAGE plgpsql AS
$func$
DECLARE
_ids int[] := '{1,2,3}';
BEGIN
-- whatever
RETURN QUERY
SELECT * FROM foofunc(_param1, _param2, _ids);
END
$func$;
Elaborating on Frank's answer on this thread:
The VARIADIC agument doesn't have to be the only argument, only the last one.
You can use VARIADIC for functions that may take zero variadic arguments, it's just a little fiddlier in that it requires a different calling style for zero args. You can provide a wrapper function to hide the ugliness. Given an initial varardic function definition like:
CREATE OR REPLACE FUNCTION foofunc(param1 integer, param2 date, param2 date, optional_list_of_ids VARIADIC integer[]) RETURNS SETOF RECORD AS $$
....
$$ language sql;
For zero args use a wrapper like:
CREATE OR REPLACE FUNCTION foofunc(integer, date, date) RETURNS SETOF RECORD AS $body$
SELECT foofunc($1,$2,$3,VARIADIC ARRAY[]::integer[]);
$body$ LANGUAGE 'sql';
or just call the main func with an empty array like VARIADIC '{}'::integer[] directly. The wrapper is ugly, but it's contained ugliness, so I'd recommend using a wrapper.
Direct calls can be made in variadic form:
SELECT foofunc(1,'2011-01-01','2011-01-01', 1, 2, 3, 4);
... or array call form with array ctor:
SELECT foofunc(1,'2011-01-01','2011-01-01', VARIADIC ARRAY[1,2,3,4]);
... or array text literal form:
SELECT foofunc(1,'2011-01-01','2011-01-01', VARIADIC '{1,2,3,4}'::int[]);
The latter two forms work with empty arrays.
You mean SQL Functions with Variable Numbers of Arguments? If so, use VARIADIC.

PostgreSQL: store function in column as value

Can functions be stored as anonymous functions directly in column as its value?
Let's say I want this function be stored in column.
Example (pseudocode):
Table my_table: pk (int), my_function (func)
func ( x ) { return x * 100 }
And later use it as:
select
t.my_function(some_input) AS output
from
my_table as t
where t.pk = 1999
Function may vary for each pk.
Your title asks something else than your example.
A function has to be created before you can call it. (title)
An expression has to be evaluated. You would need a meta-function for that. (example)
Here are solutions for both:
1. Evaluate expressions dynamically
You have to take into account that the resulting type can vary. I use polymorphic types for that.
CREATE OR REPLACE FUNCTION f1(int)
RETURNS int
LANGUAGE sql IMMUTABLE AS
'SELECT $1 * 100;';
CREATE OR REPLACE FUNCTION f2(text)
RETURNS text
LANGUAGE sql IMMUTABLE AS
$$SELECT $1 || '_foo';$$;
CREATE TABLE my_expr (
expr text PRIMARY KEY
, def text
, rettype regtype
);
INSERT INTO my_expr VALUES
('x', 'f1(3)' , 'int')
, ('y', $$f2('bar')$$, 'text')
, ('z', 'now()' , 'timestamptz')
;
CREATE OR REPLACE FUNCTION f_eval(text, _type anyelement = 'NULL'::text, OUT _result anyelement)
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE
'SELECT ' || (SELECT def FROM my_expr WHERE expr = $1)
INTO _result;
END
$func$;
Related:
Refactor a PL/pgSQL function to return the output of various SELECT queries
Call:
SQL is strictly typed, the same result column can only have one data type. For multiple rows with possibly heterogeneous data types, you might settle for type text, as every data type can be cast to and from text:
SELECT *, f_eval(expr) AS result -- default to type text
FROM my_expr;
Or return multplce columns like:
SELECT *
, CASE WHEN rettype = 'text'::regtype THEN f_eval(expr) END AS text_result -- default to type text
, CASE WHEN rettype = 'int'::regtype THEN f_eval(expr, NULL::int) END AS int_result
, CASE WHEN rettype = 'timestamptz'::regtype THEN f_eval(expr, NULL::timestamptz) END AS tstz_result
-- , more?
FROM my_expr;
db<>fiddle here
2. Create and use functions dynamically
It is possible to create functions dynamically and then use them. You cannot do that with plain SQL, however. You will have to use another function to do that or at least an anonymous code block (DO statement), introduced in PostgreSQL 9.0.
It can work like this:
CREATE TABLE my_func (func text PRIMARY KEY, def text);
INSERT INTO my_func VALUES
('f'
, $$CREATE OR REPLACE FUNCTION f(int)
RETURNS int
LANGUAGE sql IMMUTABLE AS
'SELECT $1 * 100;'$$);
CREATE OR REPLACE FUNCTION f_create_func(text)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
EXECUTE (SELECT def FROM my_func WHERE func = $1);
END
$func$;
Call:
SELECT f_create_func('f');
SELECT f(3);
db<>fiddle here
You may want to drop the function afterwards.
In most cases you should just create the functions instead and be done with it. Use separate schemas if you have problems with multiple versions or privileges.
For more information on the features I used here, see my related answer on dba.stackexchange.com.