PostgreSQL UPSERT Trigger returning id - function

I've an UPSERT trigger on a table that may update instead of inserting while doing insert operation. I've function doing insert on that table and returning id However it doesn't return an id when it updates instead of inserting. I want to get the id in both cases.
Trigger Code
perform 1 from tera_subject
where id = new.subject_id and owner = new.user_id;
if found then
return null;
else
select id into vote_id from tera_votes where
user_id = new.user_id and
subject_id = new.subject_id;
if not found then
return new;
else
-- raise notice 'vote_id: % ; vote: %',vote_id,new.vote;
if(tg_op = 'INSERT') then
begin
-- raise notice 'redirecting to update';
update tera_votes
set vote=new.vote
WHERE id = vote_id;
end;
elsif(tg_op = 'UPDATE') then
-- raise notice 'in update';
return new;
end if;
-- raise notice 'end of trigger %',tg_op;
return null;
end if;
end if;
end;

I don't think you'll manage to have anything “returned” by the trigger.
What you're doing inside is:
running update based on your conditions;
suppressing the INSERT statement that fired the trigger.
This means, that INSERT is terminated in an easy way (without exception), but it also means that it's impossible to provide you any details, as trigger functions do not return any values.
If you need to have the ID of the UPSERT-ed item, consider using a function that will always return you the ID, like this one.

Related

Record changes in a different table

I have successfully set up a history table according to this tutorial:
https://www.cybertec-postgresql.com/en/tracking-changes-in-postgresql/
My problem is that this function saves both the whole new record and the whole old record as jsons.
How can I alter this function so that only those column titles and values will be added to the json, which have really been changed?
In other words how can I replace the expression 'row_to_json(OLD)' with one which represents only the difference between row_to_json(NEW) row_to_json(OLD)?
CREATE FUNCTION change_trigger() RETURNS trigger AS $$
BEGIN
IF TG_OP = 'INSERT'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, new_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(NEW));
RETURN NEW;
ELSIF TG_OP = 'UPDATE'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, new_val, old_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP,
row_to_json(NEW), row_to_json(OLD));
RETURN NEW;
ELSIF TG_OP = 'DELETE'
THEN
INSERT INTO logging.t_history (tabname, schemaname, operation, old_val)
VALUES (TG_RELNAME, TG_TABLE_SCHEMA, TG_OP, row_to_json(OLD));
RETURN OLD;
END IF;
END;
$$ LANGUAGE 'plpgsql' SECURITY DEFINER;
Finally figured out that
I needed to convert the 'NEW' and 'OLD' rows to jsonb. (to_jsonb(NEW))
I needed another function which does the subtraction
https://coussej.github.io/2016/05/24/A-Minus-Operator-For-PostgreSQLs-JSONB/

MySQL Create Trigger Syntax Error (Last Line)

