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

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

Related

Postgres - function get the values from one query then insert as dynamic sql string

I am building a function on postgresql, basically send one id from one table then re-build the insert statement of that row and insert it as string column from another table.
I have this table, in insert_query I want to store the insert statement of one row, with his variables:
create table get_insert (tabname varchar(30), insert_query varchar(5000));
I want to store something like this on insert_query column:
Insert into baseball_table (code, name) select '01','Robet';
Currently my function is storing just this, which doesn't work since I need to store the real values:
INSERT INTO baseball_table(code,name) SELECT code,name FROM baseball_table WHERE id=1;
This is my function:
CREATE OR REPLACE FUNCTION get_values(
_id character varying
)
RETURNS boolean
LANGUAGE 'plpgsql'
VOLATILE PARALLEL UNSAFE
AS $function$
DECLARE v_id integer;
DECLARE sql_brand varchar;
BEGIN
sql_query'INSERT INTO baseball_table(code,name) SELECT code,name FROM core.brand WHERE id=' || v_id ||'
';
INSERT INTO core.clone_brand (tabname, insert_query)VALUES ('brand',sql_query);
RETURN true;
END;
$function$;
Which is the best way to get the real values without making variables of each column?
Regards
I want to get the way to get the real values without making variables of each column.

SELECT in INSERTING in postgreSQL

