PostgreSQL spread array values as input parameters for using clause - postgresql

CREATE OR REPLACE FUNCTION demo(vsql text, vals text[])
RETURNS void AS
$BODY$
BEGIN
execute vsql using vals;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
select demo('select $1 || $2', ARRAY['Barrett', ' Gilmour'] );
ERROR: there is no parameter $2
LINE 1: select $1 || $2
The error is that postgres does not understand that the two items in the array must be spread to the input parameters $1 and $2. It understand the entire array as value for $1

As discussed in SO question https://dba.stackexchange.com/questions/83278/postgressql-dynamic-execute-with-argument-values-in-array, the EXECUTE ... USING will treat an array as a single parameter.
You could try this hack, which edits the passed SQL statement so that $n becomes $1[n]
vsql := regexp_replace( vsql, '[$]([0-9]+)', '$1[\1]', 'g' );
The array vals is treated as a single parameter but the parameterised select can index into it to get the elements.
a_horse_with_no_name mentioned your other problem which is that your function doesn't actually return anything. If you want to see what is being executed:
-- anyelement is a polymorphic pseudo-type
-- problem with returning 'setof record' is that select * from so_demov3() gives error
-- ERROR: a column definition list is required for functions returning "record"
drop function so_demov3(anyelement, text, text[]);
create function so_demov3(rec_type anyelement, p_sql text, p_vals text[]) returns setof anyelement as
$$
declare v_sql text;
begin
-- edit the executable SQL stmt so that $n -> $1[n]
v_sql := regexp_replace( p_sql, '[$]([0-9]+)', '$1[\1]', 'g' );
return query execute v_sql using p_vals;
end;
$$
language plpgsql volatile;
And call it like this:
select * from so_demov3(null::text, 'select $1 || $2', array['Barrett', 'Gilmour'] );

Related

postgresql dynamic query

I need to replace schema and table name by parameters in following function (that is currently working perfectly):
CREATE OR REPLACE FUNCTION public.my_function_119()
RETURNS integer
LANGUAGE plpgsql
AS $function$
DECLARE _check INTEGER;
BEGIN
SELECT SUM("length"/1000)
FROM public."National_Grid_multiline"
INTO _check;
RETURN _check;
END
$function$
I have tried following solution (and its numerous variations) :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE _check INTEGER;
BEGIN
RETURN
'(SELECT SUM((length/1000))::integer FROM ' || schema || '."' || tablename || '")::integer INTO _check' ;
RETURN _check;
END
$function$
but keep running into following error code :
psycopg2.errors.InvalidTextRepresentation: invalid input syntax for type integer: "(SELECT SUM((length/1000))::integer FROM public."National_Grid_multiline")::integer INTO _check"
CONTEXT: PL/pgSQL function my_function_119(text,text) while casting return value to function's return type
Why is this not working ? The 'length' column contains float values.
You have to use dynamic SQL, because you cannot use a parameter for an identifier.
Also, make sure to avoid SQL injection by using format rather than concatenating strings:
EXECUTE
format(
'SELECT SUM((length/1000))::integer FROM %I.%I',
schema,
table_name
)
INTO _check';
You can try this :
CREATE OR REPLACE FUNCTION public.my_function_119(schema text, tablename text)
RETURNS INTEGER
LANGUAGE plpgsql
AS
$function$
DECLARE
res integer ;
BEGIN
EXECUTE E'
(SELECT SUM((length/1000))::integer INTO res FROM ' || schema || '."' || tablename || '"):: integer' ;
RETURN res ;
END ;
$function$

Is it possible to nest an EXECUTE statement as a source recordset in PostgreSQL?

This is the case of a dynamic _sql select passed to a function that tries to execute the query and return a JSON array with the result.
create or replace function jlist_objects_bysql (
_sql varchar
)
returns json
as $$
select
json_agg (t)
from (
execute _sql
) as t;
$$ language sql;
The validation error in PostgreSQL 12 is
psql:objects_procedures.sql:874: ERROR: syntax error at or near "t"
LINE 8: from execute _sql t;
Dynamic SQL does not work with the language sql but rather with language plpgsql.
Then, you can enlarge the dynamic query
create or replace function jlist_objects_bysql (
_sql varchar
)
returns json
as $$
declare
output json;
BEGIN
execute 'select json_agg (t) from ( ' || _sql || '
) as t;'
INTO output;
return output;
END
$$ language plpgsql;
select jlist_objects_bysql('select * from test');

Execute decode function stored in bytea column

