PostgreSQL AFTER INSERT trigger, and referencing the inserted row - postgresql

I have an INSERT trigger in PostgreSQL that I'm trying to have join the inserted row on another table, and then insert the result in a third table. Let's call the original table, that the INSERT trigger fires on, "A"; the table the cursor joins A on "B"; and the table the trigger function then inserts to "C".
My thinking was that an AFTER INSERT function should allow me to pass a value from the "NEW" row as a parameter in order to reference its corresponding row in Table A, like this:
myCursor CURSOR (insertedKey A.key%TYPE) FOR
SELECT *
FROM A
INNER JOIN B
ON A.key=B.key
WHERE A.key=insertedKey;
...
OPEN myCursor (NEW.key);
FETCH NEXT FROM myCursor INTO row_C;
INSERT INTO C VALUES (row_C.*);
This gives me an empty cursor. If I trigger the trigger on AFTER UPDATE, it works, but with the old row from A. This leads me to think that PostgreSQL doesn't think AFTER INSERT/UPDATE means what I think it means.
Or maybe I'm just doing something wrong? Is there any way of doing what I'm trying to do?

Not sure why it happens but you could do something along the line of
INSERT INTO C
SELECT NEW.*, B.*
FROM B
WHERE B.key = NEW.key

Related

Accidentally detected all data from a table, insert dummy data to table using loop psql

I had accidentally deleted most of the rows in my Postgres table (data is not important its in my test environment, but I need a dummy data to be insert in to these table).
Let us take three tables,
MAIN_TABLE(main_table_id, main_fields)
ADDRESS_TABLE(address_table_id, main_table_id, address_type, other_fielsds)
CHAID_TABLE(chaid_table_id,main_table_id, shipping_address_id, chaild_fields)
I had accidentally deleted most of the data from ADDRESS_TABLE.
ADDRESS_TABLE has a foreign key from MAIN_TABLE ,i.e. main_table_id. for each row in MAIN_TABLE there is two entries in ADDRESS_TABLE, in which one entry is its address_type is "billing/default" and other entry is for address_type "shipping".
CHAID_TABLE has two foreign keys one from MAIN_TABLE, i.e. main_table_id and other from ADDRESS_TABLE i.e., shipping_address_id. this shipping_address_id is address id of ADDRESS_TABLE, its address_type is shipping and ADDRESS_TABLE.main_table_id = CHAID_TABLE.main_table_id.
These are the things that I needed.
I need to create two dummy address entries for each raw in MAIN_TABLE one is of address type "billing/default" and other is of type "shipping".
I need to insert address_table_id to the CHAID_TABLE whose ADDRESS_TABLE.main_table_id = CHAID_TABLE.main_table_id. and addres_type ="shipping"
if first is done I know how to insert second, because it is a simple insert query. I guess.
it can be done like,
UPDATE CHAID_TABLE
SET shipping_address_id = ADDRESS_TABLE.address_table_id
FROM ADDRESS_TABLE
WHERE ADDRESS_TABLE.main_table_id = CHAID_TABLE.main_table_id
AND ADDRESS_TABLE.addres_type ='shipping';
for doing first one i can use loop in psql, ie loop through all the entries in MAIN_TABLE and insert two dummy rows for each rows. But I don't know how to do these please help me to solve this.
I hope your solution is this, Create a function that loop through all the rows in MAIN_TABLE, inside the loop do the action you want, here two insert statement, one issue of this solution is you have same data in all address.
CREATE OR REPLACE FUNCTION get_all_MAIN_TABLE () RETURNS SETOF MAIN_TABLE AS
$BODY$
DECLARE
r MAIN_TABLE %rowtype;
BEGIN
FOR r IN
SELECT * FROM MAIN_TABLE
LOOP
-- can do some processing here
INSERT INTO ADDRESS_TABLE ( main_table_id, address_type, other_fielsds)
VALUES('shipping', r.main_table_id,'NAME','','other_fielsds');
INSERT INTO ADDRESS_TABLE ( main_table_id, address_type, other_fielsds)
VALUES('billing/default',r.main_table_id,'NAME','','other_fielsds');
END LOOP;
RETURN;
END
$BODY$
LANGUAGE plpgsql;
SELECT * FROM get_all_MAIN_TABLE ();

