Can PostgreSQL declare a procedure inside a procedure? - postgresql

create or replace procedure procedure_1()
language plpgsql
as $$
declare
precedure procedure_2()
begin
select 1;
end
begin
select 1;
end; $$
Is there any way to declare a procedure_2() inside procedure_1()?

Yes - if by "declare" you mean "create".
CREATE OR REPLACE PROCEDURE procedure_1(INOUT result int)
LANGUAGE plpgsql AS
$proc1$
BEGIN
CREATE OR REPLACE PROCEDURE procedure_2(INOUT result int)
LANGUAGE plpgsql AS
$proc2$
BEGIN
result := 2;
END
$proc2$;
result := 1;
END
$proc1$;
db<>fiddle here
You just have to get the quoting right. See:
What are '$$' used for in PL/pgSQL
Insert text with single quotes in PostgreSQL
Functions and procedures are not "declared", but "created" in Postgres. That creates an object in the database which is then visible and usable by all with appropriate permissions. (Not just a temporary object local to the procedure or transaction.)
You can, however, create a "temporary" function or procedure, with this "hack" - if that's what you had in mind:
How to create a temporary function in PostgreSQL?

Related

Not able to create backup of table dynamically, through PL/pgSQL function

I am trying to create a function to create table backup dynamically.
But I am getting error like :
ERROR: syntax error at or near "'
Here's one of my approach, which I am trying:
CREATE OR REPLACE FUNCTION public.test () RETURNS varchar AS
$BODY$ DECLARE backup_string varchar(50);
BEGIN
backup_string = (SELECT '_'||LPAD(DATE_PART('DAY',CURRENT_DATE)::VARCHAR,2,'0')||DATE_PART('MONTH',CURRENT_DATE)::VARCHAR||DATE_PART('YEAR',CURRENT_DATE)::VARCHAR||'_1');
EXECUTE 'SELECT * INTO table_name'|| backup_string ||' FROM table_name';
RETURN 'Y';
EXCEPTION WHEN others THEN RETURN 'N';
END
; $BODY$
LANGUAGE 'plpgsql'
GO
SELECT * FROM test()
I am not getting, why that execute statement giving me error like that.
I suggest so simplify your code and make use of the format() function to generate the dynamic SQL. That way you can avoid the clutter that concatenation generates and you can concentrate on the actual SQL code. In addition to that it also properly deals with identifiers that might need quoting.
When dealing with dynamic SQL it's always a good idea to store the generated SQL statement in a variable, so that it can be printed for debugging purposes if you get an error. Looking at the generated SQL usually tells you where the generation code went wrong.
CREATE OR REPLACE FUNCTION test()
RETURNS varchar
AS
$BODY$
DECLARE
l_source_table text;
l_backup_table text;
l_sql text;
BEGIN
l_source_table := 'table_name';
l_backup_table := l_source_table||'_'||to_char(current_date, 'ddmmyyyy')||'_1';
l_sql := format('create table %I as select * from %I', l_backup_table, l_source_table);
-- for debugging purposes:
raise notice 'Running: %', l_sql
EXECUTE l_sql;
RETURN 'Y';
EXCEPTION
WHEN others THEN RETURN 'N';
END;
$BODY$
LANGUAGE plpgsql;
Note that I also used variables for the source and backup table to be able to use that as a place holder for the format() function.
Online example

Custom function: Insert object into table

I have the following function in my postgres database:
create function my_schema.create_my_book(book my_schema.book) returns my_schema.book as $$
declare
v_book my_schema.book;
begin
insert into my_schema.book(title, language) values (book.title, book.language) returning * into v_book;
return v_book;
end;
$$ language plpgsql volatile;
This way I have to type out all the column names (title, language) and values (book.title, book.language). My book table is quite big so this will blow up my code by a lot, and once I add a column I will have to remember to add it to this function too.
Is there a way to directly insert the whole book my_schema.book object?
Yes, here it is. You do not even need plpgsql to do this, plain sql will do (and works faster).
create or replace function my_schema.create_my_book(arg_book my_schema.book)
returns my_schema.book as
$$
insert into my_schema.book select arg_book.* returning *;
$$ language sql volatile;
I changed the argument's name to arg_book in order to avoid possible ambiguity. And since the type of arg_book is my_schema.book this simple code adapts itself to table mutation and continues to work.
To solve the id issue
create or replace function my_schema.create_my_book(arg_book my_schema.book)
returns my_schema.book as
$$
declare
v_book my_schema.book%rowtype;
begin
arg_book.id := nextval('the-id-sequence-name');
insert into my_schema.book select arg_book.* returning * into v_book;
return v_book;
end;
$$ language plpgsql volatile;
which is pretty close to your initial function, just dynamic.

how can I create a stored procedure sql workbench that uses a redshift database?

is it possible to create a stored procedures on sql workbench that uses a redshift database ?
I tried to put in some procedure found on the internet like this one
CREATE OR REPLACE FUNCTION proc_sample RETURN INTEGER
IS
l_result INTEGER;
BEGIN
SELECT max(col1) INTO l_result FROM sometable;
RETURN l_result;
END;
but I get an error
the cursor is not located inside a statement
help please.
Here is my translation of your stored procedure for Redshift:
CREATE OR REPLACE PROCEDURE proc_sample (
l_result OUT INTEGER
)
LANGUAGE plpgsql
AS $$
BEGIN
SELECT max(col1) INTO l_result FROM sometable;
END
$$;
You call this stored procedure in Redshift as follows:
BEGIN; CALL proc_sample(); END;
-- l_result
-- ----------
-- 99
For more information see "Overview of stored procedures in Amazon Redshift"
you can not use from clause in function. you have to use procedure having parameter with out clause.

