Concatenate variable into dynamic SELECT statement in PL/pgSQL - sql

I want to use a variable in a dynamic SELECT statement (that is in an EXECUTE statement) like this:
CREATE OR REPLACE FUNCTION get_factor()
RETURNS TABLE(factor numeric) AS
$BODY$
DECLARE
_query character varying;
_some_condition integer := 50;
_result decimal;
BEGIN
_query := 'SELECT factors.factor_material FROM factors
WHERE factors.condition = _some_condition;';
EXECUTE _query INTO _result;
RETURN QUERY SELECT _result;
END;
I have a variable _some_condition integer := 50; and I want to concatenate its value into the SELECT statement (...WHERE factors.condition = _some_condition;) however this is giving me a "column does no exist" error:
ERROR: column "_some_condition" does not exist
LINE 1: ...erial WHERE factors.city_id = _some_cond...
^
CONTEXT: PL/pgSQL function get_factor() line 12 at EXECUTE statement
Why am I getting this error and how to fix this? Keep in mind I have to use a dynamic SELECT statement.

Since you are trying to pass a value (which is not the same as a literal, btw), all you need is the USING clause of the plpgsql EXECUTE command, like #Craig commented.
While being at it, simplify your function with RETURN QUERY EXECUTE:
CREATE OR REPLACE FUNCTION get_factor()
RETURNS TABLE(factor numeric) AS
$func$
DECLARE
_some_condition integer := 50;
BEGIN
RETURN QUERY EXECUTE
'SELECT factor_material
FROM factors
WHERE condition = $1'
USING _some_condition;
END
$func$ LANGUAGE plpgsql;
Nothing in this example requires dynamic SQL. You could just:
RETURN QUERY
SELECT f.factor_material
FROM factors f
WHERE f.condition = _some_condition;

Related

Control sort by in postgres SQL using parameter [duplicate]