I have a bytea column in a table that contains a function decode(). What I have done to get the actual data is as follows:
select filename, convert_from(data,'UTF-8') from attachments limit 20; //this returns me decode function
select decode(E'...','hex'); // I am executing the above returned function
The above is fine as long as I have to select one row. But now my requirement is to get more than one result. How can I get the result in single query? I have tried using pl/pgsql
CREATE OR REPLACE FUNCTION get_data(integer, _type anyelement, OUT _result anyelement)
AS
$x$
BEGIN
EXECUTE
'SELECT ' || (select convert_from(data,'UTF-8') as data from attachments limit $1)
INTO _result;
END;
$x$
LANGUAGE plpgsql;
But this works only for single row and single column. What I want is a single query to fetch 2 columns without using pl/pgsql if possible. I am using this query from my Java based web app.
Thanks!
You need procedural code for this, since there is no provision for dynamic statements in SQL.
The following function converts all attachments:
CREATE FUNCTION getemall(
IN v_type anyelement,
OUT v_result anyelement
) RETURNS SETOF anyelement
LANGUAGE plpgsql AS
$$DECLARE
v_stmt text;
BEGIN
FOR v_stmt IN
SELECT convert_from(data,'UTF-8')
FROM attachments
LOOP
EXECUTE v_stmt INTO v_result;
RETURN NEXT;
END LOOP;
END;$$;
This is how I have written the function with few changes
CREATE OR REPLACE FUNCTION getmeall(tName text, fNameCol text, dataCol text,fSize
numeric)
RETURNS TABLE(bdata bytea, fname text) LANGUAGE plpgsql AS
$$DECLARE
v_stmt text;
v_name text;
BEGIN
FOR v_stmt,v_name IN
EXECUTE format('SELECT encode(%s, ''escape''), %s FROM %s
WHERE $1 IS NOT NULL AND $2 IS NOT NULL LIMIT $3'
, dataCol, fNameCol, tName)
USING dataCol, fNameCol, fSize
LOOP
fname:=v_name;
IF strpos(v_stmt,'decode') = 1 THEN
EXECUTE 'SELECT ' || v_stmt INTO bdata;
ELSE
bdata:=v_stmt;
END IF;
RETURN NEXT;
END LOOP;
END;$$;
And finally calling it this way.
select * from getmeall('attachments', '"filename"', '"data"',2)

Where the NOTICE or error messages?

No NOTICE neither error messages at
CREATE or replace FUNCTION copy_to_csv(
fname text,
query text,
header boolean DEFAULT true,
quotedfields text[] DEFAULT NULL,
usedate boolean DEFAULT true
) RETURNS text AS $f$
DECLARE
aux text :='';
BEGIN
RAISE NOTICE 'HELLO!!!!!';
IF p_quotedfields IS NOT NULL THEN
aux := ', FORCE_QUOTE('|| array_to_string(quote_ident(quotedfields),',') ||')';
END IF;
aux := format(
'COPY (%L) TO (%L) WITH (FORMAT CSV, HEADER %L%s)',
query,
CASE WHEN usedate THEN fname|| now()::date::text ELSE fname END ||'.csv',
header,
aux
);
RAISE NOTICE 'HELLO2';
EXECUTE aux;
RAISE NOTICE 'HELLO3';
RETURN aux;
END;
$f$ LANGUAGE plpgsql STRICT;
... Calling with select copy_to_csv(E'select * from t', '/tmp/t');. Using PostgreSQL v10 at UBUNTU 16 LTS.
But this function is working fine:
CREATE or replace FUNCTION test1() RETURNS void AS $f$
BEGIN
RAISE NOTICE 'HELLO!!!';
END;
$f$ LANGUAGE plpgsql STRICT;
PS: the quote_ident() overload also working fine, was implemented with
CREATE FUNCTION quote_ident(text[]) RETURNS text[] AS $f$
SELECT array_agg(quote_ident(x)) FROM unnest($1) t(x)
$f$ LANGUAGE SQL IMMUTABLE;
When the function is STRICT and one of the arguments is NULL, the function body is not executed and the result is NULL. Remove STRICT from the function definition.
Btw, you've mistaken the order of arguments.

Variable substitution in PL/pgSQL

I have a select statement that is generated dynamically based on the supplied parameter. The problem is that postgresql always says:
argument of WHERE must be type boolean, not type character varying no matter what the parameter is. Did I miss anything?
CREATE OR REPLACE FUNCTION getuid(name character varying) RETURNS integer AS $$
DECLARE
statement varchar;
uid integer;
BEGIN
IF ($1 = '') THEN
statement := 'TRUE';
statement := CAST(statement AS BOOLEAN);
ELSE
statement := 'users.keywords ILIKE''' || '%' || $1 || '%''';
END IF;
SELECT INTO uid id FROM users WHERE "statement";
RETURN uid;
END;
$$ LANGUAGE plpgsql
You need EXECUTE if you want to generate dynamic commands inside a function. You could also use two different sections:
CREATE OR REPLACE FUNCTION getuid(name character varying) RETURNS integer AS $$
DECLARE
statement varchar;
uid integer;
BEGIN
IF ($1 = '' OR $1 IS NULL) THEN -- section 1
SELECT id INTO uid FROM users;
ELSE -- section 2
SELECT id INTO uid FROM users WHERE users.keywords ILIKE '%' || $1 || '%';
END IF;
RETURN uid;
END;
$$ LANGUAGE plpgsql;
EXECUTE is a PL/pgSQL statement and not SQL statement. So you have to wrap your dynamic query into PL/pgSQL stored procedure.
Be careful about variable substitution and do not forget to use quote_literal() or quote_nullable() when building up you query.
Have look in documentation here: http://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN