Getting partial NEW.<value> using AFTER INSERT trigger - sql

I'm trying to learn PostgreSQL. Now I came across a triggers, and I've wanted to create a trigger to 'copy' inserted data to another table.
I've managed to put together a function and an trigger. But every time I attempt to insert the data, I get partial data to the NEW.nameOfTheRow.
Image as Example on data that I should receive in the user_entity table:
And here is the data that is getting inserted by the function in the Employee table:
And here's the table user_entity:
Here is the table "Employee":
Here's the code for the trigger:
CREATE OR REPLACE TRIGGER sync_two_tables_on_insert
AFTER INSERT ON user_entity
FOR EACH ROW
EXECUTE PROCEDURE sync_employee_with_keycloak_entity();
And here's the code for the function:
CREATE OR REPLACE FUNCTION sync_employee_with_keycloak_entity() RETURNS trigger AS $$
BEGIN
-- This script is not optimized by all means, it will attempt to add data with brute force,
-- Ideally, we would like to do some checking's before attempting to transfer data,
-- "Type" row is not 'valid' here, as the data is not correct, the only reason I've put it there is because it is set to Not Null
-- TODO: Optimize whole script, make validation checks, add error handlers, filter to not pass duplicates.
INSERT INTO "Employees" ("Id", "Email", "FirstName", "LastName", "Alias", "Type")
VALUES(new.id::uuid, new.email, new.first_name, new.last_name, new.username, new.not_before)
ON CONFLICT DO NOTHING;
RETURN NULL;
END
$$
LANGUAGE plpgsql;
I don't understand why I don't get new data on NEW.email, NEW.first_name, NEW.last_name
But I do get correct data on NEW.id, NEW.username and NEW.not_before.

Related

Trigger for creating a new record after new user creation doesn't work in PostgreSQL

I have a settings table and a users table. I want to create a trigger to insert a new settings record every time a new user gets created and reference the id of the newly created user in the settings record(settings.user_id in this case). Currently, I've come up with this trigger and function but whenever I try to insert a record into the users table the query fails to execute.
Procedure:
CREATE OR REPLACE FUNCTION create_settings()
RETURNS TRIGGER
LANGUAGE PLPGSQL
AS
$$
BEGIN
INSERT INTO settings (user_id) VALUES (OLD.id);
RETURN OLD;
END;
$$
Trigger:
CREATE TRIGGER create_settings
AFTER INSERT ON users
EXECUTE PROCEDURE create_settings();
and here's the error I get:
ERROR: null value in column "user_id" of relation "settings" violates not-null constraint
DETAIL: Failing row contains (95a8d1ae-cf9d-40be-b5d2-b2d231fb7e1b, null, null, null, null, light, 2022-01-02 15:29:14.290823).
CONTEXT: SQL statement "INSERT INTO settings (user_id) VALUES (OLD.id)"
PL/pgSQL function create_settings() line 3 at SQL statement
The only field that I need is the first null from the error which is the actual reference to the user by ID(most of the values that are null are optional and are NULL by default).
Take a look at the documentation for create trigger. In particular the difference between row level and statement level triggers. You save a statement level trigger. Statement level triggers do not have the pseudo roes Old or New values, They are all null. You need a row level trigger to get values from Old or New. So:
CREATE TRIGGER create_settings
AFTER INSERT ON users
for each row
EXECUTE PROCEDURE create_settings();
It is important to notice that the OLD variable is only available for triggers that execute UPDATE/DELETE in row-level as shown here, so in this case is not possible to get the data you want with an INSERT trigger.

how to make sure that trigger generated value is being returned?