for each row trigger and no 'new' relation

i have 'after insert', 'for each row' trigger. on each insert it should insert a few rows to different table.
in the trigger function i can do:
insert into c values (
new.column1
)
but when i try to change it to more powerful version (before making much more complex inserts)
insert into c select
new.column1
from new
i get an error:
ERROR: relation "new" does not exist
how can insert values calculated from a query that refers to values of new?
new in a trigger function is a composite variable representing the row being inserted, so you can just:
insert into c
select new.column1;
the NEW keyword references the newly inserted values. To access any of the values, you don't need to SELECT from the table, you can simply:
INSERT INTO c (column1) VALUES NEW.column1

Postgres Insert Into View Rule with Returning Clause

I am attempting to allow insert statements with a returning clause into a view in Postgres v9.4, but am struggling with the syntax. This is how I want to call the insert statement:
CREATE VIEW MyView AS SELECT a.*, b.someCol1
FROM tableA a JOIN tableB b USING(aPrimaryKey);
INSERT INTO MyView (time, someCol) VALUES (someTime, someValue) RETURNING *;
INSERT INTO MyView (someCol) VALUES (someValue) RETURNING *;
Note that the default for time is NOW(). This is what I have so far:
CREATE RULE MyRuleName AS ON INSERT TO MyView DO INSTEAD (
INSERT INTO tableA (time) VALUES COALESCE(NEW.time, NOW());
INSERT INTO tableB (aPrimaryKey, someCol)
VALUES (CURRVAL('tableA_aPrimaryKey_seq'), NEW.someValue);
);
The above works to insert the value, but I am struggling to try and figure out how to add the returning statement. I have tried the following without success:
CREATE RULE MyRuleName AS ON INSERT TO MyView DO INSTEAD (
INSERT INTO tableA (time) VALUES COALESCE(NEW.time, NOW())
RETURNING *, NEW.someValue;
INSERT INTO tableB (aPrimaryKey, someCol)
VALUES (CURRVAL('tableA_aPrimaryKey_seq'), NEW.someValue);
);
-- ERROR: invalid reference to FROM-clause entry for table "new"
CREATE RULE MyRuleName AS ON INSERT TO MyView DO INSTEAD (
WITH a AS (INSERT INTO tableA (time)
VALUES COALESCE(NEW.time, NOW()) RETURNING *)
INSERT INTO tableB (aPrimaryKey, someCol)
SELECT aPrimaryKey, NEW.someValue FROM a RETURNING *;
);
-- ERROR: cannot refer to NEW within WITH query
Argh! Does anyone know of a way to add a returning statement that gets the primary key (SERIAL) and time (TIMESTAMP WITH TIME ZONE) added to the database in the first insert, along with the value of someCol in the second insert? Thanks!
You are much better off using an INSTEAD OF INSERT trigger here:
CREATE FUNCTION MyFuncName() RETURNS trigger AS $$
DECLARE
id integer;
BEGIN
INSERT INTO tableA (time) VALUES COALESCE(NEW.time, NOW()) RETURNING aPrimaryKey INTO id;
INSERT INTO tableB (aPrimaryKey, someCol1) VALUES (id, NEW.someValue);
RETURN NEW;
END; $$ LANGUAGE PLPGSQL;
CREATE TRIGGER MyView_on_insert INSTEAD OF INSERT ON MyView
FOR EACH ROW EXECUTE PROCEDURE MyFuncName();
Checking the current value of a sequence to see what was inserted in another table is bad bad bad practice. Even while you are here in a single transaction, don't do it.
You are confused about the issue of RETURNING information, because I am confused too when I read your question. Inside of a function use the INTO clause to populate locally declared variables to hold record values which you can then use in subsequent statements. Outside of a function, use the RETURNING clause as you do in your top-most code snippet.
I don't agree with the the hint ("use triggers instead of rules"), because triggers don't allow RETURNING. As written in the Postgresql docu it is a little bit tedious to write the right return list. If you keep the following in mind, it works:
You can only use columns from the original table to form a list which returns columns for the view (!). This means that you have to repeat the view expressions including all subqueries. (using WHERE instead of JOIN ... ON). Additionally you have to replace the NEW table by the original table name.

