How to Run Multiple Dynamic Queries in a PostgreSQL Function - postgresql

I am having some issues figuring out how to run multiple dynamic queries in a single function.
CREATE OR REPLACE FUNCTION cnms_fy22q2.test_function(
fyq text)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
-- logic
TRUNCATE TABLE 'schema_' || fyq || '.my_table'
DROP TABLE 'schema_' || fyq || '.my_table';
END;
$BODY$;
I am generally getting syntax errors, like ERROR: syntax error at or near .... What am I doing wrong here?

You can't simply concatenate strings to make a dynamic sql statement. Take a look at EXECUTE and EXECUTE IMMEDIATE.
In your case, you could use it like this:
CREATE OR REPLACE FUNCTION cnms_fy22q2.test_function(
fyq text)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
-- logic
EXECUTE 'TRUNCATE TABLE schema_' || fyq || '.my_table';
EXECUTE 'DROP TABLE schema_' || fyq || '.my_table';
END
$BODY$;

Use the format function which will return a string and execute in your function.
create function permanently_delete_table(fyq text) RETURNS void
LANGUAGE plpgsql AS $$
declare
begin
EXECUTE format('TRUNCATE TABLE schema_%s.my_table',fyq);
EXECUTE format('DROP TABLE schema_%s.my_table',fyq);
end
$$;
Demo in sqldaddy.io