I have this INSERT query, which purpose is to insert the one row in my database.
Similarly I also have a INSERT query which insert multiple rows.
One of the columns in the table is generated after the values has been generated, since it combines a set of column values to construct a name. The name itself it generated from a Trigger, and its triggered After insert, since the column values has to exist for me to generate the name.
my problem now is when I insert one row or multiple rows, I want to know the the generated column value, but when I return it, it states its null?
#$"INSERT INTO registration_table (id, ...,)
VALUES (1,...,)
RETURNING row_id, name;";
which in return gives me an id the one I inserted, but the not actual name but instead I get null..
The trigger is pretty straight forward
CREATE TRIGGER name_insert_trigger
AFTER INSERT
ON registration_table
REFERENCING NEW TABLE AS new_inserts
FOR EACH STATEMENT
WHEN (pg_trigger_depth() = 0)
EXECUTE PROCEDURE registration_entry_name();
CREATE OR REPLACE FUNCTION registration_entry_name()
RETURNS trigger AS
$$
DECLARE
BEGIN
UPDATE registration_table
SET name = |Pattern| -- This one being the actual name generated..
FROM new_inserts
WHERE new_inserts.row_id = registration_table.row_id;
RETURN null;
END;
$$
LANGUAGE plpgsql;
but the insert query above does not return the name?
why not?
You actually need a BEFORE trigger, your data values will be there. The designation of Before and After very often causes misconceptions especially of row level triggers. The terms do not indicate their timing in relation to the DML. I have found it useful to think of them as "before final data values are set" and "after final data values are set" but both run before the invoking DML completes (for now we will bypass deferred triggers). Lets look at inserts. When the before row trigger fires the NEW row contains the values at that point for every column in the row, any value not specified in the statement will be null or contain the specified default if any. Before row triggers can can change any column. After row triggers cannot change columns, if present any change is ignored.
Your description and code imply you need to combine a couple columns to generate the content of another. Since you did not specify exactly that I will build an example and demo.
create table users ( usr_id integer generated always as identity
, lname text not null
, fname text not null
, full_name text not null
) ;
create or replace
function users_bir()
returns trigger
language plpgsql
as $$
begin
if new.full_name is null
then
new.full_name = trim(new.fname) || ' ' || trim(new.lname);
end if;
return new;
end;
$$;
create trigger users_bir_trg
before insert on users
for each row
execute procedure users_bir();
insert into users(fname, lname)
values ( 'George', 'Henery')
, ( 'Samatha', 'van Horm');
insert into users(fname, lname, full_name)
values ( 'Wacky', 'Warriors','Not so tough guys');
This setup allows the full_name to be specified or generated. If only generation is desired remove the IF leaving only the assignment statement. Even better if you have Postgres 12 or higher just define the the column as a generated column. This is also in the demo.

trigger to update specific column when insert/update happened in same table

I trying to write a trigger that will update a column when user insert or updates a row, within the same table.
Example:
insert into user(ID, F_NM, L_NM, EMAIL) values ('1', 'John','Doe','john.doe#market.org.com');
after the insert, i want to call: update user set ORG = 'market' where ID = '1'.
create or replace trigger user_change
after insert or update of EMAIL on USER
for each row
declare
NEW_ORG VARCHAR(10);
BEGIN
CASE
when :NEW.EMAIL like '$#market.org.com' then
NEW_ORG := 'market';
........
END CASE;
UPDATE USER set ORG = NEW_ORG where ID = :NEW.ID
END;
Calculating the new ORG work, but I can't get the update statement to work.
I get 'ORA-04091 table USER is mutating, trigger/funtion may not see it', figure its due to me inserting/updating the same record at same time. Tried adding 'pragma autonomous_transaction' and 'commit' to the trigger, the insert/update of fields works but the ORG does not get updated.
Also tried changing to INSTEAD OF INSERT OR UPDATE OF EMAIL but I keep getting 'ORA-04073 column list not valid for this trigger type'
create or replace trigger user_change
instead of insert or update of EMAIL on USER
while i get 'ORA-25002 cannot create instead of triggers on tables'
create or replace trigger user_change
instead of insert on USER
Why not simply turn the trigger to a before trigger, when you can set the value before it is written? This way, you don't need to run a new DML statement on the table, which avoid the "mutating" error.
create or replace trigger user_change
after insert or update of email on user
for each row
begin
if :new.email like '%#market.org.com' then
:new.org := 'market';
end if;
end;
Looks like your org column can be calculated virtual column. In this case it would be better to create user-defined deterministic pl/sql function that returns correct calculated value and add it to your table, for example:
Alter table t add org varchar2(30) generated always as (f_get_org(email))

UPSERT inserts duplicate null entry into table (ORACLE)

