I am trying to achieve here is to basically override 0 rows Updated, when UPDATE is issued in-case the actual PK/UK value doesn't exist in the table. This is what I have done:
Actual Table:
CREATE TABLE fdrgiit.vereine(
team numeric(10) primary key,
punkte int not null,
serie int not null
);
Dummy Table:
CREATE TABLE fdrgiit.dummyup
(
id numeric(1) PRIMARY KEY,
datetest timestamp
);
Inserted records in both the tables:
insert into vereine(team,punkte,serie) values(1, 50, 1);
insert into vereine(team,punkte,serie) values(2, 30, 1);
insert into vereine(team,punkte,serie) values(3, 25, 1);
insert into vereine(team,punkte,serie) values(4, 37, 2);
insert into dummyup values(1, now());
Created the following function and trigger:
create or replace function updateover()
returns trigger as
$BODY$
begin
if EXISTS (select 1 FROM vereine WHERE team = new.team ) then
RETURN NEW;
else
UPDATE fdrgiit.dummyup set datetest=now() where id=1;
RETURN NULL;
end if;
end;
$BODY$
LANGUAGE plpgsql;
create trigger update_redundancy
before update on vereine
for each row
execute procedure updateover() ;
But when I execute an UPDATE like this on the , I am still get 0 rows affected
update vereine set punkte=87 where team=5;
Kindly review and please suggest if this is something that can be done.
You cannot trigger anything with an UPDATE that does not affect row as triggers are only fired for affected rows.
But you could wrap your alternative UPDATE into a function:
CREATE OR REPLACE FUNCTION updateover()
RETURNS int AS
$func$
UPDATE dummyup
SET datetest = now()
WHERE id = 1
RETURNING 2;
$func$ LANGUAGE sql;
... and run your UPDATE nested like this:
WITH upd AS (
UPDATE vereine
SET punkte = 87
WHERE team = 5 -- does not exist!
RETURNING 1
)
SELECT 1 FROM upd
UNION ALL
SELECT updateover()
LIMIT 1;
db<>fiddle here
If no row qualifies for an UPDATE, then 1st outer SELECT 1 FROM upd returns no row and Postgres keeps processing the 2nd SELECT updateover(). But if at least one row is affected, the final SELECT is never executed. Exactly what you want.
This updates dummyup one time if the UPDATE on vereine does not affect any rows; never several times. But that's ok, since now() is STABLE for the duration of the transaction.
Related:
Return a value if no record is found
Related
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;
Assuming I have two tables final table and table_1, I want to use the the newest values from table_1 and insert them with a trigger in the final_table with every INSERT ON table_1. When I create the triggerfunction inserttrigger() as shown in the example and create the trigger, I get the newest value times the number of rows in table_1. How to write the trigger proper that I get only the single newest record in table1?
Doing:
-- Create tables and inserting example values
CREATE TABLE final_table(id INTEGER, value_fin INTEGER);
CREATE TABLE table_1(id INTEGER, value INTEGER);
INSERT INTO table_1 VALUES(1, 200), (2,203), (3, 209);
-- Create Triggerfunction
CREATE OR REPLACE FUNCTION inserttrigger()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO final_table
SELECT latest.id, latest.value
FROM (SELECT NEW.id, NEW.value FROM table_1) AS latest;
RETURN NEW;
END;
$func$ language plpgsql;
-- Create Trigger
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
FOR EACH ROW EXECUTE PROCEDURE inserttrigger() ;
--Insert example values
INSERT INTO table_1 VALUES(4, 215);
Results in:
SELECT * FROM final_table
id | value_fin
4 215
4 215
4 215
But should look like:
id | value_fin
4 215
While:
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
EXECUTE PROCEDURE inserttrigger() ;
Results in:
ERROR: record "new" is not assigned yet
DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
I would recommend the VALUES() syntax:
CREATE OR REPLACE FUNCTION inserttrigger()
RETURNS TRIGGER AS
$func$
BEGIN
INSERT INTO final_table VALUES(NEW.id, NEW.value);
RETURN NEW;
END;
$func$ language plpgsql;
CREATE TRIGGER final_table_update BEFORE INSERT ON table_1
FOR EACH ROW EXECUTE PROCEDURE inserttrigger();
Note that you could also get the same behavior with a common-table-expression and the returning syntax, which avoids the need for a trigger:
with t1 as (
insert into table_1(id, value_fin) values(4, 215)
returning id, value_fin
)
insert into final_table(id, value) select id, value_fin from t1
Hi I try to Insert value in the second trigger with new id from first trigger only if condition is fulfiled, but I'm stuck.
table1_trg works
CREATE TABLE table1 (
id NUMBER(9,0) NOT NULL,
subject VARCHAR2(200) NOT NULL,
url_address VARCHAR2(200) NOT NULL,
)
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
SELECT table1_seq.NEXTVAL
INTO :new.id
FROM dual;
END;
/
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
WHEN (NEW.subject = 'Task')
BEGIN
INSERT INTO CSB.table1 (url_address)
VALUES ('blabla.com?' || :new.id);
END;
/
I insert only subject but after that i receive exception that subject can not be null.
INSERT INTO corp_tasks_spec (subject) VALUES ('Task')
Any ideas how to resolve it?
You should not be inserting a new record into the same table, you should be modifying the column values for the row you're already inserting - which the trigger is firing against. You're getting the error because of that second insert - which is only specifying the URL value, not the subject or ID (though the first trigger would fire again and set the ID for that new row as well - so it complains about the subject).
Having two triggers on the same firing point can be difficult in old versions of Oracle as the order they fired wasn't guaranteed - so for instance your second trigger might fire before the first, and ID hasn't been set yet. You can control the order in later versions (from 11g) with FOLLOWS:
CREATE OR REPLACE TRIGGER table1_url
BEFORE INSERT ON table1
FOR EACH ROW
FOLLOWS table1_trg
WHEN (NEW.subject = 'Task')
BEGIN
:NEW.url_address := 'blabla.com?' || :new.id;
END;
/
This now fires after the first trigger, so ID is set, and assigns a value to the URL in this row rather than trying to create another row:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
But you don't really need two triggers here, you could do:
DROP TRIGGER table1_url;
CREATE OR REPLACE TRIGGER table1_trg
BEFORE INSERT ON table1
FOR EACH ROW
BEGIN
:NEW.id := table1_seq.NEXTVAL; -- no need to select from dual in recent versions
IF :NEW.subject = 'Task' THEN
:NEW.url_address := 'blabla.com?' || :new.id;
END IF;
END;
/
Then that trigger generates the ID and sets the URL:
INSERT INTO table1 (subject) VALUES ('Task');
1 row inserted.
SELECT * FROM table1;
ID SUBJECT URL_ADDRESS
---------- ---------- --------------------
2 Task blabla.com?2
3 Task blabla.com?3
Of course, for anything except Task you'll have to specify the URL as part of the insert, or it will error as that is a not-null column.
Create sequence
CREATE SEQUENCE table1_SEQ
START WITH 1
MAXVALUE 100000
MINVALUE 1
NOCYCLE
NOCACHE
NOORDER;
CREATE TRIGGER
CREATE OR REPLACE TRIGGER table1_TRG
Before Insert
ON table1 Referencing New As New Old As Old
For Each Row
Declare V_Val Number;
Begin
Select table1_SEQ.NextVal into V_Val From Dual;
If Inserting Then
:New.id:= V_Val;
End If;
End;
/
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';
attempting to maintain an edit log using rules.
create table t1(
id serial primary key,
c1 text,
... );
create table edit_log(
id int references t1,
editor_id int references users,
edit_ts timestamp default current_timestamp );
with an update, wish to update t1 and insert into edit_lot
update t1 set c1='abc', ... where id=456;
insert into edit_log( id, editor_id, current_timestamp );
this would be a pretty straightforward except for the arbitrary number of columns, eg,
update t1 set c1='abc', c2='def', editor_id=123 where id=456;
update t1 set c3='xyz', editor_id=123 where id=456;
how to write a rule for that?
I think a trigger will serve you better than a rule. Consider this demo.
Test setup
CREATE TEMP TABLE t1(id int, editor_id int, c1 text);
INSERT INTO t1(id, editor_id) VALUES (1,1),(2,2);
CREATE TEMP TABLE edit_log(id int, editor_id int, edit_ts timestamp);
Create trigger function
CREATE OR REPLACE FUNCTION trg_t1_upaft_log()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD IS DISTINCT FROM NEW THEN -- to avoid empty updates
INSERT INTO edit_log(id, editor_id, edit_ts)
VALUES(NEW.id, NEW.editor_id, now()::timestamp);
END IF;
RETURN NULL; -- trigger will be fired AFTER updates, return value is irrelevant.
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
Create trigger
CREATE TRIGGER upaft_log
AFTER UPDATE ON t1
FOR EACH ROW
EXECUTE PROCEDURE trg_t1_upaft_log();
Test
UPDATE t1 SET c1 = 'baz' WHERE id = 1;
SELECT * FROM edit_log; -- 1 new entry
UPDATE t1 SET c1 = 'baz' WHERE id = 1;
SELECT * FROM edit_log; -- no new entry, update changed nothing!
UPDATE t1 SET c1 = 'blarg';
SELECT * FROM edit_log; -- 2 new entries, update changed two rows.
Cleanup
DROP TRIGGER upaft_log ON t1;
DROP FUNCTION trg_t1_upaft_log()
-- Temp. tables will be dropped automatically at end of session.
Comment
It is very hard or plain impossible (depending on the details of your setup) for a rule to figure out which rows are updated.
A trigger AFTER UPDATE can decide after the fact and is the better choice. Also easy to integrate with (most) additional triggers and / or rules in this scenario.