I have two table Documents and RegCard (name uses a name of current_user)
Table Documents
CREATE TABLE public.document_dimauser
(
documentid uuid NOT NULL,
documentname character varying(100),
author character varying(100),
contents bytea,
CONSTRAINT document_dimauser_pkey PRIMARY KEY (documentid)
)
Table RegCard
CREATE TABLE public.regcard_dimauser
(
regcardid uuid NOT NULL,
documentid uuid,
documentintronumber character varying(100),
documentexternnumber character varying(100),
dateintro date,
dateextern date,
CONSTRAINT regcard_dimauser_pkey PRIMARY KEY (regcardid),
CONSTRAINT regcard_dimauser_documentid_fkey FOREIGN KEY (documentid)
REFERENCES public.document_dimauser (documentid) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
This two tables are connected one row 'documentid'
Also, i have a function, which inserting data in this two tables. Here i try selecting last rec of documentid from first table and transer him in second table
CREATE OR REPLACE FUNCTION public.addrecuserdocuments(
documentname character varying,
contents bytea,
documentintronumber character varying)
RETURNS void AS
$BODY$
DECLARE
comm VARCHAR;
nameuser VARCHAR;
currdate DATE;
iddoc uuid;
BEGIN
SELECT CURRENT_USER INTO STRICT nameuser;
SELECT CURRENT_DATE INTO STRICT currdate;
comm:='INSERT INTO Document_'||nameuser||' VALUES ('||quote_literal(uuid_generate_v4())||', '||quote_literal(documentname)||','||quote_literal(nameuser)||','||quote_literal(contents)||');
SELECT documentid INTO STRICT '||quote_literal(iddoc)||' FROM Document_'||nameuser||' order by documentid DESC LIMIT 1;
INSERT INTO Regcard_'||nameuser||' (regcardid, documentid, documentintronumber, dateintro) VALUES ('||quote_literal(uuid_generate_v4())||' , '||quote_literal(iddoc)||', '||quote_literal(documentintronumber)||', '||quote_literal(currdate)||');';
EXECUTE comm;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
But i have a error
ERROR: query string argument of EXECUTE is null
SQL-состояние: 22004
Контекст: PL/pgSQL function addrecuserdocuments(character varying,bytea,character varying) line 13 at EXECUTE
Please, tell me, what i do wrong?
you concat with || operator - if any value is null the whole string is null...
you can instead do smth like :
comm := format('INSERT INTO %I VALUES(%L)',concat(Document_',nameuser),uuid_generate_v4());
and so on...
Don't concatenate input values if you don't need that, use parameters in the string and use format() to build it. You also don't need variables to store the results of built-in functions and you don't need to "select" the UUID you generated inside your code.
Putting all that together you can simplify your code to:
BEGIN
iddoc := uuid_generate_v4();
comm : = format('INSERT INTO Document_%s VALUES ($1, $2, $3, $4)', current_user);
execute comm
using iddoc, documentname, current_user, contents;
comm := format('insert into regcard_%s (regcardid, documentid, documentintronumber, dateintro) values ($1, $2, $3, $4)', current_user);
execute comm
using uuid_generate_v4(), iddoc, documentintronumber, current_date;
END;
This not only makes the code much more readable it is also a way to prevent SQL injection through that function.
Unrelated, but: I find a design that uses a table with the user name appended highly questionable - especially if that user name is also stored inside the table. If you don't do that you can avoid all that dynamic SQL.

Function to return dynamic set of columns for given table

I have a fields table to store column information for other tables:
CREATE TABLE public.fields (
schema_name varchar(100),
table_name varchar(100),
column_text varchar(100),
column_name varchar(100),
column_type varchar(100) default 'varchar(100)',
column_visible boolean
);
And I'd like to create a function to fetch data for a specific table.
Just tried sth like this:
create or replace function public.get_table(schema_name text,
table_name text,
active boolean default true)
returns setof record as $$
declare
entity_name text default schema_name || '.' || table_name;
r record;
begin
for r in EXECUTE 'select * from ' || entity_name loop
return next r;
end loop;
return;
end
$$
language plpgsql;
With this function I have to specify columns when I call it!
select * from public.get_table('public', 'users') as dept(id int, uname text);
I want to pass schema_name and table_name as parameters to function and get record list, according to column_visible field in public.fields table.
Solution for the simple case
As explained in the referenced answers below, you can use registered (row) types, and thus implicitly declare the return type of a polymorphic function:
CREATE OR REPLACE FUNCTION public.get_table(_tbl_type anyelement)
RETURNS SETOF anyelement AS
$func$
BEGIN
RETURN QUERY EXECUTE format('TABLE %s', pg_typeof(_tbl_type));
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM public.get_table(NULL::public.users); -- note the syntax!
Returns the complete table (with all user columns).
Wait! How?
Detailed explanation in this related answer, chapter
"Various complete table types":
Refactor a PL/pgSQL function to return the output of various SELECT queries
TABLE foo is just short for SELECT * FROM foo:
Is there a shortcut for SELECT * FROM?
2 steps for completely dynamic return type
But what you are trying to do is strictly impossible in a single SQL command.
I want to pass schema_name and table_name as parameters to function and get record list, according to column_visible field in
public.fields table.
There is no direct way to return an arbitrary selection of columns (return type not known at call time) from a function - or any SQL command. SQL demands to know number, names and types of resulting columns at call time. More in the 2nd chapter of this related answer:
How do I generate a pivoted CROSS JOIN where the resulting table definition is unknown?
There are various workarounds. You could wrap the result in one of the standard document types (json, jsonb, hstore, xml).
Or you generate the query with one function call and execute the result with the next:
CREATE OR REPLACE FUNCTION public.generate_get_table(_schema_name text, _table_name text)
RETURNS text AS
$func$
SELECT format('SELECT %s FROM %I.%I'
, string_agg(quote_ident(column_name), ', ')
, schema_name
, table_name)
FROM fields
WHERE column_visible
AND schema_name = _schema_name
AND table_name = _table_name
GROUP BY schema_name, table_name
ORDER BY schema_name, table_name;
$func$ LANGUAGE sql;
Call:
SELECT public.generate_get_table('public', 'users');
This create a query of the form:
SELECT usr_id, usr FROM public.users;
Execute it in the 2nd step. (You might want to add column numbers and order columns.)
Or append \gexec in psql to execute the return value immediately. See:
How to force evaluation of subquery before joining / pushing down to foreign server
Be sure to defend against SQL injection:
INSERT with dynamic table name in trigger function
Define table and column names as arguments in a plpgsql function?
Asides
varchar(100) does not make much sense for identifiers, which are limited to 63 characters in standard Postgres:
Maximum characters in labels (table names, columns etc)
If you understand how the object identifier type regclass works, you might replace schema and table name with a singe regclass column.
I think you just need another query to get the list of columns you want.
Maybe something like (this is untested):
create or replace function public.get_table(_schema_name text, _table_name text, active boolean default true) returns setof record as $$
declare
entity_name text default schema_name || '.' || table_name;
r record;
columns varchar;
begin
-- Get the list of columns
SELECT string_agg(column_name, ', ')
INTO columns
FROM public.fields
WHERE fields.schema_name = _schema_name
AND fields.table_name = _table_name
AND fields.column_visible = TRUE;
-- Return rows from the specified table
RETURN QUERY EXECUTE 'select ' || columns || ' from ' || entity_name;
RETURN;
end
$$
language plpgsql;
Keep in mind that column/table references may need to be surrounded by double quotes if they have certain characters in them.

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;

db2 create procedure using razor

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