Postgresql function and trigger to copy content from one column to another? - sql

I need to implement a function and a trigger to copy the contents of COLUMN A to COLUMN B( last one completely NULL) both in the same table. This function must be executed by the trigger whenever a new record is entered. I have tried to do the following in the function but it does not seem to work
UPDATE table
SET column1 = column2;
RETURN column1;
On the other hand in the trigger I have:
CREATE TRIGGER name
AFTER UPDATE OR INSERT ON table
FOR EACH ROW EXECUTE PROCEDURE function();
Does anyone see the error?

maybe you need generated column? Documentation
create table test_table (column1 text, column2 text
GENERATED ALWAYS AS (column1) stored);
and results
insert into test_table(column1) values('Megatest'),('Ubertest');
column1 | column2
----------+----------
Megatest | Megatest
Ubertest | Ubertest
update
update test_table set column1='11111111111111' where column1='Megatest';
select * from test_table;
column1 | column2
----------------+----------------
Ubertest | Ubertest
11111111111111 | 11111111111111

You need a before insert trigger for that.
create or replace function the_trigger_f() returns trigger language plpgsql as
$$
begin
new.column_b := new.column_a;
return new;
end;
$$;
CREATE TRIGGER trigger_name
BEFORE INSERT ON the_table
FOR EACH ROW EXECUTE PROCEDURE the_trigger_f();

Related

Oracle SQL combine columns from one table

i am using oracle sql developer and want to build a trigger to insert values into a table column.
My table is:
column1(num) | column2(num) | column3(var)
1 5
6 4
7 3
I want to combine the first two columns, so in the end column3 should look like this:
column3(var)
1_5
6_4
7_3
My Idea was:
create or replace TRIGGER "Database"."TRIGGER"
BEFORE INSERT OR UPDATE ON "Database"."TABLE"
FOR EACH ROW
BEGIN
SELECT column1 || column2
INTO :NEW.column3
FROM TRIGGER;
END;
But column3 is still empty, can anybody tell me what i am doing wrong?
thanks in advance
Rather than using a trigger, you can preferably add a virtual column after dropping the existing one such as
SQL> ALTER TABLE t DROP COLUMN col3;
SQL> ALTER TABLE t
ADD (
col3 AS (col1||'_'||col2)
);
which always will depend on those two columns, and any DML is not allowed, already not needed, it's good for displaying purposes with no interfering human factor.
Demo
BEGIN
:NEW.column3 :=column1 || column2;
END;
Calling a trigger name "Trigger" is a bad idea.

Updating another table on conflict postgres

I have a requirement to insert on one table (test01) and update on another table(result) whenever the conflict arises. I tried with below query:
insert into test01 as cst (col1,col2)
select col1,col2 from (
select 1 col1,'test' col2) as excluded
on conflict (col1) do
update result as rst set conflictid = excluded.col1, updated_at = now() where rst.conflictid= excluded.col1 ;
but its returns "syntax error at or near result". Can anyone please help me with the right solution.
Basically, your approach is not feasible. The ON CONFLICT ... DO UPDATE clause applies only to the table into which the rows are inserted. See INSERT syntax in the documentation.
A solution requires a bit more work. You should create a trigger for table test01 to get the effect you want.
Example tables (slightly changed names of columns and table):
create table test01_conflicts(
conflict_id int primary key,
updated_at timestamp);
create table test01(
id int primary key,
str text);
When the table test01 is updated with the same id the trigger function inserts or updates a row in the conflict table. Note that in this case the function returns null so the update on test01 will not proceed.
create or replace function before_update_on_test01()
returns trigger language plpgsql as $$
begin
if new.id = old.id then
insert into test01_conflicts(conflict_id, updated_at)
values (new.id, now())
on conflict(conflict_id)
do update set updated_at = now();
return null;
end if;
return new;
end $$;
create trigger before_update_on_test01
before update on test01
for each row execute procedure before_update_on_test01();
The query. On conflict update test01 with the same id only:
insert into test01
select col1, col2
from (
select 1 as col1, 'test' as col2
) as source
on conflict (id) do
update set
id = excluded.id;

Values of the inserted row in a Trigger Oracle

I want a trigger that updates the value of a column, but I just want to update a small set of rows that depends of the values of the inserted row.
My trigger is:
CREATE OR REPLACE TRIGGER example
AFTER INSERT ON table1
FOR EACH ROW
BEGIN
UPDATE table1 t
SET column2 = 3
WHERE t.column1 = :new.column1;
END;
/
But as I using FOR EACH ROW I have a problem when I try it, I get the mutating table runtime error.
Other option is not to set the FOR EACH ROW, but if I do this, I dont know the inserted "column1" for comparing (or I dont know how to known it).
What can I do for UPDATING a set of rows that depends of the last inserted row?
I am using Oracle 9.
You should avoid the DML statements on the same table as defined in a trigger. Use before DML to change values of the current table.
create or replace trigger example
before insert on table1
for each row
begin
:new.column2 := 3;
end;
/
You can modify the same table with pragma autonomous_transaction:
create or replace trigger example
after insert on table1 for each row
declare
procedure setValues(key number) is
pragma autonomous_transaction;
begin
update table1 t
set column2 = 3
where t.column1 = key
;
end setValues;
begin
setValues(:new.column1);
end;
/
But I suggest you follow #GordonLinoff answere to your question - it's a bad idea to modify the same table in the trigger body.
See also here
If you need to update multiple rows in table1 when you are updating one row, then you would seem to have a problem with the data model.
This need suggests that you need a separate table with one row per column1. You can then fetch the value in that table using join. The trigger will then be updating another table, so there will be no mutation problem.
`create table A
(
a INTEGER,
b CHAR(10)
);
create table B
(
b CHAR (10),
d INTEGER
);
create trigger trig1
AFTER INSERT ON A
REFERENCING NEW AS newROW
FOR EACH ROW
when(newROW.a<=10)
BEGIN
INSERT into B values(:newROW.b,:newROW.a);
END trig1;
insert into A values(11,'Gananjay');
insert into A values(5,'Hritik');
select * from A;
select * from B;`

