Include parameter in function to UNION multiple PostgreSQL tables - sql

I have developed a function to UNION ALL tables from a list of table names (a table called tablelist below) inspired by this SO post.
The initial function just returns a selection, but now I'd like to write a new table with a name taken from a parameter new_table_name.
I'm struggling with the syntax to insert the parameter into the DROP TABLE AND CREATE TABLE statements. Here's one of the attempts which returns ERROR: mismatched parentheses at or near ";"
DROP FUNCTION IF EXISTS f_multi_union(text);
CREATE OR REPLACE FUNCTION f_multi_union(new_tab_name text)
RETURNS Table (my_id int, metric double precision, geom geometry)
LANGUAGE plpgsql AS
$func$
BEGIN
RETURN QUERY EXECUTE
(
DROP TABLE IF EXISTS working.'' || new_tab_name || '';
CREATE TABLE working.'' || new_tab_name || '' AS (
SELECT string_agg(format('SELECT * FROM %s', tbl), ' UNION ALL ')
FROM (SELECT tbl FROM working.tablelist) sub
)
);
END
$func$;

Something like this?
DROP FUNCTION IF EXISTS f_multi_union(text);
CREATE OR REPLACE FUNCTION f_multi_union(new_tab_name text)
RETURNS void -- nothing to return
LANGUAGE plpgsql AS
$func$
DECLARE
_sql TEXT;
BEGIN
_sql := format('DROP TABLE IF EXISTS working.%I;', new_tab_name); -- avoid SQL injection
EXECUTE _sql;
_sql := 'SELECT string_agg(format(''SELECT * FROM %I'', tbl), '' UNION ALL '')
FROM (SELECT tbl FROM working.tablelist) sub;';
EXECUTE _sql
INTO _sql; -- overwrite current _sql content
_sql := format('CREATE TABLE working.%I AS %s;', new_tab_name, _sql);
EXECUTE _sql;
END
$func$;
I would replace the * in the SELECT statement with the columns that you need.

Related

How to run get the result from a query built from SELECT FORMAT in Postgresql at pgadmin?

I have a command to run in pgadmin which looks like below:
SELECT format('SELECT * FROM %I.%I CROSS JOIN LATERAL json_to_record(%I::json) AS rs(%s)', 'public', 'vehicles', 'column_A', array_to_string(
(SELECT ARRAY(SELECT DISTINCT col FROM vehicles CROSS JOIN LATERAL json_object_keys(column_A::json) AS t(col) ORDER BY col)), ' text , '
) || ' text')
It prints a string starting with SELECT statement.
How do I get the result from the query straight from the string returned by the FORMAT?
I have tried something like:
DO
$$
WITH str as( SELECT format('SELECT * FROM %I.%I CROSS JOIN LATERAL json_to_record(%I::json) AS rs(%s)', 'public', 'vehicles', 'column_A', array_to_string(
(SELECT ARRAY(SELECT DISTINCT col FROM vehicles CROSS JOIN LATERAL json_object_keys(column_A::json) AS t(col) ORDER BY col)), ' text , '
) || ' text'))
BEGIN EXECUTE str;
END
$$
However, I got an error message saying:
ERROR: syntax error at or near "WITH"
What have I missed here? Please advise!!
Updated answer
After combining answers from the experts below, here is the updated version for future reference:
do $$
DECLARE
query text;
begin
query := format('SELECT * FROM %I.%I CROSS JOIN LATERAL json_to_record(%I::json) AS rs(%s)', 'public', 'vehicles', 'column_A', array_to_string(
(SELECT ARRAY(SELECT DISTINCT col FROM vehicles CROSS JOIN LATERAL json_object_keys(column_A::json) AS t(col) ORDER BY col)), ' text , '
) || ' text');
execute format('create or replace temp view tmp_view_vehicles as %s', query);
end $$;
select * from tmp_view_vehicles;
Thank you everyone & your patience!
If you don't want to create the stored function but want to get the result using anonymous do block then you could to use temporary view:
do $$
begin
execute format('create or replace temp view tmp_view_123 as select ...', ...);
end $$;
select * from tmp_view_123;
Created view is visible for the current session only.
demo
You are mixing up SQL and PL/pgSQL syntax, and not in a very consistent fashion.
Define a PL/pgSQL variable:
DO
$$DECLARE
query text;
result record;
BEGIN
query := format(...);
EXECUTE query INTO result;
END;$$;
The plain answer is
do language plpgsql
$$
begin
EXECUTE format('SELECT ....' <your code here>);
end;
$$;
However anonymous blocks do not return anything. Maybe you'll have to shape the block as a table-returning function.
Edit
I do not think that there is a straightforward way to do this - change the return table structure of a function dynamically. But you can return a single json column with key-value pairs inside.
Here is such a function:
create or replace function query_to_jsonset(qr text) returns setof json as
$$
begin
return query execute 'SELECT row_to_json(dyntbl) FROM ('||qr||') AS dyntbl';
end;
$$ language plpgsql;
and then your query will look simple as that:
select js from query_to_jsonset(format(....)) js;
Please note that query_to_jsonset is unsafe.

