I am trying to create and populate a temp table inside a procedure to save some intermediate state of the data I am working with.
I have created an example code to explain what I am trying to do:
CREATE OR REPLACE PROCEDURE etl.my_test_procedure()
LANGUAGE sql
AS
$$
CREATE TEMP TABLE IF NOT EXISTS my_temp(
var1 VARCHAR(255),
var2 VARCHAR(255)
) ON COMMIT DROP;
INSERT INTO my_temp (
var1,
var2
)
SELECT
table_schema,
column_name
FROM information_schema.columns;
SELECT
*
FROM my_temp
$$
When trying to create this Stored Procedure the database returns this error message:
ERROR: relation "my_temp" does not exist
LINE 10: INSERT INTO my_temp (
^
SQL state: 42P01
Character: 171
PD: My version of Postgres is 13.3
You would have to use plpgsql instead of sql
CREATE OR REPLACE FUNCTION my_test_procedure()
RETURNS TABLE(var1 VARCHAR(255), var2 VARCHAR(255))
AS
$$
DECLARE
BEGIN
CREATE TEMP TABLE IF NOT EXISTS my_temp(
var1 VARCHAR(255),
var2 VARCHAR(255)
) ON COMMIT DROP;
INSERT INTO my_temp (
var1,
var2
)
SELECT
table_schema,
column_name
FROM information_schema.columns;
RETURN QUERY SELECT *
FROM my_temp;
END;
$$ LANGUAGE plpgsql;
The reason for the error is that SQL functions are parsed when they are created. You can avoid that by setting the parameter check_function_bodies to off.
But that doesn't help you much: it allows you to create the function, but you will end up with the same error when you execute the procedure, since all statements are parsed when the function starts, and my_temp does not exist at that time.
The solution is to use PL/pgSQL, like JGH's answer suggests.
Related
I am trying to create and populate a temp table inside a procedure to save some intermediate state of the data I am working with.
I have created an example code to explain what I am trying to do:
CREATE OR REPLACE PROCEDURE etl.my_test_procedure()
LANGUAGE sql
AS
$$
CREATE TEMP TABLE IF NOT EXISTS my_temp(
var1 VARCHAR(255),
var2 VARCHAR(255)
) ON COMMIT DROP;
INSERT INTO my_temp (
var1,
var2
)
SELECT
table_schema,
column_name
FROM information_schema.columns;
SELECT
*
FROM my_temp
$$
When trying to create this Stored Procedure the database returns this error message:
ERROR: relation "my_temp" does not exist
LINE 10: INSERT INTO my_temp (
^
SQL state: 42P01
Character: 171
PD: My version of Postgres is 13.3
You would have to use plpgsql instead of sql
CREATE OR REPLACE FUNCTION my_test_procedure()
RETURNS TABLE(var1 VARCHAR(255), var2 VARCHAR(255))
AS
$$
DECLARE
BEGIN
CREATE TEMP TABLE IF NOT EXISTS my_temp(
var1 VARCHAR(255),
var2 VARCHAR(255)
) ON COMMIT DROP;
INSERT INTO my_temp (
var1,
var2
)
SELECT
table_schema,
column_name
FROM information_schema.columns;
RETURN QUERY SELECT *
FROM my_temp;
END;
$$ LANGUAGE plpgsql;
The reason for the error is that SQL functions are parsed when they are created. You can avoid that by setting the parameter check_function_bodies to off.
But that doesn't help you much: it allows you to create the function, but you will end up with the same error when you execute the procedure, since all statements are parsed when the function starts, and my_temp does not exist at that time.
The solution is to use PL/pgSQL, like JGH's answer suggests.
I am trying to write a stored procedure where the table name comes dynamically.
Also it has to check whether the table already exists and create only if it does not exist.
Then later I am trying to insert data into the table like below.
Here I am passing pkey and filedata as parameters to insert query in which pkey is a string and filedata is a json data which looks like { "customer": "John Doe", "items": {"product": "Beer","qty": 6}}
I have tried the below query but the table is not getting created it is giving the message
Notice: identifier public.tablename_11111 will be truncated to public.tablename_11111
here the table name is public.tablename_11111
CREATE OR REPLACE FUNCTION public.generate_table(tb_name text)
RETURNS text LANGUAGE 'plpgsql'
COST 100 VOLATILE AS $BODY$
BEGIN
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I(
id serial PRIMARY KEY,
pkey VARCHAR (250) NULL,
fpo_data TEXT NULL
)', tb_name || '_pk');
EXECUTE 'INSERT INTO' || tb_name || '_pk (pkey, fpo_data) VALUES
('|| pkey ||', '|| filedata ||')';
END;
$BODY$;
First: %I, when used with a name like public.tablename_11111, won't do what you want.
You will end up with a table called "public.tablename_11111", not with a table tablename_11111 in schema public. For that, you should separate schema and table name and use the format %I.%I:
EXECUTE
format(
'CREATE TABLE %I.%I (...)',
schema_name, tb_name || '_pk'
);
Second, your INSERT statement is vulnerable to SQL injection. You must use the format function there as well, just like in CREATE TABLE.
Ideally you should pass the schema name and table name as two separate values. And it's better to not concatenate values into a SQL string, but use placeholders. Mainly so that you don't need to worry about formatting them correctly.
Something like the following:
CREATE OR REPLACE FUNCTION public.generate_table(tb_schema text, tb_name text, ???)
RETURNS text
LANGUAGE plpgsql --<< the language name is an identifier, don't quote it
COST 100 VOLATILE
AS $BODY$
BEGIN
tb_name := tb_name ||'_pk';
EXECUTE format('
CREATE TABLE IF NOT EXISTS %I.%I (
id serial PRIMARY KEY,
pkey VARCHAR (250) NULL,
fpo_data TEXT NULL
)', tb_schema, tb_name);
-- where do pkey and filedata come from?
EXECUTE format('INSERT INTO %I.%I (pkey, fpo_data) VALUES (:1, :2)',
tb_schema, tb_name)
using pkey, filedata;
END;
$BODY$;
I am trying to create the query as a string and execute that in PostgreSQL 10.
As far as I know, I can use the EXECUTE command to execute my query from a defined string.
Unfortunately, I have got an error: SQL Error [42601]: ERROR: syntax error at or near "execute"
Below is my code:
drop table if exists delinquent;
create table delinquent
(
report_date date
,account_id text
)
;
INSERT INTO delinquent VALUES('2019-07-23', 'a1234');
INSERT INTO delinquent VALUES('2019-07-23', 'b5679');
--------------
drop table if exists output1;
create temp table output1
(
report_date date
,account_id text
)
;
--------------
do $$
declare table_name text := 'delinquent';
begin
truncate table output1;
insert into output1
execute concat('select * from ',table_name);
end; $$;
select * from output1;
Anybody has an idea on what is wrong and what to do about it?
Many thanks,
You need to run the complete INSERT statement as dynamic SQL. And to build dynamic SQL, using format() is highly recommended to properly deal with identifiers and literals:
do $$
declare
table_name text := 'delinquent';
some_value text := 'a1234';
begin
truncate table output1;
execute format('insert into output1 select * from %I where some_column = %L',
table_name, some_value);
end; $$;
select *
from output1;
I have a few db tables.
I want write universtal postgres function on copy rows to history tables
I have tables:
table1
table1_h
table2
table2_h
I wrote function (with help stackoverflow)
CREATE OR REPLACE FUNCTION copy_history_f() RETURNS TRIGGER AS
$BODY$
DECLARE
tablename_h text:= TG_TABLE_NAME || '_h';
BEGIN
EXECUTE 'INSERT INTO ' || quote_ident(TG_TABLE_SCHEMA) || '.' || quote_ident(tablename_h) || ' VALUES (' || OLD.* ||')';
RETURN NULL;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
And functions was create, but after update is error.
ERROR: syntax error at or near ","
ROW 1: ...RT INTO table1_h VALUES ((12,,,0,,"Anto...
I know where is error in this insert but I don't know how I repair that.
Structure tables table1 and table1_h are identical but table1_h has one more column (id_h)
Can you help me, how I have create psql function?
Thnak you.
drop table if exists t;
drop table if exists t_h;
drop function if exists ftg();
create table t(i serial, x numeric);
insert into t(x) values(1.1),(2.2);
create table t_h(i int, x numeric);
create function ftg() returns trigger language plpgsql as $ftg$
declare
tablename_h text:= TG_TABLE_NAME || '_h';
begin
execute format($q$ insert into %I.%I select $1.*; $q$, TG_TABLE_SCHEMA, tablename_h) using old;
return null;
end $ftg$;
create trigger tg_t after delete on t for each row execute procedure ftg();
delete from t where i = 1;
select * from t_h;
dbfiddle
Update It solves your problem, but I think that you want to have a bit more info in your history tables. It will be more complex a bit:
drop table if exists t;
drop table if exists t_h;
drop function if exists ftg();
create table t(i serial, x numeric);
insert into t(x) values(1.1),(2.2);
create table t_h(
hi serial, -- just ID
hd timestamp, -- timestamp
hu text, -- user who made changes
ha text, -- action
i int, x numeric
);
create function ftg() returns trigger language plpgsql as $ftg$
declare
tablename_h text:= TG_TABLE_NAME || '_h';
begin
execute format(
$q$
insert into %I.%I
select
nextval(%L || '_hi_seq'),
clock_timestamp(),
current_user,
%L,
$1.*
$q$, TG_TABLE_SCHEMA, tablename_h, tablename_h, TG_OP) using old;
return null;
end $ftg$;
create trigger tg_t after delete or update on t for each row execute procedure ftg();
update t set x = x * 2;
update t set x = x * 2 where i = 2;
delete from t where i = 1;
select * from t_h;
dbfiddle
I assume you are inserting the 'old' values from table1 into table1_h.
The additional column is your problem. When you using an insert without naming columns you must use a matching number and type for the insert.
You must use column referencing.
eg.
Insert into table1_h(column1, column2, column3)
values (a,b,c)
Consider a default value for the additional column in table table1_h.
I am using RazorSQL tool to work with DB2. I try to create procedure which contains if table not exist statement.
the problem I am having is that if table doesn't exist it procedure has to execute create table statements.
trying co create a procedure returns error (syntax error), like it can not execute more then only create table statement.
example:
CREATE PROCEDURE KLEMENTEST.create_table
()
LANGUAGE SQL
MODIFIES SQL DATA
--READS SQL DATA
--CONTAINS SQL
begin atomic
if (not exists(select 'A' from syscat.tables where tabschema = 'KLEMENTEST' and tabname='bendeldoba')) then
create table klementest.bendeldoba (
bdd_id_bdd INTEGER not null,
bdd_naziv VARCHAR(128) not null,
bdd_mesecev INTEGER not null default 0,
bdd_prispevki INTEGER,
bdd_procent numeric,
bdd_racuni INTEGER,
bdd_datvpisa DATE not null,
bdd_vpisal_uporabnik INTEGER not null default 0
);
alter table klementest.bendeldoba add constraint P_Key_1 primary key (bdd_id_bdd);
end if;
end
alter table is causing the problems. If I comment it it works, also trying co execute smth like
CREATE PROCEDURE KLEMENTEST.create_table
()
LANGUAGE SQL
MODIFIES SQL DATA
--READS SQL DATA
--CONTAINS SQL
begin atomic
if (not exists(select 'A' from syscat.tables where tabschema = 'KLEMENTEST' and tabname='bendeldoba')) then
crete view def_schema.view1 as select * from sometable;
crete view def_schema.view2 as select * from someothertable;
end if;
end
it works
where is the "syntax error" problem with my first create procedure query??
thank you
In DB2, SQL stored procedures are bound statically in the database. This means that any static SQL statements (i.e. ones that you don't execute using PREPARE/EXECUTE or EXECUTE IMMEDIATE) will be checked and compiled when you create the stored procedure.
Therefore, the error occurs because when DB2 checks the ALTER TABLE statement to validity, the KLEMENTEST.BENDELDOBA does not yet exist.
The best way to resolve this is to make the ALTER TABLE statement a dynamic statement:
declare vSQL varchar(1024);
-- portion of procedure that creates the table...
set vSQL = 'alter table ...';
execute immediate vSQL;
I had to add some additional "setters", to declare where atomic procedure starts and ends
The code looks like this now
CREATE PROCEDURE KLEMENTEST.create_table
()
LANGUAGE SQL
ap: begin atomic
declare vsql varchar(1024) ;
set vSQL = 'alter table KLEMENTEST.avtvrsteplacilapod add constraint P_Key_1 primary key (avp_id_avp)';
if (not exists(select 'A' from syscat.tables where tabschema = 'KLEMENTEST' and tabname='AVTVRSTEPLACILAPOD')) then
create table KLEMENTEST.avtvrsteplacilapod (
avp_id_avp INTEGER not null,
avp_vrsteplacila INTEGER not null,
avp_naziv VARCHAR(64) not null,
avp_skupinevrpl INTEGER not null ,
avp_avtvrplmestovprog INTEGER ,
avp_postrm SMALLINT not null,
avp_upostzap SMALLINT not null
);
execute immediate vsql;
end if ;
end ap