SQL query with variables in Postgresql - sql

I have an SQL query
SELECT c,d FROM tableX where a='str' AND b=var1 ;
I would like to substitute the var1 with a variable. I tried to use plpgsql.
CREATE OR REPLACE FUNCTION foo (var1 integer)
RETURNS TABLE (c integer, d varchar) AS
$BODY$
DECLARE
aa varchar = 'str';
BEGIN
RETURN QUERY EXECUTE
'SELECT c,d FROM tableX where a=aa AND b=#1' using var1;
END;
$BODY$
LANGUAGE plpgsql;
The error is
No operator matches the given name and argument type(s). You might need to add explicit type casts.

First - the correct way to specify parameters is $1, not #1.
Second - you do not need dynamic sql to pass parameters to the query. Just write something like:
CREATE OR REPLACE FUNCTION foo (var1 integer)
RETURNS TABLE (c integer, d varchar) AS
$BODY$
DECLARE
aa varchar = 'str';
BEGIN
RETURN QUERY SELECT c,d FROM tableX where a=aa AND b=var1;
END;
$BODY$
LANGUAGE plpgsql;

Just to practice in PostgreSQL, as a_horse_with_no_name said, it's possible to write function in plain SQL, here's my attempt:
CREATE FUNCTION foo1 (var1 integer) RETURNS TABLE(c int, d text)
AS $$ SELECT c,d FROM tableX where a='str' AND b=$1 $$
LANGUAGE SQL;
SQL FIDDLE EXAMPLE

Just try:
CREATE OR REPLACE FUNCTION foo (var1 integer)
RETURNS TABLE (c integer, d varchar) AS
$BODY$
DECLARE
aa varchar = 'str';
BEGIN
RETURN QUERY
SELECT c,d FROM tableX where a=aa AND b=var1;
END;

Related

Multiple ALTER TABLE ADD COLUMN in one SQL function call