Inserts into Sybase table in the cursor fetch loop

I am writing a Sybase stored procedure with a cursor fetching records from table and inserting records back. Surprisingly I found that fetches see amd return records inserted in the same loop. Under some conditions this results in endless loop, sometimes the loop ends inseting a number of extra records. Looking through Sybase documentation I could not find a cure. Transaction isolation does not help since we are acting inside a single transaction. Of course I could solve the problem by inserting into a temporary table in the loop and then inserting back into main table after the loop ends.
But the question remain: how can I isolate cursor fetches from inserts into the same table?
Pseudocode follows:
create table t (v int)
go
insert into t(v) values (1)
create procedure p as
begin
declare #v int
declare c cursor for select v from t
begin transaction
open c
while 1=1 begin
fetch c into #v
if (##sqlstatus != 0) break
insert into t (v) values (#v+1)
end
close c
commit
end
go
exec p
go
I was surprised by this behavior, too. Cursors only iterate over rows, they are not immune to changes during the loop. The manual states this:
A searched or positioned update on an allpages-locked table can change
the location of the row; for example, if it updates key columns of a
clustered index. The cursor does not track the row; it remains
positioned just before the next row at the original location.
Positioned updates are not allowed until a subsequent fetch returns
the next row. The updated row may be visible to the cursor a second
time, if the row moves to a later position in the search order
My solution was to insert into a temporary table and copy the results back at the end, this also sped up the process by a factor of about 10.
Pseudo-code:
select * into #results from OriginalTable where 1<>1 --< create temp table with same columns
WHILE fetch...
BEGIN
insert into #results
select -your-results-here
END
insert into OriginalTable select * from #results

PL/pgSQL: Delete (update) row matching a record

I'm writing three triggers in PL/pgSQL. In each case, I have a RECORD variable and want to insert that into a table, delete it from the table, or update it to represent a second RECORD variable.
Adding is easy: INSERT INTO mytable VALUES (NEW.*);
Deleting isn't as easy, there doesn't seem to be a syntax for something like this:
DELETE FROM mytable
WHERE * = OLD.*;
Updating has the same problem. Is there an easy solution, short of generating matching SQL queries that compare each attribute using ideas from this answer?
You can use a trick for delete
create table t(a int, b int);
create table ta(a int, b int);
create function t1_delete()
returns trigger as $$
begin
delete from ta where ta = old;
return null;
end
$$ language plpgsql;
But this trick doesn't work for UPDATE. So fully simple trigger in PL/pgSQL is not possible simply.
You write about a record variable and it is, indeed, not trivial to access individual columns of an anonymous record in plpgsql.
However, in your example, you only use OLD and NEW, which are well known row types, defined by the underlying table. It is trivial to access individual columns in this case.
DELETE FROM mytable
WHERE mytable_id = OLD.mytable_id;
UPDATE mytable_b
SET some_col = NEW.some_other_col
WHERE some_id = NEW.mytable_id;
Etc.
Just be careful not to create endless loops.
In case you just want to "update" columns of the current row, you can simply assign to columns the NEW object in the trigger. You know that, right?
NEW.some_col := 'foo';
Dynamic column names
If you don't know column names beforehand, you can still do this generically with dynamic SQL as detailed in this related answer:
Update multiple columns in a trigger function in plpgsql