Execute decode function stored in bytea column - postgresql

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)

Related

How to nest variable in query string passed to function

I need to be able to get the value stored inside rec_key.empname when I call this function:
CREATE OR REPLACE FUNCTION public.txt(text)
RETURNS SETOF record
LANGUAGE plpgsql
AS $function$
declare
var_param text;
var_req TEXT;
rec_key record;
cur_key CURSOR FOR Select empname::varchar from employee;
BEGIN
open cur_key;
loop
fetch cur_key into rec_key;
EXIT WHEN NOT FOUND;
var_req :=
'
' || $1 || '
';
return query execute var_req;
end loop;
close cur_key;
END
$function$
;
What do I have to change to get the desired empname when calling the function?
If I call it like this it doesn't work: :(
select * from public.txt('select empid, age::integer,''''''|rec_key.empname|''''''::varchar from employee') as (empid integer, age integer, empname varchar)
To address the question asked:
CREATE OR REPLACE FUNCTION public.txt(_sql text)
RETURNS SETOF record
LANGUAGE plpgsql AS
$func$
DECLARE
_rec record;
BEGIN
FOR _rec IN
SELECT empname::text FROM employee
LOOP
RETURN QUERY EXECUTE _sql
USING _rec.empname;
END LOOP;
END
$func$;
Call:
SELECT * FROM public.txt('SELECT empid, age::integer, $1 AS empname FROM employee')
AS (empid integer, age integer, empname varchar);
The example does not make any sense, though, and all of it could be replaced with a simple query. See my anser to your earlier question:
Doesn't find variable when passing query as parameter
Use the much simpler implicit cursor of a FOR loop. See:
Cursor based records in PostgreSQL
Truncating all tables in a Postgres database
Pass the variable as value with a USING clause. $1 is the symbol to reference the first USING argument. See:
Replace double quotes with single quotes in Postgres (plpgsql)

postgresql for loop script in text form can not be executed

I am trying to write function in postgresql, that creates temp_table with columns table_name text, table_rec jsonb and fill it through for loop with table names from my table containing names of tables and records in json. I have the for loop in string and I want to execute it. But it doesnt work.
I have variable rec record, sql_query text and tab_name text and I want to do this:
CREATE OR REPLACE FUNCTION public.test51(
)
RETURNS TABLE(tabel_name text, record_json jsonb)
LANGUAGE 'plpgsql'
COST 100
VOLATILE
ROWS 1000
AS $BODY$
declare
rec record;
tabel_name text;
tabel_names text[];
counter integer := 1;
sql_query text;
limit_for_sending integer;
rec_count integer;
begin
select into tabel_names array(select "TABLE_NAME" from public."TABLES");
create temp table temp_tab(tab_nam text, recik jsonb);
while array_length(tabel_names, 1) >= counter loop
tabel_name := '"' || tabel_names[counter] || '"';
select into limit_for_sending "TABLE_LIMIT_FOR_SENDING_DATA" from public."TABLES" where "TABLE_NAME" = tabel_name;
sql_query := 'select count(*) from public.' || tabel_name;
execute sql_query into rec_count;
if (rec_count >= limit_for_sending and limit_for_sending is not null) then
sql_query := 'for rec in select * from public.' || tabel_name || '
loop
insert into temp_tab
select ' || tabel_name || ', to_jsonb(rec);
end loop';
execute sql_query;
end if;
counter := counter + 1;
end loop;
return query
select * from temp_tabik;
drop table temp_tabik;
end;
$BODY$;
Thank you for response.
It seems you have some table that contains the information for which tables you want to return all rows as JSONB. And that meta-table also contains a column that sets a threshold under which the rows should not be returned.
You don't need the temp table or an array to store the table names. You can iterate through the query on the TABLES table and run the dynamic SQL directly in that loop.
return query in PL/pgSQL doesn't terminate the function, it just appends the result of the query to the result of the function.
Dynamic SQL is best created using the format() function because it is easier to read and using the %I placeholder will properly deal with quoted identifiers (which is really important as you are using those dreaded upper case table names)
As far as I can tell, your function can be simplified to:
CREATE OR REPLACE FUNCTION public.test51()
RETURNS TABLE(tabel_name text, record_json jsonb)
LANGUAGE plpgsql
AS
$BODY$
declare
rec record;
sql_query text;
rec_count bigint;
begin
for rec in
select "TABLE_NAME" as table_name, "TABLE_LIMIT_FOR_SENDING_DATA" as rec_limit
from public."TABLES"
loop
if rec.rec_limit is not null then
execute format('select count(*) from %I', rec.table_name)
into rec_count;
end if;
if (rec.rec_limit is not null and rec_count >= rec.rec_limit) then
sql_query := format('select %L, to_jsonb(t) from %I as t', rec.table_name, rec.table_name);
return query execute sql_query;
end if;
end loop;
end;
$BODY$;
Some notes
the language name is an identifier and should not be enclosed in single quotes. This syntax is deprecated and might be removed in a future version so don't get used to it.
you should really avoid those dreaded quoted identifiers. They are much more trouble than they are worth it. See the Postgres wiki for details.

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');

PostgreSQL spread array values as input parameters for using clause

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'] );

"INSERT INTO ... FETCH ALL FROM ..." can't be compiled

I have some function on PostgreSQL 9.6 returning a cursor (refcursor):
CREATE OR REPLACE FUNCTION public.test_returning_cursor()
RETURNS refcursor
IMMUTABLE
LANGUAGE plpgsql
AS $$
DECLARE
_ref refcursor = 'test_returning_cursor_ref1';
BEGIN
OPEN _ref FOR
SELECT 'a' :: text AS col1
UNION
SELECT 'b'
UNION
SELECT 'c';
RETURN _ref;
END
$$;
I need to write another function in which a temp table is created and all data from this refcursor are inserted to it. But INSERT INTO ... FETCH ALL FROM ... seems to be impossible. Such function can't be compiled:
CREATE OR REPLACE FUNCTION public.test_insert_from_cursor()
RETURNS table(col1 text)
IMMUTABLE
LANGUAGE plpgsql
AS $$
BEGIN
CREATE TEMP TABLE _temptable (
col1 text
) ON COMMIT DROP;
INSERT INTO _temptable (col1)
FETCH ALL FROM "test_returning_cursor_ref1";
RETURN QUERY
SELECT col1
FROM _temptable;
END
$$;
I know that I can use:
FOR _rec IN
FETCH ALL FROM "test_returning_cursor_ref1"
LOOP
INSERT INTO ...
END LOOP;
But is there better way?
Unfortunately, INSERT and SELECT don't have access to cursors as a whole.
To avoid expensive single-row INSERT, you could have intermediary functions with RETURNS TABLE and return the cursor as table with RETURN QUERY. See:
Return a query from a function?
CREATE OR REPLACE FUNCTION f_cursor1_to_tbl()
RETURNS TABLE (col1 text) AS
$func$
BEGIN
-- MOVE BACKWARD ALL FROM test_returning_cursor_ref1; -- optional, see below
RETURN QUERY
FETCH ALL FROM test_returning_cursor_ref1;
END
$func$ LANGUAGE plpgsql; -- not IMMUTABLE
Then create the temporary table(s) directly like:
CREATE TEMP TABLE t1 ON COMMIT DROP
AS SELECT * FROM f_cursor1_to_tbl();
See:
Creating temporary tables in SQL
Still not very elegant, but much faster than single-row INSERT.
Note: Since the source is a cursor only the first call succeeds. Executing the function a second time would return an empty set. You would need a cursor with the SCROLL option and move to the start for repeated calls.
This function does INSERT INTO from refcursor. It is universal for all the tables. The only requirement is that all columns of table corresponds to columns of refcursor by types and order (not necessary by names).
to_json() does the trick to convert any primitive data types to string with double-quotes "", which are later replaced with ''.
CREATE OR REPLACE FUNCTION public.insert_into_from_refcursor(_table_name text, _ref refcursor)
RETURNS void
LANGUAGE plpgsql
AS $$
DECLARE
_sql text;
_sql_val text = '';
_row record;
_hasvalues boolean = FALSE;
BEGIN
LOOP --for each row
FETCH _ref INTO _row;
EXIT WHEN NOT found; --there are no rows more
_hasvalues = TRUE;
SELECT _sql_val || '
(' ||
STRING_AGG(val.value :: text, ',') ||
'),'
INTO _sql_val
FROM JSON_EACH(TO_JSON(_row)) val;
END LOOP;
_sql_val = REPLACE(_sql_val, '"', '''');
_sql_val = TRIM(TRAILING ',' FROM _sql_val);
_sql = '
INSERT INTO ' || _table_name || '
VALUES ' || _sql_val;
--RAISE NOTICE 'insert_into_from_refcursor(): SQL is: %', _sql;
IF _hasvalues THEN --to avoid error when trying to insert 0 values
EXECUTE (_sql);
END IF;
END;
$$;
Usage:
CREATE TABLE public.table1 (...);
PERFORM my_func_opening_refcursor();
PERFORM public.insert_into_from_refcursor('public.table1', 'name_of_refcursor_portal'::refcursor);
where my_func_opening_refcursor() contains
DECLARE
_ref refcursor = 'name_of_refcursor_portal';
OPEN _ref FOR
SELECT ...;