I'm creating a MySQL trigger designed to update various tables with a new value if certain values are changed by an UPDATE query. I keep receiving the following syntax error for this particular trigger:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 29
Line 29 in this case is the line of the END statement.
This is my full code:
DELIMITER $$
CREATE TRIGGER update_selling_prices BEFORE UPDATE ON t1
FOR EACH ROW
BEGIN
DECLARE update_price INT DEFAULT 0;
DECLARE selling_price_1 DECIMAL(10, 3) DEFAULT 0.000;
DECLARE selling_price_2 DECIMAL(10, 3) DEFAULT 0.000;
IF (OLD.rrp_price <> NEW.rrp_price OR OLD.discount_1 <> NEW.discount_1 OR OLD.discount_2 <> NEW.discount_2 OR OLD.net_price <> NEW.net_price OR OLD.markup <> NEW.markup OR OLD.delivery_cost <> NEW.delivery_cost) THEN
SET update_price = (SELECT b.is_auto_update FROM price_categories c INNER JOIN brands b ON b.brand_name = c.brand_name WHERE c.id = NEW.category_id LIMIT 1);
IF (update_price = 1) THEN
IF (NEW.is_bundle = 0) THEN
UPDATE t2 SET temp = 'Fired Single' WHERE id = NEW.id;
ELSE IF (NEW.is_bundle = 1) THEN
UPDATE t2 SET temp = 'Fired Bundle' WHERE id = NEW.id;
END IF;
END IF;
END IF;
END;
$$
DELIMITER ;
The current UPDATE statements are just placeholders for some actual calculations I'll end up doing.
Please note: I use Sequel Pro for most MySQL-related stuff and initially was using their GUI to try and add the trigger - it automatically adds the surrounding code so I would only create everything between the BEGIN and END statements. That also resulted in this same syntax error, so I don't believe it's related to the delimiters like some similar threads I've already found on here. Nevertheless, I've tried adding the full trigger code via a normal query; changing the delimiter syntax - for example END$$, END $$, END; $$ etc.
I've successfully created other triggers with similar syntax, but they do not include the DECLARE syntax.
Where am I going wrong?
The problem is here:
IF (NEW.is_bundle = 0) THEN
UPDATE t2 SET temp = 'Fired Single' WHERE id = NEW.id;
ELSE IF (NEW.is_bundle = 1) THEN
UPDATE t2 SET temp = 'Fired Bundle' WHERE id = NEW.id;
END IF;
Review documentation: https://dev.mysql.com/doc/refman/8.0/en/if.html
MySQL supports ELSEIF and this is different than ELSE IF. If you use ELSEIF, this continues the structure of the IF statement. If you use ELSE IF, it starts a new IF statement, so it should be like this:
IF (NEW.is_bundle = 0) THEN
UPDATE t2 SET temp = 'Fired Single' WHERE id = NEW.id;
ELSE
IF (NEW.is_bundle = 1) THEN
UPDATE t2 SET temp = 'Fired Bundle' WHERE id = NEW.id;
END IF;
END IF;
See that there is a complete IF/THEN/END IF statement within the ELSE block of the outer one?
But you didn't do that, so the END IF applies to the innermost IF statement, and then you're one level off for the rest of the body of the trigger.
When MySQL gets to the end of the whole CREATE TRIGGER statement, if there aren't enough ENDs to balance the blocks you began, MySQL complains with the error you saw.

How to check difference in each on OLD.* to NEW.* column in a MySQL Trigger?