Create temp table in a STABLE stored procedure in postgresql

I would like to create a temp table in a stored procedure which has a STABLE volatility category setted to store the result of a select for later usage in the stored procedure. At the end of the stored procedure this temp table is deallocated and i am sure that this temp table does not have any affect on the database, because as far as i know with this volatility category i ensure the optimizer that this stored procedure will not affect the database.
So i would like to do something like this:
Create a stored procedure which returns with a query:
CREATE OR REPLACE FUNCTION storedproc()
RETURNS TABLE
(Egy TEXT,
Ketto TEXT)
AS $$
BEGIN
RETURN QUERY SELECT * FROM temptable;
END;
$$ LANGUAGE plpgsql;
Create a stored procedure which is using the previous query:
CREATE OR REPLACE FUNCTION stablefunction()
RETURNS TABLE
(Egy TEXT,
Ketto TEXT)
AS $$
BEGIN
-- I would like to store the results here for later usage
CREATE TEMP TABLE buba AS select * from storedproc();
-- Do other stuff
-- ...
-- Reuse the results here which was stored before
END;
$$ LANGUAGE plpgsql
STABLE;
But when i want to execute this stored procedure as this:
DO
$$
BEGIN
perform stablefunction() ;
END;
$$ LANGUAGE plpgsql;
i get the following error message:
ERROR: CREATE TABLE AS is not allowed in a non-volatile function
Maybe this is not the intended usage of the stored procedures, but then is there a way for store the result of a query inside of the stored procedure for later usage in the same stored procedure, maybe like a handle or somethings?
The documentation states clearly: A stable function cannot modify the database. A temporary table is a part of a database as well, so you cannot create it, insert into, delete from etc. Your concept seems a bit strange but I don't want to judge it. There is a trick that allows what you want to do. Perfom all actions on the temp table using other functions that do not have to be stable. Example:
create or replace function create_my_temp_table()
returns void language plpgsql volatile as $$
begin
create temp table temp_table(id int);
insert into temp_table values (123);
end $$;
create or replace function stable_function()
returns text language plpgsql stable as $$
begin
perform create_my_temp_table();
return 'ok';
end $$;
Test:
select stable_function();
stable_function
-------------
ok
(1 row)
select * from temp_table;
id
-----
123
(1 row)

"perform create index" in plpgsql doesn't run

I'm having problems executing a "perform create index" inside of a plgpsql function (postgres 9.4). For example:
create or replace function foo() returns void language plpgsql as $$
begin
perform 'create unique index patients_row_id_key on patients(row_id)';
end; $$;
It seems to run fine:
select foo();
However, the index is not created. Any diagnosis and workaround? I tried:
alter function foo() VOLATILE;
and still no luck.
What #Abelisto wrote about PERFORM.
And what #Chris added about SQL injection.
Plus, I suggest to use format() for anything except the most trivial query strings to make your life with dynamic SQL easier. And the manual does, too:
A cleaner approach is to use format()'s %I specification for table or column names.
CREATE OR REPLACE FUNCTION foo(_tbl text)
RETURNS void AS
$func$
BEGIN
EXECUTE format('CREATE UNIQUE INDEX %I ON %I(row_id)', _tbl || _row_id_key', _tbl);
END
$func$ LANGUAGE plpgsql;
A regclass parameter is a convenient alternative for passing table names, but concatenating new identifiers can be tricky - as this recent related case goes to show:
PL/pgSQL regclass quoting of table named like keyword
As a supplement to the point of using execute, note two important points about this.
You are doing string interpolation with sql queries (dangerous!), and
You have to use quote_ident, not quote_literal
If you use Abelisto's function above, and call it with:
SELECT foo('test_idx on test; drop table foo; --');
SQL injection in stored procedure. Worse if it is security definer. A fixed version would be:
create or replace function foo(p_tablename text) returns void language plpgsql as $$
begin
execute 'create unique index ' || quote_ident(p_tablename || '_row_id_key') || ' on ' || quote_ident(p_tablename) || '(row_id)';
end; $$;
PERFORM statement in the PLPGSQL used to execute queries which does not return result or which result is not useful. Technically PERFORM ... inside the PLPGSQL block is equal to SELECT ... in the plain SQL. So in your example you are trying to execute something like
select 'create unique index patients_row_id_key on patients(row_id)';
and just ignore the result.
Read more: Executing a Command With No Result
You should not to wrap DDL statements inside PLPGSQL and can use it as is:
create or replace function foo() returns void language plpgsql as $$
begin
create unique index patients_row_id_key on patients(row_id);
end; $$;
Or if you want to construct it at runtime then use EXECUTE statement: Executing Dynamic Commands like this:
create or replace function foo(p_tablename text) returns void language plpgsql as $$
begin
execute 'create unique index ' || p_tablename || '_row_id_key on ' || p_tablename || '(row_id)';
end; $$;