sqlconcat two variables postgresql

Im writing a psql function. I want to do a concat between two variables inside psql.
and im getting syntax_error.
Let's notice that date_contract is of type date.
Thank you Who can help me
declare
result_table regclass := $$public.contract$$||text;
time_now time
BEGIN
execute $$SELECT MIN(date_contract) FROM $$||result_table INTO date_;
execute $$SELECT CURRENT_TIMESTAMP::time FROM $$||result_table INTO time_now;
execute $$
INSERT INTO $$||result_table||$$
(id, dat_beg_contract, dat_end_contract,date_contract, long_c)
SELECT id, dat_beg_contract, dat_end_contract, sum(extract(epoch from (least(s.dat_beg_contract, gs.date_contract||time_ + interval '1 day')::timestamp -
greatest(s.dat_beg_contract, gs.date_contract)
)
) / 60) as long_c
$$;
END;
This code block might give you an idea of how to concatenate variables
DO $$
DECLARE
rec1 text;rec2 text;
BEGIN
EXECUTE 'SELECT ''foo'' ' INTO rec1;
EXECUTE 'SELECT ''bar'' 'INTO rec2;
RAISE NOTICE 'Option 1 %, Opiton 2: %', rec1||rec2, rec1||' '||rec2 ;
END; $$ LANGUAGE plpgsql;
NOTICE: Option 1 foobar, Opiton 2: foo bar
EDIT Returning a query with a function
CREATE TABLE tab (id int);
INSERT INTO tab VALUES (100),(42);
CREATE OR REPLACE FUNCTION myfunc (table_name TEXT)
RETURNS TABLE (res int) AS $$
DECLARE v1 int; v2 int;
BEGIN
EXECUTE 'SELECT max(id) FROM '||table_name INTO v1;
EXECUTE 'SELECT min(id) FROM '||table_name INTO v2;
RETURN QUERY EXECUTE 'SELECT ' || v1 || v2 ;
END;
$$ LANGUAGE plpgsql;
SELECT myfunc('tab');
myfunc
--------
10042
EDIT 2 Example concatenating timestamp and integer
CREATE TABLE tab (id int);
INSERT INTO tab VALUES (100),(42);
CREATE OR REPLACE FUNCTION myfunc (table_name TEXT)
RETURNS TABLE (res text) AS $$
DECLARE v1 int; v2 timestamp;
BEGIN
EXECUTE 'SELECT min(id) FROM '||table_name INTO v1;
EXECUTE 'SELECT CURRENT_TIMESTAMP' INTO v2;
RETURN QUERY EXECUTE 'SELECT ' || quote_literal(v1 || ' - ' ||v2) ;
END;
$$ LANGUAGE plpgsql;
SELECT myfunc('tab');
EXECUTE 'SELECT min(id) FROM '||table_name INTO v1;
EXECUTE 'SELECT CURRENT_TIMESTAMP' INTO v2;
RETURN QUERY EXECUTE 'SELECT ' || quote_literal(v1::text || ' - ' ||v2) ;
END;
$$ LANGUAGE plpgsql;
SELECT myfunc('tab');
myfunc
--------------------------------
42 - 2021-02-02 15:54:24.24179
(1 Zeile)
EDIT 3 let me know if this works, so that I can clean the answer
CREATE TABLE tab (date_contract date);
INSERT INTO tab VALUES (current_date+7),(current_date);
CREATE OR REPLACE FUNCTION myfunc (table_name TEXT)
RETURNS TABLE (date_contract date, col_new text) AS $$
DECLARE date_ date;
BEGIN
EXECUTE 'SELECT min(date_contract) FROM '||table_name INTO date_;
RETURN QUERY EXECUTE 'SELECT date_contract,'|| quote_literal(date_|| ' ' ||current_time) ||' FROM ' || table_name;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM myfunc('tab');
date_contract | col_new
---------------+-------------------------------
2021-02-09 | 2021-02-02 16:29:49.013344+01
2021-02-02 | 2021-02-02 16:29:49.013344+01
(2 Zeilen)

