Merging and returning arrays - arrays

I've two functions,
CREATE OR REPLACE FUNCTION function_a(input varchar)
RETURNS setof integer AS $$
BEGIN
RETURN QUERY
SELECT somecolumn FROM some_things WHERE a_column = input;
END;
$$ LANGUAGE PLpgSQL;
CREATE OR REPLACE FUNCTION function_b(inputs varchar[])
RETURNS setof integer AS $$
DECLARE
input varchar;
result integer[];
BEGIN
FOREACH input IN ARRAY inputs LOOP
result := result || ARRAY[function_a(input)];
END LOOP;
END;
$$ LANGUAGE PLpgSQL;
I am running it like,
SELECT function_b(ARRAY['a', 'b']);
The error,
ERROR: query "SELECT result || ARRAY[function_a(input)]" returned more than one row
CONTEXT: PL/pgSQL function function_b(character varying[]) line 7 at assignment
All I want to do is to run a function over an array. I've always used scripting languages like Ruby to do this kind of stuff instead of using SQL, but I'm trying to learn SQL as it is much faster to get results on the db console itself. I wish it wasn't so frustrating.

First, in SQL we mainly write queries. You can do the same with a simple query:
select somecolumn
from some_things
where a_column = any(array['a', 'b']);
If you need a function, it may be an SQL one:
create or replace function sql_function(inputs text[])
returns setof integer language sql as $$
select somecolumn
from some_things
where a_column = any(inputs);
$$;
SQL functions are simpler and usually faster than plpgsql ones.
Back to your function_b() - the array constructor should look like this:
ARRAY(SELECT function_a(input))
Note also that the function does not return anything. Because you aggregate results in an array, you should unnest it to return rows:
CREATE OR REPLACE FUNCTION function_b(inputs varchar[])
RETURNS setof integer AS $$
DECLARE
input varchar;
result integer[];
BEGIN
FOREACH input IN ARRAY inputs LOOP
result := result || ARRAY(SELECT function_a(input));
END LOOP;
RETURN QUERY SELECT unnest(result);
END;
$$ LANGUAGE PLpgSQL;

You don't need the first function at all. I believe you want to return a set of integers from the table for the given input. A function like this is all you need.
CREATE OR REPLACE FUNCTION function_b(inputs varchar[])
RETURNS setof integer AS $$
BEGIN
RETURN QUERY SELECT somecolumn
FROM some_things WHERE a_column = ANY ( inputs );
END;
$$ LANGUAGE PLpgSQL;
Demo

Related

Not able to call multiple functions from one function in PostgreSQL

Here are my two functions,
CREATE OR REPLACE FUNCTION public.insert_stagging()
RETURNS void
LANGUAGE plpgsql
AS $function$
BEGIN
INSERT into stagging(data)
select data from mytable where data not like 'table%';
END;
$function$;
CREATE OR REPLACE FUNCTION clear_mytable2()
RETURNS TABLE(data1 text) as
$BODY$
BEGIN
RETURN QUERY (select data from mytable2);
END;
$BODY$
LANGUAGE plpgsql;
Not able to call above two functions from another function,
CREATE OR REPLACE FUNCTION func_test()
returns void as
$func$
BEGIN
EXECUTE 'SELECT insert_stagging()';
EXECUTE 'SELECT clear_mytable2()';
END
$func$ LANGUAGE plpgsql;
With the above code, I am only able to get the execution result of 1st function written inside the BEGIN clause, if I change the sequence of the 'SELECT insert_stagging()'; with 'SELECT clear_mytable2()'; then I'll only get the execution result from clear_mytable2() function.
How to execute both the function calls within 1 PostgreSQL function?
When the function returns table, then returns result just to caller function. When the caller function ignores this result, then result is thrown.
If you want to call void function, then you should to use PERFORM. Using EXECUTE in this context is bad idea. This is designed to work with dynamic SQL. If you want to call table function, you have to use RETURN QUERY again:
CREATE OR REPLACE FUNCTION func_test()
RETURNS TABLE(data1 text) AS
$func$
BEGIN
PERFORM insert_stagging();
RETURN QUERY SELECT * FROM clear_mytable2();
END
$func$ LANGUAGE plpgsql;
SELECT * FROM func_test();

