Updating another table on conflict postgres - sql

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;

Related

How do I create this trigger?

I have two tables: table1 and table2. I have a trigger in table1 that inserts the current row into table2 based on some conditions. If the row gets inserted into table2, I want to delete that row from table1. Now in Oracle, it seems we cannot delete the current row from the trigger in table1 itself.
A row level trigger on a table can manipulate the data of the updated rows. It cannot perform additional dml on the table itself (select, insert, delete).
A possible solution is to create a view on table1 with an INSTEAD OF trigger that deletes from table1 and perform all insert/update/delete statements on the view instead of the table
Example: when test_table.name is updated to 'KOEN', then row itself will be deleted. This example shows just an ON UPDATE trigger but it can be done for INSERT/UPDATE/DELETE:
CREATE TABLE test_table (
id NUMBER
GENERATED BY DEFAULT ON NULL AS IDENTITY
CONSTRAINT test_table_id_pk PRIMARY KEY,
name VARCHAR2(100 CHAR)
);
CREATE VIEW test_table_v AS
SELECT
id,
name
FROM
test_table;
CREATE OR REPLACE TRIGGER test_table_v_bu
INSTEAD OF UPDATE ON test_table_v
FOR EACH ROW
DECLARE
BEGIN
-- do your stuff on the other table.
IF :NEW.NAME = 'KOEN' THEN
DELETE FROM test_table WHERE id = :NEW.ID;
END IF;
END;
/
koen>INSERT INTO test_table ( name ) VALUES ( 'JIM' );
1 row inserted.
koen>select * from test_table;
ID NAME
_____ _______
3 JIM
koen>update test_table_v set name= 'KOEN';
1 row updated.
stapp_dev--SAMPLEAPPS>select * from test_table;
no rows selected
koen>

How to use a newly inserted value from another table in PostgreSQL?

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

Change number of Rows Affected by Update

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

in postgres, splitting an update between two tables using Rules

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.

How to return a record from function, executed by INSERT/UPDATE rule (trigger)?

Do the following scheme for my database:
create sequence data_sequence;
create table data_table
{
id integer primary key;
field varchar(100);
};
create view data_view as
select id, field from data_table;
create function data_insert(_new data_view) returns data_view as
$$declare
_id integer;
_result data_view%rowtype;
begin
_id := nextval('data_sequence');
insert into data_table(id, field) values(_id, _new.field);
select * into _result from data_view where id = _id;
return _result;
end;
$$
language plpgsql;
create rule insert as on insert to data_view do instead
select data_insert(new);
Then type in psql:
insert into data_view(field) values('abc');
Would like to see something like:
id | field
----+---------
1 | abc
Instead see:
data_insert
-------------
(1, "abc")
Is it possible to fix this somehow?
Thanks for any ideas.
Ultimate idea is to use this in other functions, so that I could obtain id of just inserted record without selecting for it from scratch. Something like:
insert into data_view(field) values('abc') returning id into my_variable
would be nice but doesn't work with error:
ERROR: cannot perform INSERT RETURNING on relation "data_view"
HINT: You need an unconditional ON INSERT DO INSTEAD rule with a RETURNING clause.
I don't really understand that HINT. I use PostgreSQL 8.4.
What you want to do is already built into postgres. It allows you to include a RETURNING clause on INSERT statements.
CREATE TABLE data_table (
id SERIAL,
field VARCHAR(100),
CONSTRAINT data_table_pkey PRIMARY KEY (id)
);
INSERT INTO data_table (field) VALUES ('testing') RETURNING id, field;
If you feel you must use a view, check this thread on the postgres mailing list before going any further.