How to use string with apostrophe in function variable plpgsql

Hello I am having trouble querying when I have apostrophe in my where clause in postgresql using pgpsql function, I know that manually I could do something like:
select 'author''s'
however my word is stored in a variable, here is my function:
CREATE OR REPLACE FUNCTION public.fn_inserir_doc(caminho_arqv text, conteudo text)
RETURNS void
LANGUAGE plpgsql
AS $function$
declare
conteudo_array text array;
palavra text;
begin
execute 'insert into documento(caminho)
select ''' || caminho_arqv || '''
where not exists(select id
from documento
where caminho='''||caminho_arqv||''')';
conteudo_array := regexp_split_to_array(conteudo, E'\\s+');
FOREACH palavra in array conteudo_array
loop
if length(palavra) >=3 then
raise notice 'palavra: %', palavra;
execute 'insert into termo(descricao)
select ''' || palavra || '''
where not exists(
select id from termo
where descricao='''||palavra||''')';
execute 'insert into documento_termo(id_termo, id_documento, frequencia)
select t.id, d.id, 1
from termo t
cross join documento d
where t.descricao = '''|| palavra ||'''
and d.caminho = '''|| caminho_arqv ||'''
on conflict (id_termo, id_documento) do update set frequencia = documento_termo.frequencia + 1;';
end if;
end loop;
end;
$function$
The following sample is the one that has the problem:
select id from termo
where descricao='''||palavra||'''
because palavra contains single quote
Use dollar quoting and the function format(). Example:
create or replace function test(str text)
returns setof text language plpgsql as $$
begin
-- instead of this:
-- return query execute 'select '''||str||'''::text';
-- use:
return query execute format(
$fmt$
select %L::text
$fmt$, str);
end $$;
select * from test('O''Brian');
test
---------
O'Brian
(1 row)

Debugging a PL/pgSQL function

I am trying to get this PL/pgSQL function to work :
CREATE OR REPLACE FUNCTION loopcolumns2(tableName TEXT, pourcentage real)
RETURNS void AS $$
DECLARE
_name text;
missing_percentage real;
BEGIN
FOR _name IN SELECT column_name from information_schema.columns where table_name=tableName LOOP
SELECT 100 - (count(_name) * 100) / count(*) INTO missing_percentage FROM tableName;
IF (missing_percentage > pourcentage)
THEN ALTER TABLE tableName DROP COLUMN _name;
END IF;
END LOOP;
END; $$ LANGUAGE plpgsql;
The goal of the function is to loop through all the columns of a table, a delete the columns where the percentage of missing values is bigger than an input percentage.
I get the following error :
SELECT 100 - (count( $1 ) * 100) / count(*) FROM $2
^
CONTEXT: SQL statement in PL/PgSQL function "loopcolumns2" near line 6
You're trying to SELECT from text stored by tableName vaiable. You cannot do that because Postgres think you want him to select from table named tableName, but probably such table doesn't exist.
You nead to create dynamic query in string and use EXECUTE ... INTO ... statement. Like this:
DECLARE query TEXT;
...
query := 'SELECT 100 - (count(' || _name::TEXT || ') * 100) / count(*) FROM '
|| tableName::TEXT;
EXECUTE query INTO percentage ;
...

How can I show all tables in data output tab for plpgsql function?

DECLARE
alltables record;
table_all varchar;
BEGIN
for alltables in select distinct table_name , column_name
from information_schema.colunms
loop
table_all = alltables.table_name;
raise notice 'TAB_Name:% , table_all;
end loop;
return table_all;
In here, I can see all tables in (raise notice 'TAB_Name:% , table_all;) message tab in PgAdmin
but Data output tab (return table_all;) return only one column
How can I show all the tables in the data output tab?
I am not sure, if I understand to your query. You wont to write table function probably.
CREATE OR REPLACE FUNCTION xxx
RETURNS TABLE(table_name text, column_name text)
AS $$
BEGIN
FOR table_name, column_name IN
SELECT c.table_name, c.column_name
FROM information_schema.columns
LOOP
RETURN NEXT;
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql;
or little bit simply and little bit faster
CREATE OR REPLACE FUNCTION xxx
RETURNS TABLE(table_name text, column_name text)
AS $$
BEGIN
RETURN QUERY
SELECT c.table_name, c.column_name
FROM information_schema.columns
RETURN;
END;
$$ LANGUAGE plpgsql;
you can call it
SELECT * FROM xxx();