Passing multiple values in single parameter - sql

Let's say I have this function:
CREATE OR REPLACE FUNCTION test_function(character varaying)
RETURNS integer AS
$BODY$
DECLARE
some_integer integer;
begin
Select column2 from test_table where column1 in ($1) into some_integer;
end;
Return some_integer;
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
And I want to call it like this:
Select * from test_function ('data1', 'data2','data3');
Of course, it cannot be done this way, because Postgres tries to find function with this name and three parameter which doesn't exists.
I tried to put quotes around commas but in that case parameter is interpreted wrong: data1', 'data2','data3, like one string.
Is there a way to put multiple values in parameter so IN clause can recognized it?

(Your function wouldn't be created. RETURN after END is syntactical nonsense.)
A function with a VARIADIC parameter does exactly what you ask for:
CREATE OR REPLACE FUNCTION test_function(date, date, VARIADIC varchar[])
RETURNS SETOF integer
LANGUAGE sql AS
$func$
SELECT col1
FROM test_table
WHERE col3 > $1
AND col4 < $2
AND col2 = ANY($3)
$func$;
db<>fiddle here - demo with additional parameters
Old sqlfiddle
Call (as desired):
SELECT * FROM test_function('data1', 'data2', 'data3');
Using a simple SQL function, plpgsql is not required for the simple example. But VARIADIC works for plpgsql functions, too.
Using RETURNS SETOF integer since this can obviously return multiple rows.
Details:
Pass multiple values in single parameter
Return rows matching elements of input array in plpgsql function
VARIADIC parameter must be the last input parameter
Return rows matching elements of input array in plpgsql function

Related

Return a table using SQL Procedure?

I have problem with return table using SQL Procedure.
My code:
CREATE PROCEDURE return_data(surname character varying)
LANGUAGE SQL
AS $$
SELECT * FROM peopple WHERE surname=surname
$$;
CALL return_data('Jobs');
Currently the procedure executes without error but it doesn't return a table.
Procedures aren't meant to return anything. Use a set returning function
create function return_data(p_surname varchar)
returns setof people
as
$$
select *
from people
where surname = p_surname;
$$
language sql;
You should also avoid parameter or variable names that have the same name as a column.
Then use it like this:
select *
from return_data('Jobs');
You can append one or more rows to the result set:
CREATE OR REPLACE FUNCTION return_data(surname character varying)
RETURNS TABLE(firstname character varying, surname character varying, age integer)
LANGUAGE plpgsql
AS $function$
BEGIN
RETURN QUERY
SELECT firstname, surname, age
FROM users;
END;
$function$
;
Invoke the function:
SELECT * FROM return_data("a surname");
A function can return but a procedure cannot. Thats true, I wanna add to that we generally use function to return values of one type (in general one value)
While,
Procedure never returns but can behave like a function without a return statement using OUT parameters. To add to this we use procedure when we dont wanna return in actual but executing sql statements but in case if we require a procedure to give multiple values we can make procedure behave like a function for multole values output vja OUT.
As a table can be accessed by record or array type depending upon homogenity. Therefore, use function with return type as record/array.
Hence, as function is solely used to return single output we have to specify via return.

How to write function for optional parameters in postgresql?

My requirement is write optional parameters to a function.Parameters are optional sometimes i will add or i will not pass parameters to function.Can anyone help me how to write function.
I am writing like
select *
from test
where field3 in ('value1','value2')
and ($1 is null or field1 = $1)
and ($2 is null or field2 = $2)
and ($3 is null or field3 = $3);
i am passing parameters to Query,But my output is not expected.when i pass all three parameters my output is correct,otherwise it is not expected output.
You can define optional parameters by supplying a default value.
create function foo(p_one integer default null,
p_two integer default 42,
p_three varchar default 'foo')
returns text
as
$$
begin
return format('p_one=%s, p_two=%s, p_three=%s', p_one, p_two, p_three);
end;
$$
language plpgsql;
You can "leave out" parameters from the end, so foo(), foo(1) or foo(1,2) are valid. If you want to only supply a parameter that is not the first you have to use the syntax that specifies the parameter names.
select foo();
returns: p_one=, p_two=42, p_three=foo
select foo(1);
returns: p_one=1, p_two=42, p_three=foo
select foo(p_three => 'bar')
returns: p_one=, p_two=42, p_three=bar
Apart of the VARIADIC option pointed by #a_horse_with_no_name, which is only a syntax sugar for passing an array with any number of elements of the same type, you can't define a function with optional parameters because, in postgres, functions are identified not only by its name but also by its arguments and the types of them.
That is: create function foo (int) [...] and create function foo (varchar) [...] will create different functions.
Which is called when you execute, for example, select foo(bar) depends on bar data type itself. That is: if it is an integer, you will call the first one and if it is varchar, then second one will be called.
More than that: if you execute, for example, select foo(now()), then a function not exists exception will be triggered.
So, as I said, you can't implement functions with variable arguments, but you can implement multiple functions with the same name and distinct argument (an/or type) sets returning the same data type.
If you (obviously) doesn't want to implement the function twice, the only thing you need to do is to implement a "master" function with all possible parameters and the others (which have fewer parameters) only calling the "master" one with default values for the non received parameters.
As an option, I got a function i tested with Navicat App:
CREATE OR REPLACE FUNCTION "public"."for_loop_through_query"(sponsor_name varchar default 'Save the Children')
It generates me this. (Note: Please look at the parameter difference)
CREATE OR REPLACE FUNCTION "public"."for_loop_through_query"("sponsor_name" varchar='Save the Children'::character varying)
CREATE OR REPLACE FUNCTION "public"."for_loop_through_query"("sponsor_name" varchar='Save the Children'::character varying)
RETURNS "pg_catalog"."void" AS $BODY$
DECLARE
rec RECORD;
BEGIN
FOR rec IN SELECT
companies."name" AS org_name,
"sponsors"."name" AS sponsor_name
FROM
"donor_companies"
JOIN "sponsors"
ON "donor_companies"."donor_id" = "sponsors"."id"
JOIN companies
ON "donor_companies"."organization_id" = companies."id"
WHERE
"public"."sponsors"."name" = sponsor_name
LOOP
RAISE NOTICE '%', rec.org_name;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

how to use argument as table name in a postgresql SQL function

I've got a function to return relevant position of an array matching given value like below:
CREATE OR REPLACE FUNCTION get_index(needle ANYELEMENT, haystack ANYARRAY)
RETURNS TABLE (i_position int)
AS $$
SELECT
i+i_step AS i_position
FROM (VALUES(1),(2)) steps(i_step),
generate_series(array_lower($2,1), array_upper($2,1)) AS i
WHERE $2[i] = $1
$$ LANGUAGE SQL STABLE;
Instead of passing a single value to the function, I want to pass a table name, as one column of the table would be used to do the value comparison (WHERE $2[i] = $1 ), instead of a single value passed to the function. However, it doesn't seem like the function support SQL using argument as table name.
I'm wondering if there's alternative. I'd like to use SQL function instead of PLPGSQL, for the sake of performance. As our table is huge.
I'd like to achieve something like below:
CREATE OR REPLACE FUNCTION get_index(tbl ANYELEMENT, haystack ANYARRAY)
RETURNS TABLE (i_position int)
AS $$
SELECT
i+i_step AS i_position
FROM (VALUES(1),(2)) steps(i_step),
generate_series(array_lower($2,1), array_upper($2,1)) AS i,
$1
WHERE $2[i] = $1.col1
$$ LANGUAGE SQL STABLE;
It is not possible in SQL function - It doesn't support dynamic sql. You have to use PLpgSQL - EXECUTE statement.
SQL function is faster than PLpgSQL only when inlining is successful. When it isn't successful, and SQL function should be evaluated, then PLpgSQL should not be slower. When body of SQL function contains SRF functions like generate_series, then inlining is not effective.

Passing a ResultSet into a Postgresql Function

Is it possible to pass the results of a postgres query as an input into another function?
As a very contrived example, say I have one query like
SELECT id, name
FROM users
LIMIT 50
and I want to create a function my_function that takes the resultset of the first query and returns the minimum id. Is this possible in pl/pgsql?
SELECT my_function(SELECT id, name FROM Users LIMIT 50); --returns 50
You could use a cursor, but that very impractical for computing a minimum.
I would use a temporary table for that purpose, and pass the table name for use in dynamic SQL:
CREATE OR REPLACE FUNCTION f_min_id(_tbl regclass, OUT min_id int) AS
$func$
BEGIN
EXECUTE 'SELECT min(id) FROM ' || _tbl
INTO min_id;
END
$func$ LANGUAGE plpgsql;
Call:
CREATE TEMP TABLE foo ON COMMIT DROP AS
SELECT id, name
FROM users
LIMIT 50;
SELECT f_min_id('foo');
Major points
The first parameter is of type regclass to prevent SQL injection. More info in this related answer on dba.SE.
I made the temp table ON COMMIT DROP to limit its lifetime to the current transaction. May or may not be what you want.
You can extend this example to take more parameters. Search for code examples for dynamic SQL with EXECUTE.
-> SQLfiddle demo
I would take the problem on the other side, calling an aggregate function for each record of the result set. It's not as flexible but can gives you an hint to work on.
As an exemple to follow your sample problem:
CREATE OR REPLACE FUNCTION myMin ( int,int ) RETURNS int AS $$
SELECT CASE WHEN $1 < $2 THEN $1 ELSE $2 END;
$$ LANGUAGE SQL STRICT IMMUTABLE;
CREATE AGGREGATE my_function ( int ) (
SFUNC = myMin, STYPE = int, INITCOND = 2147483647 --maxint
);
SELECT my_function(id) from (SELECT * FROM Users LIMIT 50) x;
It is not possible to pass an array of generic type RECORD to a plpgsql function which is essentially what you are trying to do.
What you can do is pass in an array of a specific user defined TYPE or of a particular table row type. In the example below you could also swap out the argument data type for the table name users[] (though this would obviously mean getting all data in the users table row).
CREATE TYPE trivial {
"ID" integer,
"NAME" text
}
CREATE OR REPLACE FUNCTION trivial_func(data trivial[])
RETURNS integer AS
$BODY$
DECLARE
BEGIN
--Implementation here using data
return 1;
END$BODY$
LANGUAGE 'plpgsql' VOLATILE;
I think there's no way to pass recordset or table into function (but I'd be glad if i'm wrong). Best I could suggest is to pass array:
create or replace function my_function(data int[])
returns int
as
$$
select min(x) from unnest(data) as x
$$
language SQL;
sql fiddle demo

Custom aggregate function

I am trying to understand aggregate functions and I need help.
So for instance the following sample:
CREATE OR REPLACE FUNCTION array_median(timestamp[])
RETURNS timestamp AS
$$
SELECT CASE WHEN array_upper($1,1) = 0 THEN null ELSE asorted[ceiling(array_upper(asorted,1)/2.0)] END
FROM (SELECT ARRAY(SELECT ($1)[n] FROM
generate_series(1, array_upper($1, 1)) AS n
WHERE ($1)[n] IS NOT NULL
ORDER BY ($1)[n]
) As asorted) As foo ;
$$
LANGUAGE 'sql' IMMUTABLE;
CREATE AGGREGATE median(timestamp) (
SFUNC=array_append,
STYPE=timestamp[],
FINALFUNC=array_median
)
I am not understanding the structure/logic that needs to go into the select statement in the aggregate function itself. Can someone explain what the flow/logic is?
I am writing an aggregate, a strange one, that the return is always the first string it ever sees.
You're showing a median calculation, but want the first text value you see?
Below is how to do that. Assuming you want the first non-null value, that is. If not, you'll need to keep track of if you've got a value already or not.
The accumulator function is written as plpgsql and sql - the plpgsql one lets you use variable names and debug it too. It simply uses COALESCE against the previous accumulated value and the new value and returns the first non-null. So - as soon as you have a non-null in the accumulator everything else gets ignored.
You may also want to consider the "first_value" window function for this sort of thing if you're on a modern (8.4+) version of PostgreSQL.
http://www.postgresql.org/docs/9.1/static/functions-window.html
HTH
BEGIN;
CREATE FUNCTION remember_first(acc text, newval text) RETURNS text AS $$
BEGIN
RAISE NOTICE '% vs % = %', acc, newval, COALESCE(acc, newval);
RETURN COALESCE(acc, newval);
END;
$$ LANGUAGE plpgsql IMMUTABLE;
CREATE FUNCTION remember_first_sql(text,text) RETURNS text AS $$
SELECT COALESCE($1, $2);
$$ LANGUAGE SQL IMMUTABLE;
-- No "initcond" means we start out with null
--
CREATE AGGREGATE first(text) (
sfunc = remember_first,
stype = text
);
CREATE TEMP TABLE tt (t text);
INSERT INTO tt VALUES ('abc'),('def'),('ghi');
SELECT first(t) FROM tt;
ROLLBACK;