I am trying to make an upsert trigger on ORACLE via PL/SQL by checking some examples, i am doing fine, i think it is the last step i should only configure. My requirement is that :
A system that will insert to that field will remain one column always null, so i will read column value from another table, then upsert it with inclusion of that value.
d2c_region_locale_config holds d2c_is_active value, so i firstly read that value regarding to locale condition then trigger inserts or updates to table with addition of this value on active_for_d2c column.(for update i am using locale and country columns as it is shown on where clause, they are not PK but has not null condition)
So i've created this trigger:
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
DECLARE
l_active_for_d2c INTEGER;
BEGIN
if :NEW.active_for_d2c is null then
DELETE from pim_locale_country where active_for_d2c is null;
select distinct(d2c_isactive) into l_active_for_d2c from d2c_region_locale_config where d2c_locale= :NEW.locale;
UPDATE pim_locale_country
SET locale = :NEW.locale, locale_name = :NEW.locale_name,
country = :NEW.country, country_name = :NEW.country_name, isdummy = :NEW.isdummy,
active_for_d2c = l_active_for_d2c, itextpos = :NEW.itextpos, locale_charset = :NEW.locale_charset,
fallback_locale = :NEW.fallback_locale, default_for_lang = :NEW.default_for_lang, opeclang = :NEW.opeclang
where locale = :NEW.locale and country = :NEW.country;
IF ( sql%notfound ) THEN
INSERT INTO PIM_LOCALE_COUNTRY (locale,locale_name,country,country_name,isdummy,active_for_d2c,itextpos,locale_charset,fallback_locale,default_for_lang,opeclang)
VALUES (:NEW.locale, :NEW.locale_name,:NEW.country,:NEW.country_name,:NEW.isdummy,l_active_for_d2c,:NEW.itextpos,:NEW.locale_charset,:NEW.fallback_locale,:NEW.default_for_lang,:NEW.opeclang);
END IF;
end if;
END;
It currently does the job, reads value and inserts or updates the existing locale-country couple for other values. But critical thing is that, table always has one "null" value(Please check screenshot), even that i run delete statement at the beginning on my trigger. So my question would be how to delete, or how to make this approach on trigger side ?
Many thanks for answers!
Trigger before insert doesn't block insert itself, so you insert that record twice. That is, once your trigger done its work (inserted or updated record), oracle will proceed with insert (or update) using values that stand in NEW record of your trigger. If trigger modifies NEW., it will be stored as you changed it, but if trigger inserts something itself, you can get more records.
You can use instead of insert or update triggers, and then oracle will not run its own inserts/updates after trigger finishes.
But more common way for 1-record triggers is to modify fields in NEW, for this case field NEW.d2c_is_active.
It looks like this (possible typos, please check)
CREATE OR REPLACE TRIGGER BL_PIM_LOCALE_COUNTRY
BEFORE INSERT OR UPDATE ON PIM_LOCALE_COUNTRY REFERENCING NEW AS NEW OLD AS OLD
FOR EACH ROW
BEGIN
if :NEW.active_for_d2c is null then
select d2c_isactive
into :NEW.active_for_d2c
from d2c_region_locale_config
where d2c_locale= :NEW.locale and rownum<=1;
end if;
END;

SQL postgresql insert statement

I have a following question, for example I have a following table:
CREATE TABLE "regions" (gid serial PRIMARY KEY,
"__gid" int8,
"name" varchar(20),
"language" varchar(7),
"population" int8);
And I want to insert some records, say one of the values for "name" is - 'B', what sort of code would I have to write to change 'B' to 'English-Speaking'? Is that done with some sort of trigger? So would I have to write a trigger to change the values automatically on insert? Any help greatly appriciated!!!
It's an UPDATE statement which will do what you wish, in this case:
UPDATE regions set name = 'English-Speaking' where name = 'B';
To put this in a function use something like:
CREATE OR REPLACE FUNCTION insert_into_wgs()
RETURNS void AS
$$
BEGIN
UPDATE regions SET name = 'English-Speaking' WHERE name = 'B';
END
$$
LANGUAGE 'pgpsql';
Then you create a trigger to run this function:
CREATE TRIGGER log_update
AFTER UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.* IS DISTINCT FROM NEW.*)
EXECUTE PROCEDURE
insert_into_wgs();
Assuming I've guessed what you mean correctly from your description:
You will need a simple BEFORE INSERT OR UPDATE ... FOR EACH ROW EXECUTE trigger to invoke a PL/PgSQL trigger procedure that changes the value of the NEW record and then does a RETURN NEW.
The documentation contains abundant details, and since this is homework I'm not going to provide a complete example. Start with CREATE TRIGGER and PL/pgSQL trigger procedures.