for each row trigger and no 'new' relation - postgresql

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

Related

Yii2 PostgreSql how to add default value from another column on addColumn in migration

I have PostgreSQL DB table with 4 columns ex. col1, col2, col3, col4.
Col3 is unique indexed. I want to add new column with notNull and default value of col3 as after that I want to make newly created column also unique?
Is it possible to achieve this by simple Yii2 migration?
You cannot get what you want directly; the default expression cannot reference a another column. What you can do is make the new column unique but null-able, then update the values, and finally make the column not null. If needed you can create a trigger to maintain the new column. (see demo)
alter table a_table add new_col text unique;
update a_table set new_col = col3;
-- clean up any null values in new_col; (there is nothing saying
alter table a_table alter column new_col set not null;
If need long term the create a trigger function and trigger to manage new_col:
-- set new_col column
create or replace function new_col()
returns trigger
language plpgsql
as $$
begin
new.new_col = coalesce(new.new_col, new.col3, old.new_col, ''); -- always on update ???
return new;
end;
$$;
create trigger a_table_biur
before insert or update
on a_table
for each row execute function new_col();
The trigger function used the minimum logic necessary, and will not satisfy every condition. You will need to evaluate that.

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 ();

I want to just log all the columns of the table with new or old values, but the trigger fails to compile

I want to create a trigger on firebird db:
SET TERM ^ ;
create trigger log_trigger_error for test
before insert or update
as
begin
INSERT INTO test_log (a,b)
select case when (new.rdb$field_name is null and old.rdb$field_name is not null
or new.rdb$field_name is not null and old.rdb$field_name is null
or new.rdb$field_name <> old.rdb$field_name)
then new.rdb$field_name
else old.rdb$field_name as a
end , 2 as b
from rdb$relation_fields
where rdb$relation_name ='TEST';
end^
SET TERM ; ^
You are creating a trigger for the table test but then in the trigger body you use new and old context variables on a select from table rdb$relation_fields. That's not possible, the new and old variables are only available for the table the trigger is for.
If you want to log changes to the data then use trigger like
create trigger log_trigger_error for test before update
as
begin
INSERT INTO test_log (a, b) VALUES(old.a, old.b);
end^
Now when record in the test table is updated the old values are logged into test_log table.

PostgreSQL AFTER INSERT trigger, and referencing the inserted row

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

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.