Mariadb trigger set specific row - triggers

Hei, I got a trigger problem ...
I have a table DuelRound(Id, Status, Winner, RoundNumber, DuelId ..)
There are four rounds each Duel. So the Roundnumber indicates which round it is (from 0 to 3)
When I am updating the Status, I want to update the Status from the next Round.
But how can I get the next Round in a trigger ?
I tried something like that: (Before update)
BEGIN
IF new.Status = 3 THEN
SET status = 1
WHERE NEW.RoundNumber = DuelRound.RoundNumber -1
AND NEW.DuelId = DuelRound.DuelId;
END IF;
END
But I cant use the variable Status there. Any Ideas ?

You cannot use a trigger to update the same table the trigger is attached to.
"A stored function or trigger cannot modify a table that is already being used (for reading or writing) by the statement that invoked the function or trigger."
What you can do instead, is to use a stored procedure which will do both updates in a transaction.
As for the error itself, the syntax SET status = 1 WHERE is incorrect, a trigger expects a valid SQL statement (SET does not have a WHERE-clause).

Related

How to get value from table using Firebird trigger

I want to get a value from the table and compare it with inserted value in the Firebird trigger.
Here is my code.
SET TERM ^;
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
select sys_code from system_table;
BEGIN
/* enter trigger code here */
if(sys_code == NEW.SYSTEM_CODE) then
insert into logs(log_detail)values('code matched');
end
END^
SET TERM;^
Alternatively, you can use a singular select expression instead.
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
BEGIN
sys_code = (select sys_code from system_table);
if(sys_code == NEW.SYSTEM_CODE) then
begin
insert into logs(log_detail)values('code matched');
end
END
If your select returns...
one single row or more, then it is the same as Mark's answer (error when multiple rows).
not a single row, the expression would return NULL while Mark's statement would do nothing (no change of variable value)
You may also think SQL SINGULAR existence predicate and about how it is different from EXISTS one.
Firebird docs - chapter 4.2.3. Existential Predicates
Interbase docs, stemming from old pre-Firebird documentation.
You also have to make your mind clearly what should happen if the transaction was rolled back (because of any database or network error, or because an application commanded to ROLLBACK changes): should your LOG still contain a record about the data modification that was not persisted or should the LOG record vanish with the un-inserted data row it describes.
If former is the case you have to insert log records in autonomous transaction (chapter 7.6.16).
You need to use the INTO clause:
CREATE TRIGGER after_in_systab FOR SYSTEMTAB
ACTIVE AFTER INSERT POSITION 0
AS
declare sys_code integer;
BEGIN
select sys_code from system_table into sys_code;
if(sys_code == NEW.SYSTEM_CODE) then
begin
insert into logs(log_detail)values('code matched');
end
END

Creating a trigger function in SQL

I've been given a task to create a trigger function for this question:
Add an update to the support ticket from a given staff member. If the ticket is closed no more updates should be allowed by either customers or staff members.
Insert an update to a ticket with values: ticketUpdateID = 10050; Message ='Highlight folder for backup and press to trash or right click and choose delete'; TicketID = 1010 ; StaffID=2
All I've managed so far is:
CREATE OR REPLACE FUNCTION update_ticket()
RETURNS trigger AS $BODY$
BEGIN
I would just like to know the layout of what i would have to put next.
Thanks
The question becomes "How do you want to handle bypassing the update: silently ignore the action or raise an exception"? Postgres provides for both cases. The trigger function for an Update DML receives 2 pseudo rows conveniently named old and new (at least by default) which represent the current database row and the "tobe" database row respectively. Somewhere in the table definition you have some column indicating the current status of the some value indicating "closed". Unfortunately you have not defined either so I will just make something up for demo purpose. The following will suppress the any update on a 'closed' ticket. I will leave Raising an exception for your research.
create or replace
function update_ticket()
returns trigger
language plpgsql
as $$
begin
if old.status = 'closed' then
return null;
end if;
return new;
end;
$$;

How to freeze field-values in postgres?

I would like to make sure that the values of certain required fields can not be changed later on. Is there a way to define this on the schema level?
Currently, I'm thinking about implementing this using a Record Trigger to raise an exception if a value change is noticed but this feels clunky.
E.g.:
BEGIN
IF (TG_OP = 'UPDATE') THEN
IF (NEW.product_id !== OLD.product_id) THEN
RAISE EXCEPTION 'Attempt to change frozen field "product_id" on UPDATE.'
END IF;
END IF;
END
If you want a trigger with comparison on a field, you can save execution by specifying condition on the trigger itself:
A Boolean expression that determines whether the trigger function will
actually be executed. If WHEN is specified, the function will only be
called if the condition returns true. In FOR EACH ROW triggers, the
WHEN condition can refer to columns of the old and/or new row values
by writing OLD.column_name or NEW.column_name respectively. Of course,
INSERT triggers cannot refer to OLD and DELETE triggers cannot refer
to NEW.
eg:
CREATE TRIGGER check_update
BEFORE UPDATE ON accounts
FOR EACH ROW
WHEN (OLD.product_id IS DISTINCT FROM NEW.product_id)
EXECUTE PROCEDURE check_account_update();
Of course it does not freeze anything. Now you cant change it with update, unless you disable the trigger, update and enable trigger back. But at least later requires alter table, not just update

