Postgres date value dynamic inserting - postgresql

I am scratching my head trying to fix the following:
create or replace procedure my_schema.Test()
as $$
declare
today date = now();
begin
drop table if exists my_schema.tst_table;
create table my_schema.tst_table (
todays_date varchar );
execute('
insert into my_schema.tst_table
values (' || today || ')
');
end;
$$
language plpgsql;
Basically I am trying to dynamically insert the current date into a table which will be used in later steps.
The issue I am facing is that due to the today variable looking like '2022-02-11', and because I am inserting the record dynamically, postgres is interpreting the "-" as a minus sign and inserting the value 2009 into my table.
Does anyone have a workaround for this?

Don't concatenate input values into dynamic SQL. And never store date values in a varchar column:
create or replace procedure my_schema.Test()
as $$
declare
today date := current_date;
begin
drop table if exists my_schema.tst_table;
create table my_schema.tst_table
(
todays_date date
);
execute('
insert into my_schema.tst_table
values ($1)
') using today;
end;
$$
language plpgsql;
But creating a table just to store the value of current_date seems a bit of an overkill.

You can use the cast operator to force the value conversion to VARCHAR datatype:
execute('
insert into my_schema.tst_table
values ('today'::VARCHAR)
');

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.

Loop over NEW Record

Writing an audit trigger. Inside the postgresql function I'm trying todo:
'INSERT INTO ' || able_name || ' (' || columns || ') VALUES ' || NEW || ';'
When NEW is turned into string, varchar variables will not have quotes around them. This will cause the insert to fail. Easier would be to turn all the column values of NEW into varchar values, and postgres would automatically cast them into right values - when INSERT is executed.
Can I loop over the NEW record without turning it into json?
Looking around, I couldn't find good resource explaining how to work with Postgres Record type.
If your target table's structure is identical to the new structure, you don't really need to iterate over the columns.
Something like this will work:
create function audit_trigger()
returns trigger
as
$$
declare
l_columns text;
l_table_name text;
begin
-- this builds the name of the target table dynamicall
l_table_name := tg_table_name||'_audit';
execute format('insert into %I select ($1).*', l_table_name) using new;
return new;
end;
$$
language plpgsql;
Even if you don't want to store the changed data as a JSONB column, you can still use JSON functions to iterate over the columns of the new record if think you need it nevertheless.
The following will store the list of column names of the new record in the variable l_columns:
select string_agg(quote_ident(col), ',')
into l_columns
from jsonb_each_text(to_jsonb(new)) as t(col, val);

Date part in WHERE clause of a function

I want to select persons from a table where the date is within a given month.
This is what I have so far, but it's not working:
CREATE OR REPLACE FUNCTION u7()
RETURNS character varying AS
$BODY$
DECLARE
data varchar=`data`;
mes varchar=`2016-11-21`;
incidencia varchar=`expulsions`;
valor varchar;
BEGIN
EXECUTE `SELECT `
||quote_ident(data)
||`FROM `
||quote_ident(incidencia)
||` WHERE data IN(select date_part(`month`, TIMESTAMP $1))`
INTO valor USING mes;
return valor;
END;
$BODY$
LANGUAGE plpgsql;
select * FROM u7();
Clean syntax for what you are trying to do could look like this:
CREATE OR REPLACE FUNCTION u7()
RETURNS TABLE (valor text) AS
$func$
DECLARE
data text := 'data'; -- the first 3 would typically be function parameters
incidencia text := 'expulsions';
mes timestamp = '2016-11-21';
mes0 timestamp := date_trunc('month', mes);
mes1 timestamp := (mes0 + interval '1 month');
BEGIN
RETURN QUERY EXECUTE format(
'SELECT %I
FROM %I
WHERE datetime_column_name >= $1
AND datetime_column_name < $2'
, data, incidencia)
USING mes0, mes1;
END
$func$ LANGUAGE plpgsql;
SELECT * FROM u7();
Obviously, data cannot be a text column and a timestamp or date column at the same time. I use datetime_column_name for the timestamp column - assuming it's data type timestamp.
Aside from various syntax errors, do not use the construct with date_part(). This way you would have to process every row of the table and could not use an index on datetime_column_name - which my proposed alternative can.
See related answers for explanation:
EXECUTE...INTO...USING statement in PL/pgSQL can't execute into a record?
Table name as a PostgreSQL function parameter
How do I match an entire day to a datetime field?

multi table auditing trigger function

I want to keep all changes of my tables. I have a working solution for making a trigger per table, but it seems silly to copy the code foreach table. Is there any way to create a single trigger function that does this?
Example of my working per-table trigger (including table definitions):
CREATE TABLE departments (
id bigserial Primary Key,
name varchar not null,
created bigint not null default date_part('epoch', NOW()),
created_by bigint references Employees (id) not null
);
create table Departments_hist ("action" varchar not null, change_date bigint not null, rev bigserial not null, like Departments);
CREATE OR REPLACE FUNCTION add_to_history_Departments() RETURNS TRIGGER AS $$
BEGIN
IF(TG_OP='INSERT' OR TG_OP='UPDATE') THEN
INSERT INTO Departments_hist values (TG_OP,date_part('epoch', NOW()),DEFAULT,NEW.*);
END IF;
IF (TG_OP='DELETE') THEN
INSERT INTO Departments_hist values (TG_OP,date_part('epoch', NOW()),DEFAULT,OLD.*);
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trigger_history_Departments AFTER INSERT OR UPDATE OR DELETE ON Departments FOR EACH ROW EXECUTE PROCEDURE add_to_history_Departments();
I've tried to make it multi-table by concatenating '_hist' to TG_TABLE_NAME:
CREATE OR REPLACE FUNCTION add_to_hist_table() RETURNS TRIGGER AS $$
DECLARE
histTable text :=TG_TABLE_NAME || '_hist';
BEGIN
IF (TG_OP='INSERT' OR TG_OP='UPDATE') THEN
INSERT INTO histTable values (TG_OP,date_part('epoch', NOW()),DEFAULT,NEW.*);
ELSIF TG_OP='DELETE' THEN
INSERT INTO histTable values (TG_OP,date_part('epoch', NOW()),DEFAULT,OLD.*);
END IF;
RETURN null; --ignored since it is an AFTER triggger.
END;
$$ LANGUAGE plpgsql;
But I get an error:
ERROR: syntax error at or near "$1"
LINE 1: INSERT INTO $1 values ( $2 ,date_part('epoch', NOW()),DEFA...
^
QUERY: INSERT INTO $1 values ( $2 ,date_part('epoch', NOW()),DEFAULT, $3 .*)
CONTEXT: SQL statement in PL/PgSQL function "add_to_hist_table" near line 5
I guess it is a problem with variable substitution ( http://www.postgresql.org/docs/8.4/static/plpgsql-implementation.html ).
How can this functionality be achieved?
PS. I'm using postgresql 8.4 but will likely upgrade to 9.3 soon.
I found a solution on this "related question" https://stackoverflow.com/a/1997417/844731
I didn't think of doing 'EXECUTE USING' with NEW and OLD. So now a working solution is:
CREATE OR REPLACE FUNCTION add_to_hist_table() RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP='INSERT' OR TG_OP='UPDATE') THEN
execute 'INSERT INTO '|| TG_TABLE_NAME ||'_hist values (''' || TG_OP || ''',date_part(''epoch'', NOW()),DEFAULT,$1.*)' using NEW;
ELSIF TG_OP='DELETE' THEN
execute 'INSERT INTO '|| TG_TABLE_NAME ||'_hist values (''' || TG_OP || ''',date_part(''epoch'', NOW()),DEFAULT,$1.*)' using OLD;
END IF;
RETURN null; --ignored since it is an AFTER triggger.
END;
$$ LANGUAGE plpgsql;
#Pascal_dher, somebody can create table with name with containing attacker code. Due Postgresql this probably dosnt do some really bad, only failed queries. But if your trigger will be more complex, then impacts can be much more worse.

Postgresql: execute update on a temporary table in plpgsql is not working

I'm trying to update a field in a temporary table I've created.
The code for the temporary table looks like this:
CREATE OR REPLACE FUNCTION insertTable ()
RETURNS VOID AS $$
BEGIN
execute 'create temporary table myTable (id INTEGER, value TEXT) on commit preserve rows';
execute 'insert into myTable values(1, NULL)';
end if;
end;
$$ LANGUAGE plpgsql;
Next I try to update the value filed with the following function:
CREATE OR REPLACE FUNCTION setValue (msg TEXT)
RETURNS VOID AS $$
BEGIN
EXECUTE format('UPDATE myTable SET value = value || $1 WHERE id = '|| quote_literal(1))USING msg;
END;
$$ LANGUAGE plpgsql;
However it does not work and the value field stays empty.
I tried the same code with an already existing table (not temporary) and the code worked as expected.
I searched the documentation for a difference between updating a temporary and a normal table but couldn't find any.
I hope you can help me with this.
Thanks in advance for your help.
Edit: edited the name of the table
The issue is not related to temporary table. The problem is that the column you want to update is actually empty. You try to update this column by concatenating the value of the column with another text, but, because the value itself is null, the concatenated value is also null.
This query:
SELECT null||'some text';
returns null. Also this update statement:
UPDATE xmlBuffer SET value = value || 'some text';
will not update the rows where the actual content is null. You could fix this issue in several ways, depending on you needs. In example you could use the COALESCE statement in the second function, using a empty string as fallback value (besides, the quote_literal and formatstatements are not necessary):
CREATE OR REPLACE FUNCTION setValue (msg TEXT)
RETURNS VOID AS $$
BEGIN
EXECUTE 'UPDATE xmlBuffer SET value = COALESCE(value,'''') || $1 WHERE id = '|| 1
USING msg;
END;
$$ LANGUAGE plpgsql;