Since SQL does not have a FOR-EACH statement, how could we verify if there is a difference on each value from the OLD object to the NEW object in a AFTER UPDATE type TRIGGER without knowing the table columns [and table names]?
Example today:
CREATE TRIGGER `audit_events_ugly`
AFTER UPDATE ON `accounts`
FOR EACH ROW
BEGIN
DECLARE changes VARCHAR(8000);
IF OLD.user_name <> NEW.user_name THEN
SET changes = 'user_name from % to %';
END IF;
IF OLD.user_type <> NEW.user_type THEN
SET changes = CONCAT(changes, ', user_type from % to %');
END IF;
IF OLD.user_email <> NEW.user_email THEN
SET changes = CONCAT(changes, ', user_email from % to %');
END IF;
CALL reg_event(how_canI_get_tableName?, #user_id, changes);
-- and that can go on and on... differently for every table.
END;
Example as I wish it could be:
CREATE TRIGGER `audit_events_nice`
AFTER UPDATE ON `accounts`
FOR EACH ROW
BEGIN
DECLARE changes VARCHAR(8000);
DECLARE N INT DEFAULT 1;
FOREACH OLD, NEW as OldValue, NewValue
BEGIN
IF OldValue <> NewValue THEN
SET changes = CONCAT(changes, ', column N: % to %');
SET N = N + 1;
END IF;
CALL reg_event(how_canI_get_tableName?, #user_id, changes);
-- now I can paste this code in every table that is audited..
END;
Any Ideas? WHILE, FOREACH, ARRAYS...
I think you cannot do that directly in a for-loop at the trigger level.
However, you could use a script to generate the trigger code. You would need to re-generate it every time you add/remove a field to the table (usually not frequently).

Trigger: Referencing updated attribute

I have a Trigger on UPDATE.
What is the correct procedure for referencing attribute from the table that is not updated by the UPDATE SQL command? Is the attribute still in the UPDATE variable? I would like to get the value of that attribute for the updated row.
You can access a values of a column before update and after update in MySQL by using keywords OLD and NEW.
For example if you want to determine whether a value of a column actually has been changed during updated you can do
IF NOT OLD.column_name <=> NEW.column_name THEN
-- do something here
END IF;
Note: <=> is NULL-safe comparison operator in MySQL
BTW: There is no UPDATED virtual table in MySQL. It's from SQL Server.
Here is a SQLFiddle demo. Note that even though update affected all records in the table, only one message has been logged in log table. It's because value for a row with id 2 in the end stayed the same.
UPDATE: to keep your finished flag in sync you need triggers for all events (insert, update, delete).
DELIMITER //
CREATE TRIGGER tg_ai_event
AFTER INSERT ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END//
CREATE TRIGGER tg_ad_event
AFTER DELETE ON event
FOR EACH ROW
BEGIN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = OLD.activity;
END//
CREATE TRIGGER tg_au_event
AFTER UPDATE ON event
FOR EACH ROW
BEGIN
IF NOT OLD.activity <=> NEW.activity THEN
-- if activity id was changed for an event then clculate finished flag
-- for both old and new activity id
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id IN(OLD.activity, NEW.activity);
ELSE
-- otherwise calculate finished flag only if done flag is changed
IF NOT OLD.done <=> NEW.done THEN
UPDATE activity a
SET status = (EXISTS(SELECT *
FROM event
WHERE activity = a.activity_id
AND done = 0))
WHERE activity_id = NEW.activity;
END IF;
END IF;
END//
DELIMITER ;
Here is SQLFiddle demo

PostgreSQL trigger updating secondary related table

I have two tables: table1 and table2, which triggers on inserts and on updates in the same function.
As you insert a value in table1 or table2 a value is inserted in table3, with the value table1.lastname || table1.firstname assigned to column3. The id obtained for the insert in table3 must be inserted into table1.id_table3.
CREATE OR REPLACE FUNCTION myschema.myfunction() RETURNS trigger AS
$BODY$
DECLARE
new_id_table_4 integer;
BEGIN
IF TG_OP = 'INSERT' THEN
IF TG_TABLE_NAME = 'table1' THEN
new_id_table_4 := 1;
ELSIF TG_TABLE_NAME = 'table2' THEN
new_id_table_4 := 2;
END IF;
INSERT INTO myschema.table3
(id, id_table4, name)
VALUES (DEFAULT, new_id_table_4, NEW.columnA||', '||NEW.columnB, TRUE, TRUE)
RETURNING id
INTO NEW.id_table3;
ELSIF TG_OP = 'UPDATE' THEN
IF OLD.columnA <> NEW.columnA OR OLD.columnB <> NEW.columnB THEN
UPDATE myschema.table3 SET
name = NEW.columnA||', '||NEW.columnB
WHERE id = NEW.id_cuenta;
END IF;
END IF;
RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE COST 100;
ALTER FUNCTION myschema.myfunction() OWNER TO myuser;
CREATE TRIGGER add_table3record_table1
AFTER INSERT OR UPDATE
ON myschema.table1
FOR EACH ROW
EXECUTE PROCEDURE myschema.myfunction();
CREATE TRIGGER add_table3record_table2
AFTER INSERT OR UPDATE
ON myschema.table2
FOR EACH ROW
EXECUTE PROCEDURE myschema.myfunction();
The problem is that when I insert a new record into table1 or table2,
...RETURNING id INTO NEW.id_table3;
It does not seem to have any effect.
This is my first function/trigger ever, and I cannot find the error.
Thank you!
I'm pretty sure you can't update a row AFTER it has already been inserted just by setting NEW.foo = bar.
Either:
Perform an update on the table1 setting the new id_table3 value (which is going to recursively call you ON UPDATE trigger, so be careful), or
Use a BEFORE trigger instead of an AFTER.
Depending on how your foreign keys are set up between table1 and table3, the latter may or may not be an option.