db2 create procedure using razor - db2

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

Related

Postgresql 13 Stored Procedure issue [duplicate]

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.

Temp table inside a Procedure in Postgres

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.

Postgresql: Partitioning a table for values in SELECT

I want to use PostgreSQL's declarative table partitioning on a column with UUID values. I want it partitioned by values that are returned by a SELECT.
CREATE TABLE foo (
id uuid NOT NULL,
type_id uuid NOT NULL,
-- other columns
PRIMARY KEY (id, type_id)
) PARTITION BY LIST (type_id);
CREATE TABLE foo_1 PARTITION OF foo
FOR VALUES IN (SELECT id FROM type_ids WHERE type_name = 'type1');
CREATE TABLE foo_2 PARTITION OF foo
FOR VALUES IN (SELECT id FROM type_ids WHERE type_name = 'type2');
I don't want to use specific UUIDs in FOR VALUES IN ('uuid'), since the UUIDs may differ by environment (dev, qa, prod). However, the SELECT syntax doesn't seem to be accepted by Postgres. Any suggestions?
I just wanted the SELECT to be evaluated at the table creation time
You should have made that clear in the question, not in a comment.
In that case - if this is a one time thing, you can use dynamic SQL, e.g. wrapped into a procedure
create procedure create_partition(p_part_name text, p_type_name text)
as
$$
declare
l_sql text;
l_id uuid;
begin
select id
into l_id
from type_ids
where type_name = p_type_name;
l_sql := format('CREATE TABLE %I PARTITION OF foo FOR VALUES IN (%L)', p_part_name, l_id);
execute l_sql;
end;
$$
language plpgsql;
Then you can do:
call create_partition('foo_1', 'type1');
call create_partition('foo_2', 'type2');

How to create a table and insert data with dynamic values in postgres

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$;

Insert record dynamically inside of Procedural Trigger

We are looking to convert our database over to Postgres (9.3.5), which I have no experience with, and I am trying to get our audit tables up and running. I understand that each table will need its own trigger, but all triggers can call a single function.
The trigger on the table is passing a list of the columns that need to be audited since some of our columns are not tracked.
Here are some of the posts I followed:
- https://stackoverflow.com/a/7915100/229897
- http://www.postgresql.org/docs/9.3/static/plpgsql-statements.html
- http://www.postgresql.org/docs/9.4/static/plpgsql-trigger.html
When I run this I get the error: ERROR: syntax error at or near "$1"
DROP TABLE IF EXISTS people;
DROP TABLE IF EXISTS a_people;
CREATE TABLE IF NOT EXISTS people (
record_id SERIAL PRIMARY KEY NOT NULL,
first_name VARCHAR NOT NULL,
last_name VARCHAR NOT NULL,
last_updated_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS a_people (
record_id SERIAL PRIMARY KEY NOT NULL,
a_record_id INT,
first_name VARCHAR NULL,
last_name VARCHAR NULL,
last_updated_on TIMESTAMP
);
/******************************************************/
--the function
CREATE OR REPLACE FUNCTION audit_func()
RETURNS TRIGGER AS
$BODY$
DECLARE
audit TEXT := TG_TABLE_SCHEMA || '.a_' || TG_TABLE_NAME;
cols TEXT := TG_ARGV[0];
BEGIN
EXECUTE format('INSERT INTO %1$s(a_%2$s) SELECT %2$s FROM ($1)', audit, cols) USING OLD;
NEW.last_updated_on = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
/******************************************************/
--the trigger calling the function to update inbound records
CREATE TRIGGER build_user_full_name_trg
BEFORE UPDATE
ON people
FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE audit_func('record_id,first_name,last_name');
/******************************************************/
INSERT INTO people (first_name, last_name) VALUES ('George','Lincoln');
UPDATE people SET last_name = 'Washington' WHERE first_name = 'George';
SELECT * FROM people;
I welcome your assistance (and patience)!
This subselect should work:
EXECUTE format('INSERT INTO %1$s(a_%2$s) SELECT %2$s FROM (select ($1).*) XX', audit, cols) USING OLD;