PostgreSQL RETURN NEXT error "returned more than one row"

I have a long drawn out PGPLSQL function :here is a summary
CREATE FUNCTION get_features_by_buffer(
p_buffer GEOMETRY
)
RETURNS SETOF JSON AS $BODY$
DECLARE
v_buffer GEOMETRY;
v_sql TEXT
BEGIN
FOR REC IN EXECUTE $$(
(
SELECT row_to_json(foo_pole) AS json FROM
(
SELECT * FROM pole WHERE $$ || v_sql_where || $$
) AS foo_pole
)
UNION ALL
(
SELECT row_to_json(foo_transformerbank) AS json FROM
(
SELECT * FROM transformerbank WHERE $$ || v_sql_where || $$) AS foo_transformerbank
)
)$$ LOOP
RETURN NEXT REC.json;
END LOOP;
END
$BODY$
LANGUAGE plpgsql;
My function returns RETURNS SETOF JSON and its a bit more complicated than shown here, however I have run the queries that are inside the UNION ALL statement and there is no syntax or other error. It looks weird here because I've been tinkering but I originally tried it by putting the query statement in v_sql and doing RETURN QUERY EXECUTE v_sql, that also gives the same error as the version show here. The error is as follows:
ERROR: query "SELECT get_features_by_buffer(v_buffer)" returned more than one row
CONTEXT: PL/pgSQL function get_features_by_pole_distance(character varying,double precision) line 7 at RETURN NEXT
I have been tinkering with this for a while, not sure what I'm missing here, something to do with the UNION ALL?
As far as I can tell, you don't need the cursor. Just use the generated SQL as the input to return query execute ...:
It's also easier to generate dynamic SQL using the format() function:
CREATE FUNCTION get_features_by_buffer(p_buffer GEOMETRY)
RETURNS SETOF JSON
AS
$BODY$
DECLARE
v_buffer GEOMETRY;
v_sql TEXT
BEGIN
RETURN QUERY EXECUTE
format(
'SELECT row_to_json(foo_pole) AS json
FROM (
SELECT *
FROM pole
WHERE %s
) AS foo_pole
UNION ALL
SELECT row_to_json(foo_transformerbank) AS json
FROM (
SELECT *
FROM transformerbank
WHERE %s
) AS foo_transformerbank', v_sql_where, v_sql_where);
END
$BODY$
LANGUAGE plpgsql;
As that function is declared as returns setof you have to use it like a table:
select *
from get_features_by_buffer(...);

Avoiding infinite recursion when calculating value of database view column depending on another view column

I have the following case of using PostgreSQL view. I have to dynamically calculated the value of view column depending of the value of another dynamically calculated view column. Here is simplified version of the code:
BEGIN;
CREATE TABLE test
(
id serial PRIMARY KEY,
value integer NOT NULL
);
INSERT INTO test VALUES
(1, 13),
(2, 42);
CREATE FUNCTION inc(value integer)
RETURNS integer AS
$BODY$
BEGIN
RETURN value + 1;
END;
$BODY$
LANGUAGE plpgsql;
CREATE FUNCTION double(id integer)
RETURNS integer AS
$BODY$
DECLARE
local_value integer;
BEGIN
SELECT value_1 INTO local_value
FROM test_view WHERE double.id = test_view.id;
RETURN 2 * local_value;
END;
$BODY$
LANGUAGE plpgsql;
CREATE VIEW test_view AS
SELECT *,
inc(test.value) AS value_1,
double(test.id) AS value_2
FROM test;
COMMIT;
But this code falls in infinite recursion because of the following statement in the second function.
SELECT value_1 INTO local_value
FROM test_view WHERE double.id = test_view.id;
The exact error is the following:
ERROR: stack depth limit exceeded
HINT: Increase the configuration parameter "max_stack_depth
(currently 2048kB), after ensuring the platform's stack depth limit is adequate.
CONTEXT: PL/pgSQL function inc(integer) line 3 at RETURN
SQL statement "SELECT value_1 FROM test_view WHERE double.id = test_view.id"
PL/pgSQL function double(integer) line 5 at SQL statement
The problem can easily be overcome by using second view. For example:
CREATE FUNCTION double(value integer)
RETURNS integer AS
$BODY$
BEGIN
RETURN 2 * value;
END;
$BODY$
LANGUAGE plpgsql;
CREATE VIEW test_view_1 AS
SELECT *,
inc(test.value) AS value_1
FROM test;
CREATE VIEW test_view_2 AS
SELECT *,
double(test_view_1.value_1) AS value_2
FROM test_view_1;
But I don't like this approach because it requires creating of second view. It not scales in the case I have n different values each depending of the previous one. Then I must have n different views.
Is it possible to solve the problem with only one view?
Why not?
CREATE FUNCTION inc(value integer)
RETURNS integer AS
$BODY$
BEGIN
RETURN value + 1;
END;
$BODY$
LANGUAGE plpgsql;
CREATE FUNCTION double(value integer)
RETURNS integer AS
$BODY$
BEGIN
RETURN 2 * value;
END;
$BODY$
LANGUAGE plpgsql;
CREATE VIEW test_view AS
SELECT *,double(value_1) AS value_2 FROM
(SELECT *,
inc(test.value) AS value_1
FROM test) x;
select * from test_view;
Did i miss something from OP?
You can do it in on step
CREATE OR REPLACE FUNCTION incdouble(value integer)
RETURNS RECORD AS
$BODY$
DECLARE
linc integer;
ret RECORD;
BEGIN
linc := value + 1;
SELECT linc,linc*2 INTO ret;
RETURN ret;
END;
$BODY$
LANGUAGE plpgsql ;
select * from test t,incdouble(t.value) as (i integer ,d integer)

Using %rowtype when returning in a PostgreSQL function

If I have a function that returns only one row with some columns from a table. Do I need to add %rowtype in the function return declaration?
CREATE OR REPLACE FUNCTION test(int n)
RETURNS tableName%rowtype AS
$BODY$
DECLARE
r tableName%rowtype;
BEGIN
select a,b,c into r from tableName where d=n;
return r;
$BODY$
END;
About %ROWTYPE
The %ROWTYPE construct is only good for portability to other RDBMS. Rarely useful, since PL/pgSQL functions are hardly portable to begin with.
If you are going to use it, it's only meant for variable declaration inside PL/pgSQL function, not to declare the RETURN type, which is part of the outer SQL syntax.
The manual:
(Since every table has an associated composite type of the same name,
it actually does not matter in PostgreSQL whether you write %ROWTYPE
or not. But the form with %ROWTYPE is more portable.)
Answer
This would achieve what you seem to be trying:
CREATE OR REPLACE FUNCTION test_plpgsql(_n int)
RETURNS tbl
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN (SELECT t FROM tbl t where tbl_id = _n); -- selecting whole row
END
$func$;
Call:
SELECT * FROM test_plpgsql(1);
But if it's as simple as that, use a simpler SQL function to begin with:
CREATE OR REPLACE FUNCTION test_sql(_n int)
RETURNS SETOF tbl
LANGUAGE sql AS
$func$
SELECT * FROM tbl WHERE tbl_id = _n;
$func$;
Call:
SELECT * FROM test_sql(1);
Your original code example had too many issues. Search for more plpgsql examples to get a grasp on basic syntax.

Postgres STRING_TO_ARRAY alternative? Like STRING_TO_RECORD?

I need to convert a comma separated text into a set of records. I created this function
but I am not convinced that the best way:
CREATE OR REPLACE FUNCTION F_StringListToRecord(pStringList TEXT, pDelimiter VARCHAR(10)) RETURNS SETOF RECORD AS $$
DECLARE
vIndex INT;
arrSize INT;
arrValue TEXT[];
BEGIN
arrValue := STRING_TO_ARRAY(pStringList, pDelimiter);
arrSize := ARRAY_UPPER(arrValue, 1);
FOR vIndex IN 1..arrSize LOOP
RETURN QUERY SELECT arrValue[vIndex];
END LOOP;
END $$
LANGUAGE plpgsql;
Is there any other function similar to STRING_TO_ARRAY (perhaps STRING_TO_RECORD)?
In 8.4 you can use:
select * from unnest(string_to_array(my_string_here,delimiter)) as v(x);

Resources