Just to add a third option that I came up with which combines the queries inside a single EXECUTE format(); statement, here's what I came up with:
CREATE OR REPLACE FUNCTION cnms_fy22q2.drop_summary_tables_function(
fyq text)
RETURNS void
LANGUAGE 'plpgsql'
COST 100
VOLATILE
AS $BODY$
BEGIN
-- logic
EXECUTE format(
'TRUNCATE TABLE schema_%s.my_table;
DROP TABLE schema_%s.my_table', fyq, fyq);
END;
$BODY$;

Related

Argument not taking the value from Postgres function

I have a simple Postgres function where I want to take table_name as a parameter and pass it into an argument and delete the data from table by condition.
CREATE OR REPLACE FUNCTION cdc.audit_refresh(tablename text)
RETURNS integer AS
$$
BEGIN
delete from tablename where id<4;
RETURN(select 1);
END;
$$ LANGUAGE plpgsql;
select cdc.audit_refresh('cdc.adf_test');
But it throws out an error that tablename
ERROR: relation "tablename" does not exist in the delete statement.(refer snapshot)
What you want to achieve is to execute Dynamic SQL statements. You can do this with EXECUTE. See more here
CREATE OR REPLACE FUNCTION audit_refresh(tablename text)
RETURNS integer AS
$$
DECLARE
stmt TEXT;
BEGIN
stmt = 'delete from '||tablename||' where id<4;';
EXECUTE stmt;
RETURN 1;
END
$$ LANGUAGE plpgsql;

Using to_jsonb(NEW) and execute

CREATE OR REPLACE FUNCTION change_trigger() RETURNS trigger AS $$
BEGIN
INSERT INTO static_table_name (content) VALUES (to_jsonb(NEW));
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
↑ is working, but I'd like to have opportunity to send target table name for inserting.
So,
by the code above, I was able to use dymanic_table_name,
CREATE OR REPLACE FUNCTION change_trigger() RETURNS trigger AS $$
DECLARE
dymanic_table_name TEXT;
BEGIN
dymanic_table_name := TG_ARGV[0];
EXECUTE 'INSERT INTO ' || dymanic_table_name || ' (content) VALUES (' || to_json(NEW) || ');';
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
but it fails, when try to insert result of 'to_json' function...
ERROR:
ERROR: "{"またはその近辺で構文エラー
LINE 1: ..._table (content) VALUES ({"id":43,"...
^
※ Sorry for Japanese syntax ( *´艸`)
You'll have to use dynamic SQL like this:
EXECUTE
format(
'INSERT INTO %I (content) VALUES (to_json($1))',
dymanic_table_name
)
USING NEW;

How to create a trigger function dynamically in pgsql?

I want to write a pgsql function to create trigger dynamically. For example, a trigger to count insertions in each table. I've tried EXECUTE like this:
CREATE FUNCTION trigen(tbl text) RETURNS void AS $$
BEGIN
EXECUTE format(
'CREATE FUNCTION %s_insertCnt() RETURNS TRIGGER AS $$
BEGIN
UPDATE insertions SET n = n + 1 WHERE tablename = %s;
END
$$ LANGUAGE plpgsql', tbl, quote_nullable(tbl));
EXECUTE format('CREATE TRIGGER %s_inCnt BEFORE INSERT ON %s
FOR EACH ROW EXECUTE PROCEDURE %s_insertCnt();', tbl, tbl, tbl);
END
$$ LANGUAGE plpgsql
But this approach doesn't work. A lot of syntax error occurred when I import this code. It seems that EXECUTE cannot execute a function creation.
What else can I do to create trigger functions dynamically?
The two $$ sections were getting confused. By using the $name$ syntax instead you can separate these.
Also the trigger was missing a RETURN.
CREATE OR REPLACE FUNCTION trigen(tbl text) RETURNS void AS $T1$
BEGIN
EXECUTE format(
'CREATE FUNCTION %s_insertCnt() RETURNS TRIGGER AS $T2$
BEGIN
UPDATE insertions SET n = n + 1 WHERE tablename = %s;
RETURN NEW;
END
$T2$ LANGUAGE plpgsql', tbl, quote_nullable(tbl));
EXECUTE format('CREATE TRIGGER %s_inCnt BEFORE INSERT ON %s
FOR EACH ROW EXECUTE PROCEDURE %s_insertCnt();', tbl, tbl, tbl);
END
$T1$ LANGUAGE plpgsql;

How to use EXECUTE FORMAT ... USING in postgres function

CREATE OR REPLACE FUNCTION dummytest_insert_trigger()
RETURNS trigger AS
$BODY$
DECLARE
v_partition_name VARCHAR(32);
BEGIN
IF NEW.datetime IS NOT NULL THEN
v_partition_name := 'dummyTest';
EXECUTE format('INSERT INTO %I VALUES ($1,$2)',v_partition_name)using NEW.id,NEW.datetime;
END IF;
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
ALTER FUNCTION dummytest_insert_trigger()
OWNER TO postgres;
I'm trying to insert using
insert into dummyTest values(1,'2013-01-01 00:00:00+05:30');
But it's showing error as
ERROR: function format(unknown) does not exist
SQL state: 42883
Hint: No function matches the given name and argument types. You might need to add explicit type casts.
Context: PL/pgSQL function "dummytest_insert_trigger" line 8 at EXECUTE statement
I'm unable get the error.
Your function could look like this in Postgres 9.0 or later:
CREATE OR REPLACE FUNCTION dummytest_insert_trigger()
RETURNS trigger AS
$func$
DECLARE
v_partition_name text := quote_ident('dummyTest'); -- assign at declaration
BEGIN
IF NEW.datetime IS NOT NULL THEN
EXECUTE
'INSERT INTO ' || v_partition_name || ' VALUES ($1,$2)'
USING NEW.id, NEW.datetime;
END IF;
RETURN NULL; -- You sure about this?
END
$func$ LANGUAGE plpgsql;
About RETURN NULL:
To ignore result in BEFORE TRIGGER of PostgreSQL?
I would advice not to use mixed case identifiers. With format( .. %I ..) or quote_ident(), you'd get a table named "dummyTest", which you'll have to double quote for the rest of its existence. Related:
Are PostgreSQL column names case-sensitive?
Use lower case instead:
quote_ident('dummytest')
There is really no point in using dynamic SQL with EXECUTE as long as you have a static table name. But that's probably just the simplified example?
You need explicit cast to text:
EXECUTE format('INSERT INTO %I VALUES ($1,$2)'::text ,v_partition_name) using NEW.id,NEW.datetime;

Create a schema with the name passed by variable

I want to create a schema with with a name passed by variable.
Example:
CREATE OR REPLACE FUNCTION test1("name" character varying)
RETURNS void AS
'CREATE SCHEMA "name";'
LANGUAGE 'sql' VOLATILE
COST 100;
You could use plpgsql and than EXECUTE:
CREATE OR REPLACE FUNCTION test1("name" character varying)
RETURNS void AS
$$
BEGIN
EXECUTE 'CREATE SCHEMA '|| quote_ident($1); -- security
RETURN;
END;
$$
LANGUAGE plpgsql
VOLATILE
COST 20;
user search_path to change the default schema so you may easily add tables to it!
and use format with %I to escape the schema name as identifier.
like this:
CREATE OR REPLACE FUNCTION test1("name" character varying)
RETURNS void AS
$$
BEGIN
EXECUTE FORMAT('CREATE SCHEMA %I;', $1);
EXECUTE FORMAT('SET search_path TO %I;', $1);
CREATE TABLE table1(
column1 integer
);
RETURN;
END;
$$
LANGUAGE plpgsql
VOLATILE
COST 20;
Using PROCEDURE (PostgreSQL 11+)
CREATE OR REPLACE PROCEDURE create_schema( _schema text)
LANGUAGE plpgsql as
$$
BEGIN
EXECUTE format( 'CREATE SCHEMA IF NOT EXISTS %I ',_schema);
END
$$;
Call this procedure via:
call create_schema('test');