PosgresSQL Trigger

When a new row is inserted into table forumtopics (cols: id | userid), than I want to do a trigger that makes an insert into table upvotes, that uses the id and userid from the forumtopics row.
So the upvotes table would look: id | userid | forumtopicsid (id from original insert)
How may I do this?
First, create a trigger function, e.g. (assuming that upvotes.id is of type serial):
create or replace function before_insert_on_forumtopics()
returns trigger language plpgsql as $$
begin
insert into upvotes (userid, forumtopicsid)
values (new.userid, new.id);
return new;
end $$;
Next, create a trigger:
create trigger before_insert_on_forumtopics
before insert on forumtopics
for each row
execute procedure before_insert_on_forumtopics();
Read in the documentation about Trigger Behavior, Trigger Procedures and CREATE TRIGGER.
CREATE TRIGGER init_upvote
AFTER INSERT ON forumtopics
FOR EACH ROW
EXECUTE PROCEDURE function_that_inserts_into_upvote(NEW.id, NEW.userid);

PLPGSQL Cascading Triggers?

I am trying to create a trigger, so that when ever I add a new record it adds another record in the same table. The session field will only take values between 1 and 4. So when I add a 1 in session I want it to add another record but with session 3 blocked. But the problem is that it leads to cascading triggers and it inserts itself again and again because the trigger is triggered when inserted.
I have for example a simple table:
CREATE TABLE example
(
id SERIAL PRIMARY KEY
,name VARCHAR(100) NOT NULL
,session INTEGER
,status VARCHAR(100)
);
My trigger function is:
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO example VALUES (NEW.id + 1, NEW.name, NEW.session+2, 'blocked');
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
Trigger is:
CREATE TRIGGER add_block
AFTER INSERT OR UPDATE
ON example
FOR EACH ROW
EXECUTE PROCEDURE add_block();
I get error:
SQL statement "INSERT INTO example VALUES ( $1 +1, $2 , $3 + 2, $4)"
PL/pgSQL function "add_block" line 37 at SQL statement
This error repeats itself so many times that I can't see the top.
How would I solve this?
EDIT:
CREATE TABLE block_rules
(
id SERIAL PRIMARY KEY
,session INTEGER
,block_session INTEGER
);
This table holds the block rules. So if a new record is inserted into the EXAMPLE table with session 1 then it blocks session 3 accordingly by inserting a new record with blocked status in the same (EXAMPLE) table above (not block_rules). Same for session 2 but it blocks session 4.
The block_rules table holds the rules (or pattern) to block a session by. It holds
id | session | block_session
------------------------------
1 | 1 | 3
2 | 2 | 4
3 | 3 | 2
How would I put that in the WHEN statement of the trigger going with Erwin Branstetter's answer below?
Thanks
New answer to edited question
This trigger function adds blocked sessions according to the information in table block_rules.
I assume that the tables are linked by id - information is missing in the question.
I now assume that the block rules are general rules for all sessions alike and link by session. The trigger is only called for non-blocked sessions and inserts a matching blocked session.
Trigger function:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name
,(SELECT block_session
FROM block_rules
WHERE session = NEW.session)
,'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
Trigger:
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.status IS DISTINCT FROM 'blocked')
EXECUTE PROCEDURE add_block();
Answer to original question
There is still room for improvement. Consider this setup:
CREATE OR REPLACE FUNCTION add_block()
RETURNS TRIGGER AS
$BODY$
BEGIN
INSERT INTO example (name, session, status)
VALUES (NEW.name, NEW.session + 2, 'blocked');
RETURN NULL;
END;
$BODY$ LANGUAGE plpgsql;
CREATE TRIGGER add_block
AFTER INSERT -- OR UPDATE
ON example
FOR EACH ROW
WHEN (NEW.session < 3)
-- WHEN (status IS DISTINCT FROM 'blocked') -- alternative guess at filter
EXECUTE PROCEDURE add_block();
Major points:
For PostgreSQL 9.0 or later you can use a WHEN condition in the trigger definition. This would be most efficient. For older versions you use the same condition inside the trigger function.
There is no need to add a column, if you can define criteria to discern auto-inserted rows. You did not tell, so I assume that only auto-inserted rows have session > 2 in my example. I added an alternative WHEN condition for status = 'blocked' as comment.
You should always provide a column list for INSERTs. If you don't, later changes to the table may have unexpected side effects!
Do not insert NEW.id + 1 in the trigger manually. This won't increment the sequence and the next INSERT will fail with a duplicate key violation.
id is a serial column, so don't do anything. The default nextval() from the sequence is inserted automatically.
Your description only mentions INSERT, yet you have a trigger AFTER INSERT OR UPDATE. I cut out the UPDATE part.
The keyword plpgsql doesn't have to be quoted.
OK so can't you just add another column, something like this:
ALTER TABLE example ADD COLUMN trig INTEGER DEFAULT 0;
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
IF NEW.trig = 0 THEN
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked', 1);
END IF;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';
it's not great, but it works :-)
CREATE OR REPLACE FUNCTION add_block() RETURNS TRIGGER AS $$
BEGIN
SET SESSION session_replication_role = replica;
INSERT INTO example VALUES (NEXTVAL('example_id_seq'::regclass), NEW.name, NEW.session+2, 'blocked');
SET SESSION session_replication_role = origin;
RETURN NULL;
END;
$$ LANGUAGE 'plpgsql';