I came across some weird behaviour I'd like to understand.
I create a plpgsql function doing nothing except of ALTER TABLE ADD COLUMN. I call it 2 times on the same table:
A) In a single SELECT sentence
B) In a SQL function with same SELECT as in A)
Results are different: A) creates two columns, while B) creates only one column. Why?
Code:
CREATE FUNCTION add_text_column(table_name text, column_name text) RETURNS VOID
LANGUAGE plpgsql
AS $fff$
BEGIN
EXECUTE '
ALTER TABLE ' || table_name || '
ADD COLUMN ' || column_name || ' text;
';
END;
$fff$
;
-- this function is called only in B
CREATE FUNCTION add_many_text_columns(table_name text) RETURNS VOID
LANGUAGE SQL
AS $fff$
WITH
col_names (col_name) AS (
VALUES
( 'col_1' ),
( 'col_2' )
)
SELECT add_text_column(table_name, col_name)
FROM col_names
;
$fff$
;
-- A)
CREATE TABLE a (id integer);
WITH
col_names (col_name) AS (
VALUES
( 'col_1' ),
( 'col_2' )
)
SELECT add_text_column('a', col_name)
FROM col_names
;
SELECT * FROM a;
-- B)
CREATE TABLE b (id integer);
SELECT add_many_text_columns('b');
SELECT * FROM b;
Result:
CREATE FUNCTION
CREATE FUNCTION
CREATE TABLE
add_text_column
-----------------
(2 rows)
id | col_1 | col_2
----+-------+-------
(0 rows)
CREATE TABLE
add_many_text_columns
-----------------------
(1 row)
id | col_1
----+-------
(0 rows)
I'm using PostgreSQL 10.4. Please note that this is only a minimal working example, not the full functionality I need.
CREATE OR REPLACE FUNCTION g(i INTEGER)
RETURNS VOID AS $$
BEGIN
RAISE NOTICE 'g called with %', i;
END
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS $$
SELECT g(id)
FROM generate_series(1, i) id;
$$ LANGUAGE SQL;
What do you think happens when I run SELECT t(4)? The only statement printed from g() is g called with 1.
The reason for this is your add_many_text_columns function returns a single result (void). Because it's SQL and is simply returning the result of a SELECT statement, it seems to stop executing after getting the first result, which makes sense if you think of it - it can only return one result after all.
Now change the function to:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS SETOF VOID AS $$
SELECT g(id)
FROM generate_series(1, i) id;
$$ LANGUAGE SQL;
And run SELECT t(4) again, and now this is printed:
g called with 1
g called with 2
g called with 3
g called with 4
Because the function now returns SETOF VOID, it doesn't stop after the first result and executes it fully.
So back to your functions, you could change your SQL function to return SETOF VOID, but it doesn't really make much sense - better I think to change it to plpgsql and have it do a PERFORM:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS $$
BEGIN
PERFORM g(id)
FROM generate_series(1, i) id;
END
$$ LANGUAGE plpgsql;
That will execute the statement fully and it still returns a single VOID.
eurotrash provided a good explanation.
Alternative solution 1
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT g(id)
FROM generate_series(1, i) id;
SELECT null::void;
$func$ LANGUAGE sql;
Because, quoting the manual:
SQL functions execute an arbitrary list of SQL statements, returning
the result of the last query in the list. In the simple (non-set)
case, the first row of the last query's result will be returned.
By adding a dummy SELECT at the end we avoid that Postgres stops after processing the the first row of the query with multiple rows.
Alternative solution 2
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT count(g(id))
FROM generate_series(1, i) id;
$func$ LANGUAGE sql;
By using an aggregate function, all underlying rows are processed in any case. The function returns bigint (that's what count() returns), so we get the number of rows as result.
Alternative solution 3
If you need to return void for some unknown reason, you can cast:
CREATE OR REPLACE FUNCTION t(i INTEGER)
RETURNS VOID AS
$func$
SELECT count(g(id))::text::void
FROM generate_series(1, i) id;
$func$ LANGUAGE sql;
The cast to text is a stepping stone because the cast from bigint to void is not defined.

call an sql function by name returned by select

I would like to find out how to call a function the name of which is returned from a select query. So let's say, I have a select query:
SELECT function_name FROM functions WHERE id=1;
Now let's say, the returned functions name is fce1
and now I want to execute:
SELECT fce1(parameters);
Now my initial idea would be:
SELECT CONCAT(SELECT function_name FROM functions WHERE id=1;, "(params)");
I am quite certain that the idea is wrong. But I was trying to figure that out some time ago and I remember that at least MS SQL was able to achieve my goal and also POSTGRESQL. Anyway, neither do I remember or am I able to find how to do it. Ideas would be appreciated.
DECLARE #func NVARCHAR(50);
SELECT #func = function_name FROM functions WHERE id=1;
EXEC ('select ' + #func + '()')
Assuming all functions return the same data type, you could build a wrapper function in Postgres that you pass the ID of the function to be called:
create or replace function call_func(p_id integer)
returns integer
as
$$
declare
l_result integer;
l_name text;
l_params text;
begin
select function_name, parameters
into l_name, l_params
from functions
where id = p_id;
execute 'select '||l_name||'('||l_params||')'
into l_result;
return l_result;
end;
$$
language plpgsql;
Note: the above is just an example.
It's wide open to SQL injection and does not do any error checking or sanitizing the parameters! But it might point you into the right direction.
Assume you have the functions:
create or replace function foo(p_arg_1 integer, p_arg_2 integer)
returns integer
as
$$
select p_arg_1 + p_arg_2;
$$
language sql;
create or replace function bar(p_value integer)
returns integer
as
$$
select p_value * 4;
$$
language sql;
And the functions table looks like this:
id | function_name | parameters
---+---------------+-----------
1 | fce | 42
2 | foo | 1,2
You can then do
select call_func(2);
or
select call_func(1);
But again: this will only work if all functions return the same result. e.g. scalar functions returning a single value, or set returning functions returning the same table definition.

cannot pass dynamic query to sql-function

I cannot seem to find a way to pass my query as a parameter to my sql-function. My problem is table 'my_employees1' could be dynamic.
DROP FUNCTION function_test(text);
CREATE OR REPLACE FUNCTION function_test(text) RETURNS bigint AS '
DECLARE ret bigint;
BEGIN
SELECT count(mt.id) INTO ret
FROM mytable as mt
WHERE mt.location_id = 29671
--and mt.employee_id in (SELECT id from my_employees1);
--and mt.employee_id in ($1);
$1;
RETURN ret;
END;
' LANGUAGE plpgsql;
select function_test('and mt.employee_id in (SELECT id from my_employees1)');
select function_test('SELECT id from my_employees1');
It must be dynamically built:
DROP FUNCTION function_test(text);
CREATE OR REPLACE FUNCTION function_test(text) RETURNS bigint AS $$
DECLARE
ret bigint;
BEGIN
execute(format($q$
SELECT count(mt.id) INTO ret
FROM mytable as mt
WHERE mt.location_id = 29671
%s; $q$, $1)
);
RETURN ret;
END;
$$ LANGUAGE plpgsql;
The $$ and $q$ are dollar quotes. They can be nested as long as the inner identifier is different. In addition to the obvious advantages of permitting the use of unquoted quotes and being nestable it also let the syntax highlighting do its work.

Postgres function with list argument and in clause

How to create a function which takes as argument integer[] parameter and executing query with IN clause with this parameter in loop.
In loop I want execute next select and result of this query I would like return.
Something like that:
CREATE OR REPLACE FUNCTION function_which_i_want(my_argument integer[]) RETURNS my_schema_and_table[] AS
$BODY$
DECLARE
result my_schema_and_table[];
BEGIN
FOR l IN SELECT * FROM table2 WHERE id in (my_argument) LOOP
SELECT * FROM my_schema_and_table;
END LOOP;
END;
...
I want to get union of each select in loop. one huge joined result.
Is this possible? Please help.
PL/pgSQL function
It could look like this:
CREATE OR REPLACE FUNCTION func1(_arr integer[])
RETURNS SETOF my_schema_and_table
LANGUAGE plpgsql AS
$func$
DECLARE
l record;
BEGIN
FOR l IN
SELECT *
FROM lookup_table
WHERE some_id = ANY(_arr)
LOOP
RETURN QUERY
SELECT *
FROM my_schema_and_table
WHERE link_id = l.link_id;
END LOOP;
END
$func$;
Assuming you actually want a SET of rows from your my_schema_and_table, not an array? To return the result of SELECT * FROM my_schema_and_table, declare the function as RETURNS SETOF my_schema_and_table
Rewrite the IN construct to = ANY(_arr). That's the way to use an array parameter directly. Logically equivalent.
Or use unnest() and join to the resulting table like #Clodoaldo demonstrates. That can be faster with long arrays.
Simplify to plain SQL function
This simple SQL function does the same:
CREATE OR REPLACE FUNCTION func2(_arr integer[])
RETURNS SETOF my_schema_and_table
LANGUAGE sql AS
$func$
SELECT t.*
FROM (SELECT unnest($1) AS some_id) x
JOIN lookup_table l USING (some_id)
JOIN my_schema_and_table t USING (link_id);
$func$
Assuming both tables have link_id.
Call:
SELECT * FROM func2('{21,31}'::int[]);
CREATE OR REPLACE FUNCTION function_which_i_want(my_argument integer[])
RETURNS my_schema_and_table[] AS
$BODY$
DECLARE
result my_schema_and_table[];
BEGIN
for l in
select t.*
from
table2 t
inner join
unnest(my_argument) m(id) on m.id = t.id
loop
SELECT * FROM my_schema_and_table;
END LOOP;
END;

Return a query from a function?

I am using PostgreSQL 8.4 and I want to create a function that returns a query with many rows.
The following function does not work:
create function get_names(varchar) returns setof record AS $$
declare
tname alias for $1;
res setof record;
begin
select * into res from mytable where name = tname;
return res;
end;
$$ LANGUAGE plpgsql;
The type record only allows single row.
How to return an entire query? I want to use functions as query templates.
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
BEGIN
RETURN QUERY
SELECT t.col_a, t.col_b -- must match RETURNS TABLE
FROM mytable t
WHERE t.name = _tname;
END
$func$ LANGUAGE plpgsql;
Call like this:
SELECT * FROM get_names('name')
Major points:
Use RETURNS TABLE, so you don't have to provide a list of column names with every call.
Use RETURN QUERY, much simpler.
Table-qualify column names to avoid naming conflicts with identically named OUT parameters (including columns declared with RETURNS TABLE).
Use a named variable instead of ALIAS. Simpler, doing the same, and it's the preferred way.
A simple function like this could also be written in LANGUAGE sql:
CREATE OR REPLACE FUNCTION get_names(_tname varchar)
RETURNS TABLE (col_a integer, col_b text) AS
$func$
SELECT t.col_a, t.col_b --, more columns - must match RETURNS above
FROM mytable t
WHERE t.name = $1;
$func$ LANGUAGE sql;