cannot pass dynamic query to sql-function - sql

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.

Related

PostgreSQL - Function with conditional local variable

I want to create a PostgreSQL function that will filter out table based on selected parameters.
However, I also need to perform some more complex logic based on the argument supplied. So I wanted to declare a local variable which would be based on some conditional logic.
What I have:
CREATE OR REPLACE FUNCTION get_something(parameter1 INT, parameter2 VARCHAR[])
DECLARE
conditional_variable := (
IF $1 = 50 THEN
'result-1'
ELSIF $1 = 100 THEN
ARRAY['result-2', 'result-3']
END IF;
)
RETURNS TABLE (
"time" BIGINT,
some_column NUMERIC
) AS $$
SELECT
time,
some_column
FROM "SomeNiceTable"
WHERE time = $1
AND some_dimension = ANY($2::VARCHAR[])
AND some_other_dimension = ANY(conditional_variable::VARCHAR[]);
$$ LANGUAGE SQL;
But it does not work this way. Is there a way how to achieve such thing?
You can not have DECLARE block and variables in a language sql function.
So you need to switch to language plpgsql and adjust the structure to be valid PL/pgSQL
CREATE OR REPLACE FUNCTION get_something(parameter1 INT, parameter2 VARCHAR[])
RETURNS TABLE ("time" BIGINT, some_column NUMERIC)
AS
$$
declare
conditional_variable text[];
begin
IF parameter1 = 50 THEN
conditional_variable := array['result-1'];
ELSIF parameter1 = 100 THEN
conditional_variable := ARRAY['result-2', 'result-3']
ELSE
????
END IF;
return query
SELECT
time,
some_column
FROM "SomeNiceTable"
WHERE time = $1
AND some_dimension = ANY($2::VARCHAR[])
AND some_other_dimension = ANY(conditional_variable);
END;
$$
LANGUAGE plpgsql;

postgres procedure list of values

I'm trying to pass in a list of values into a Postgres 11 procedure. The query has an IN condition. How do I pass in a list of values to the procedure & then how do I use it in the query?
create or replace procedure some_procedure(
source_id_list character varying <-- is this correct?
)
language plpgsql
as $$
declare
result_count int;
begin
select count(*)
into result_count
from table sd
where sd.id in source_id_list <-- syntax error
;
RAISE NOTICE 'result: %', result_count;
end;
$$
IN expects a list of values, while you are giving it a string.
If you can change the datatype of the argument to the procedure, then it would be simpler to use an array:
create or replace procedure some_procedure(source_id_list int[])
language plpgsql
as $$
declare
result_count int;
begin
select count(*)
into result_count
from table sd
where id = any(source_id_list);
raise notice 'result: %', result_count;
end;
$$
If you can't change the argument of the datatype, then another option uses string_to_array() to parse the string as an array:
select count(*)
into result_count
from table sd
where sd.id = any(string_to_array(source_id_list, ',')::int[]);

Postgres function to return Table into variables

How can I capture different columns into different variables like so (note this is only pseudocode so I am assuming it will cause errors. Example taken from here)
create or replace function get_film (
p_pattern varchar
)
returns table (
film_title varchar,
film_release_year int
)
language plpgsql
as $$
begin
return query
select
title,
release_year::integer
from
film
where
title ilike p_pattern;
end;$$
create or replace function get_film_into_variables (
p_pattern varchar
)
returns null
language plpgsql
as $$
declare
v_title varchar,
v_release_year integer
begin
SELECT
get_film (p_pattern)
INTO
v_title,
v_release_year;
end;$$
Assuming you have some purpose for the variables after retrieving them not just ending the function your "get_film_into_variables" is almost there. But first let's backup just a bit. A function that returns a table does just that, you can use the results just like a table stored on disk (it just goes away after query or calling function ends). To that end only a slight change to the "get_film_into_variables" function is required. The "get_film" becomes the object of the FROM clause. Also change the returns null, to returns void. So
create or replace function get_film_into_variables (
p_pattern varchar
)
returns void
language plpgsql
as $$
declare
v_title varchar;
v_release_year integer;
begin
select *
from get_film (p_pattern)
INTO
v_title,
v_release_year;
end;
$$;
The above works for a single row returned by a function returning table. However for a return of multiple rows you process the results of the table returning function just lake you would an actual table - with a cursor.
create or replace
function get_film_into_variables2(p_pattern varchar)
returns void
language plpgsql
as $$
declare
k_message_template constant text = 'The film "%s" was released in %s.';
v_title varchar;
v_release_year integer;
v_film_message varchar;
c_film cursor (c_pattern varchar) for
select * from get_film (c_pattern);
begin
open c_film (p_pattern);
loop
fetch c_film
into v_title
, v_release_year;
exit when not found;
v_film_message = format( k_message_template,v_title,v_release_year::text);
raise notice using
message = v_film_message;
end loop;
end;
$$;
BTW: the get_film function can be turned into a SQL function. See fiddle here. For demo purposes get_film_into_variable routines return a message.

Create function which return size of table

My code:
CREATE OR REPLACE FUNCTION sizeOfTableFunction
(
p_tableName varchar(100)
)
RETURNS integer
AS $$
DECLARE
p_tableSize integer;
BEGIN
SELECT count(*) into p_tableSize from p_tableName;
return p_tableSize;
END;
$$ LANGUAGE plpgsql STRICT;
Function has been created properly:
CREATE FUNCTION
Execute:
SELECT * FROM sizeOfTableFunction('Run');
Output - problem with executing the function?:
mydb=> SELECT * FROM sizeOfTableFunction('Run');
ERROR: relation "p_tablename" does not exist
LINE 1: SELECT count(*) from p_tableName
^
QUERY: SELECT count(*) from p_tableName
CONTEXT: PL/pgSQL function "sizeoftablefunction" line 5 at SQL statement
You need dynamic SQL for that:
CREATE OR REPLACE FUNCTION sizeOfTableFunction
(
p_tableName varchar(100)
)
RETURNS integer
AS $$
DECLARE
p_tableSize integer;
BEGIN
execute 'SELECT count(*) from '||p_tableName into p_tablesize; -- this is the difference
return p_tableSize;
END;
$$ LANGUAGE plpgsql STRICT;
To be safe, it's better to use the quote_ident function, just in case your tablename contains special characters. It also gives you some protection from SQL injection.
execute 'SELECT count(*) from '||quote_ident(p_tableName) into p_tablesize;

SQL query with variables in Postgresql

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;