Error during execution of trigger. How to rework select statement?

I'm relatively new to triggers so forgive me if this doesn't look how it should. I am creating a trigger that checks a user account for last payment date and sets a value to 0 if they haven't paid in a while. I created what I thought was a correct trigger but I get the error, "error during execution of trigger" when its triggered. From what I understand the select statement is causing the error as it selecting values which are in the process of being changed. Here is my code.
CREATE OR REPLACE TRIGGER t
BEFORE
UPDATE OF LASTLOGINDATE
ON USERS
FOR EACH ROW
DECLARE
USER_CHECK NUMBER;
PAYMENTDATE_CHECK DATE;
ISACTIVE_CHECK CHAR(1);
BEGIN
SELECT U.USERID, U.ISACTIVE, UP.PAYMENTDATE
INTO USER_CHECK, PAYMENTDATE_CHECK, ISACTIVE_CHECK
FROM USERS U JOIN USERPAYMENTS UP ON U.USERID = UP.USERID
WHERE UP.PAYMENTDATE < TRUNC(SYSDATE-60);
IF ISACTIVE_CHECK = 1 THEN
UPDATE USERS U
SET ISACTIVE = 0
WHERE U.USERID = USER_CHECK;
INSERT INTO DEACTIVATEDUSERS
VALUES(USER_CHECK,SYSDATE);
END IF;
END;
From what I thought, since the select is in the begin statement, it would run before an update, nothing would be changing about the tables until after if runs through the trigger. I tried but using :old in front of the select variables but that doesn't seem to be the right use.
And here is the update statement i was trying.
UPDATE USERS
SET LASTLOGINDATE = SYSDATE
WHERE USERID = 5;
Some issues:
The select you do in the trigger sets the variable isactive_check to a payment date, and vice versa. There is an accidental switch there, which will have a negative effect on the next if;
The same select should return exactly one record, which by the looks of it is not guaranteed, since you join with table userpayments, which may have several payments for the selected user that meet the condition, or none at all. Change that select to do an aggregation.
If a user has more than one payment record, the condition might be true for one, but not for another. So if you are interested only in users who have not paid in a long while, such user should not be included, even though they have an old payment record. Instead you should check whether all records meet the condition. This you can do with a having clause.
As the table users is mutating (the update trigger is on that table), you cannot perform every action on that same table, as it would otherwise lead to a kind of deadlock. This means you need to rethink what the purpose is of the trigger. As this is about an update for a specific user, you actually don't need to check the whole table, but only the record that is being changed. For that you can use the special new variable.
I would suggest this SQL instead:
SELECT MAX(UP.PAYMENTDATE)
INTO PAYMENTDATE_CHECK
FROM USERPAYMENTS
WHERE USERID = :NEW.USERID
and then continue with the checks:
IF :NEW.ISACTIVE = 1 AND PAYMENTDATE_CHECK < TRUNC(SYSDATE-60) THEN
:NEW.ISACTIVE := 0;
INSERT INTO DEACTIVATEDUSERS (USER_ID, DEACTIVATION_DATE)
VALUES(USER_CHECK,SYSDATE);
END IF;
Now you have avoided to do anything in the table users and have made the checks and modification via the :new "record".
Also, it is good practice to mention the column names in an insert statement, which I have done in above code (adapt column names as needed):
Make sure the trigger is compiled and produces no compilation errors.

Postgresql: run trigger AFTER update FOR EACH STATEMENT ONLY if data changed

In Postgresql I can have two kinds of triggers: FOR EACH ROW and FOR EACH STATEMENT. If I do a FOR EACH ROW trigger, I can add a WHERE clause something like OLD.* != NEW.* so it only fires if something has actually changed. Is there any way to do something similar with STATEMENT level triggers? I know I can't do the same thing since OLD and NEW aren't available, but I was thinking perhaps there might be a way to check the number of rows changed from within my function itself or the like.
Usage case: I am using the postgresql NOTIFY system to notify my app when data changes. Ideally, the app would get a single notification each time one or more records changes, and not get notified at all if data stays the same (even if an UPDATE was run). With a basic AFTER UPDATE FOR EACH STATEMENT trigger, I am getting notified every time an update statement runs - even if it doesn't actually change anything.
You should create two triggers: before update for each row and after update for each statement.
The first trigger checks if the table is being updated and sets a flag if so.
The second trigger checks the flag and performs notify if it was set.
You can use a custom configuration parameter as the flag (e.g. flags.the_table).
The solution is simple and safe, as the parameter is local in the current session.
create or replace function before_each_row_on_the_table()
returns trigger language plpgsql
as $$
begin
if new <> old then
set flags.the_table to 'on';
end if;
return new;
end $$;
create or replace function after_each_statement_on_the_table()
returns trigger language plpgsql
as $$
begin
if current_setting('flags.the_table', true) = 'on' then
notify your_channel, 'the_table was updated';
set flags.the_table to 'off';
end if;
return null;
end $$;
create trigger before_each_row_on_the_table
before update on the_table
for each row execute procedure before_each_row_on_the_table();
create trigger after_each_statement_on_the_table
after update on the_table
for each statement execute procedure after_each_statement_on_the_table();
The function current_setting() with two arguments is available in Postgres 9.6 or later.