How can I write a stored procedure that contains a dynamically built SQL statement that returns a result set? Here is my sample code:
CREATE OR REPLACE FUNCTION reporting.report_get_countries_new (
starts_with varchar,
ends_with varchar
)
RETURNS TABLE (
country_id integer,
country_name varchar
) AS
$body$
DECLARE
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
sql VARCHAR;
BEGIN
sql = 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name >= ' || starts_with ;
IF ends_with IS NOT NULL THEN
sql = sql || ' AND lookups.countries.country_name <= ' || ends_with ;
END IF;
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
This code returns an error:
ERROR: syntax error at or near "RETURN"
LINE 1: RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE o...
^
QUERY: RETURN QUERY SELECT * FROM omnipay_lookups.countries WHERE omnipay_lookups.countries.country_name >= r
CONTEXT: PL/pgSQL function "report_get_countries_new" line 14 at EXECUTE statement
I have tried other ways instead of this:
RETURN QUERY EXECUTE sql;
Way 1:
RETURN EXECUTE sql;
Way 2:
sql = 'RETURN QUERY SELECT * FROM....
/*later*/
EXECUTE sql;
In all cases without success.
Ultimately I want to write a stored procedure that contains a dynamic sql statement and that returns the result set from the dynamic sql statement.
There is room for improvements:
CREATE OR REPLACE FUNCTION report_get_countries_new (starts_with text
, ends_with text = NULL)
RETURNS SETOF lookups.countries AS
$func$
DECLARE
sql text := 'SELECT * FROM lookups.countries WHERE country_name >= $1';
BEGIN
IF ends_with IS NOT NULL THEN
sql := sql || ' AND country_name <= $2';
END IF;
RETURN QUERY EXECUTE sql
USING starts_with, ends_with;
END
$func$ LANGUAGE plpgsql;
-- the rest is default settings
Major points
PostgreSQL 8.4 introduced the USING clause for EXECUTE, which is useful for several reasons. Recap in the manual:
The command string can use parameter values, which are referenced in
the command as $1, $2, etc. These symbols refer to values supplied in
the USING clause. This method is often preferable to inserting data
values into the command string as text: it avoids run-time overhead of
converting the values to text and back, and it is much less prone to
SQL-injection attacks since there is no need for quoting or escaping.
IOW, it is safer and faster than building a query string with text representation of parameters, even when sanitized with quote_literal().
Note that $1, $2 in the query string refer to the supplied values in the USING clause, not to the function parameters.
While you return SELECT * FROM lookups.countries, you can simplify the RETURN declaration like demonstrated:
RETURNS SETOF lookups.countries
In PostgreSQL there is a composite type defined for every table automatically. Use it. The effect is that the function depends on the type and you get an error message if you try to alter the table. Drop & recreate the function in such a case.
This may or may not be desirable - generally it is! You want to be made aware of side effects if you alter tables. The way you have it, your function would break silently and raise an exception on it's next call.
If you provide an explicit default for the second parameter in the declaration like demonstrated, you can (but don't have to) simplify the call in case you don't want to set an upper bound with ends_with.
SELECT * FROM report_get_countries_new('Zaire');
instead of:
SELECT * FROM report_get_countries_new('Zaire', NULL);
Be aware of function overloading in this context.
Don't quote the language name 'plpgsql' even if that's tolerated (for now). It's an identifier.
You can assign a variable at declaration time. Saves an extra step.
Parameters are named in the header. Drop the nonsensical lines:
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
Use quote_literal() to avoid SQL injection (!!!) and fix your quoting problem:
CREATE OR REPLACE FUNCTION report_get_countries_new (
starts_with varchar,
ends_with varchar
)
RETURNS TABLE (
country_id integer,
country_name varchar
) AS
$body$
DECLARE
starts_with ALIAS FOR $1;
ends_with ALIAS FOR $2;
sql VARCHAR;
BEGIN
sql := 'SELECT * FROM lookups.countries WHERE lookups.countries.country_name ' || quote_literal(starts_with) ;
IF ends_with IS NOT NULL THEN
sql := sql || ' AND lookups.countries.country_name <= ' || quote_literal(ends_with) ;
END IF;
RETURN QUERY EXECUTE sql;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100 ROWS 1000;
This is tested in version 9.1, works fine.

How do I return a table from a function with a bespoke column name?

This function works:
CREATE OR REPLACE FUNCTION public.a()
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a from ztable';
END;
$function$;
But when I try to add some text to the column name:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as $1_a from ztable' using prefix;
END;
$function$;
This just fails as a syntax error on $1.
Or:
CREATE OR REPLACE FUNCTION public.a(prefix text)
RETURNS TABLE(a text)
LANGUAGE plpgsql
AS $function$
BEGIN
return query execute
'select a as '||prefix||'_a from ztable';
END;
$function$;
select * from a('some prefix') doesn't work.
Is there some other syntax that does the job?
That's simply not possible. SQL does not allow dynamic column names.
You must assign a column alias with the call. Like:
SELECT a AS prefix_a FROM public.a();
Or in a column definition list directly attached to the function:
SELECT * FROM public.a() AS f(prefix_a);
Or, while dealing with a single output column, even just:
SELECT * FROM public.a() AS prefix_a;
See:
RETURNING rows using unnest()?

ERROR: function json_typeof(json) does not exist

I am getting a problem while running my function in postgres version 9.3.9 It given me a below error when i run this:
Caused by: org.postgresql.util.PSQLException: ERROR: function json_typeof(json) does not exist
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Where: PL/pgSQL function myArray(character varying,json) line 9 at SQL statement
Here is my function:
CREATE OR REPLACE FUNCTION myArray(domain_name varchar, val json) RETURNS varchar LANGUAGE plpgsql STABLE AS $$
DECLARE
result varchar;
isarray BOOLEAN;
q cursor for
select json_agg(blogIn(null,b.value))
from json_array_elements_text(val) b;
BEGIN
SELECT json_typeof(val) = 'array' into isarray;
if not isarray THEN
return val;
end if;
open q;
fetch q into result;
close q;
if result is null then
return val;
end if;
return result;
END;
$$;
It is very strange that this function run without any problem in Postgres 9.5 version but on 9.3 it creating above error. Can someone tell me what actually type cast it require at "SELECT json_typeof(val) = 'array' into isarray;" ?
Surely you'll upgrade your Postgres soon. Before this happens you can use this function:
create or replace function is_json_array(json)
returns boolean language sql immutable as $$
select coalesce(left(regexp_replace($1::text, '\s', '', 'g'), 1) = '[', false)
$$;

SQL (Postgres) function definition - RETURNS

I am trying to create a very simple PostgreSQL function, though I keep getting a very strange syntax error. The syntax I use is different from anything I have seen online (though this is the one the textbook uses), and thus I can't figure out why it fails...
This is the SQL:
CREATE OR REPLACE FUNCTION gsum(graphID integer)
RETURNS integer
BEGIN
DECLARE total integer DEFAULT 0
SELECT sum(weight) INTO total
FROM Edge
WHERE gno = graphID
RETURN total;
END;
The error is:
ERROR: syntax error at or near "BEGIN"
LINE 3: BEGIN
^
********** Error **********
ERROR: syntax error at or near "BEGIN"
SQL state: 42601
Character: 68
Your basic mistakes:
DECLARE must come before BEGIN.
Statements need to be terminated with ;.
Function body of a plpgsql function is a string and needs to be quoted. Use dollar-quoting to avoid complications with quotes in the body.
Missing keyword AS.
Missing language declaration LANGUAGE plpgsql.
Type mismatch.
You don't need a default.
This would still return NULL if the sum is NULL.
CREATE OR REPLACE FUNCTION gsum(graphID integer)
RETURNS integer AS
$func$
DECLARE
total integer;
BEGIN
SELECT sum(weight)::int INTO total
FROM edge
WHERE gno = graphID;
RETURN COALESCE(total, 0);
END
$func$ LANGUAGE plpgsql;
And you'd better use a simple SQL function for this like #Clodoaldo advised. Just add COALESCE().
It can be plain SQL in instead of plpgsql
create or replace function gsum(graphid integer)
returns bigint as $$
select sum(weight) as total
from edge
where gno = graphid;
$$ language sql;
Notice that if weight is integer sum will return bigint not integer.
CREATE OR REPLACE FUNCTION gsum(graphID integer)
RETURNS integer AS
$BODY$
DECLARE
total integer DEFAULT 0;
BEGIN
SELECT sum(weight) INTO total
FROM Edge
WHERE gno = graphID;
RETURN total;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;

postgresql function error: column name does not exist

i've implemented a function that check if a value appears in a specific row of a specific table:
CREATE FUNCTION check_if_if_exist(id INTEGER, table_name character(50), table_column character(20) ) RETURNS BOOLEAN AS $$
DECLARE res BOOLEAN;
BEGIN
SELECT table_column INTO res
FROM table_name
WHERE table_column = id;
RETURN res;
END;
$$ LANGUAGE plpgsql
i've create and fill a simple test table for try this function:
CREATE TABLE tab(f INTEGER);
and i call function like
SELECT check_if_exist(10, tab, f);
but i occurs in this error:
ERROR: column "prova" does not exist
LINE 1: SELECT check_if_exist(10, tab, f);
^
********** Error **********
ERROR: column "tab" does not exist
SQL state: 42703
Character: 27
why?
In addition to Elmo response you must be careful with types. You have got:
ERROR: column "tab" does not exist
because SQL parser do not know how to deal with tab which is without quote. Your query must be like:
SELECT check_if_exist(10, 'tab', 'f');
As Elmo answered you use dynamic query, so even if you quote tab you will got error:
ERROR: relation "table_name" does not exist
so you can use EXECUTE, example:
CREATE OR REPLACE FUNCTION check_if_exist(id INTEGER, table_name varchar, table_column varchar) RETURNS BOOLEAN AS $$
DECLARE
sql varchar;
cnt int;
BEGIN
sql := 'SELECT count(*) FROM ' || quote_ident(table_name) || ' WHERE ' || quote_ident(table_column) || '=$1';
RAISE NOTICE 'sql %', sql;
EXECUTE sql USING id INTO cnt;
RETURN cnt > 0;
END;
$$ LANGUAGE plpgsql
You can also use VARCHAR instead of character(N) in function arguments and use CREATE OR REPLACE FUNCTION ... instead of just CREATE FUNCTION ... which is very handy at debugging.
Your code has no chance to work - when dealing with different tables in PLPGSQL you need to utilize dynamic queries, so EXECUTE is required - http://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
But first of all - there is nothing bad in using PostgreSQL EXISTS - http://www.postgresql.org/docs/current/static/functions-subquery.html#AEN15284 instead of inventing your own - performance of your solution will be significantly worse than using included batteries...
Hopefully this is helpful. Good luck.