Oracle database - determine what table triggered a trigger - sql

I am working with Oracle Express 12c. One of the tables I created has an associated trigger which prevents it from directly updating one of its columns. But it fires even when another table, which should have this kind of access, tries to do it.
For example:
I have tables A and B, and B has a foreign key that links it to A. I purposefully added one of the attributes from A to B. One trigger, let's call it UPD_FROM_B, prevents B from updating this attribute. Another, UPD_FROM_A, should update this attribute on B if it is updated on A. Now UPD_FROM_B prevents UPD_FROM_A from doing what it is supposed to.
Or through a working example:
There are two tables: customer and order. Customer can have multiple orders, but one order has only one customer. For the sake of the project, I had to put customer_name on the order, even though every order has customer_id as foreign key.
One trigger - UPD_NAME_ORDER prevents order from updating the
customer_name, and the other - UPD_NAME_CUST updates this column in the appropriate row of the order table whenever customer_name is updated in customer
How can I determine which table triggered the action and allow UPDATE for one, but still prevent it from the other?

I think you must change your trigger UPD_FROM_B only.
Firstly you select value of column from table A when parent key and foreign key are equal, then compare this value to value of column from table B. If this value equal your trigger allow to do this updating, else not. You write this code as follow:
CREATE TRIGGER UPD_FROM_B before update on B
DECLARE
val A.upd_column%TYPE;
BEGIN
select A.upd_column into val
where A.ID=B.FKID
if val=B.upd_column then
RAISE;
else ......
end if;
END;

At face value, the way I know to do this is to use a package variable as a gate key and share it between the 2 triggers. Trigger A will set the state variable before its nested update of B. Trigger B will check if A set the var, if so, the update succeeds, if not, then B knows A is not the caller, and it should block the update.
Also, I assume your intention is to implement an "UPDATE CASCADE" trigger to update the child record foreign key values based on the parent update, preserving the relationship while updating the FK value. If so, you have to be careful with this approach, it will only work correctly if you disallow multi-row updates.
First a package and state var:
CREATE PACKAGE IsUpdating IS
A number;
END;
At top of trigger A do something like below. The exception handler is a "finally" block that always executes to avoid leaving the package variable in an invalid state in case of an error on the update:
CREATE TRIGGER A_UPD_CASCADE after update on A for each row
BEGIN
IsUpdating.A := 1;
update B set B.FKID = :new.FKID WHERE B.FKID = :old.FKID;
IsUpdating.A := 0;
EXCEPTION
WHEN OTHERS
THEN
IsUpdating.A := 0;
RAISE;
END;
Inside trigger B do this:
CREATE TRIGGER B_UPD_CASCADE before update on B
BEGIN
if IsUpdating.A != 1 then
-- Disallow update since it is coming from B alone
RAISE;
end if;
END;
The pitfall with CASCADE UPDATE is with multi-row parent updates in a single statement, Oracle will execute the trigger for each parent value, causing some child values to update multiple times based on chained before and after values.

Related

PostgreSQL, add row to table when a row is created in another table

I am trying to create a trigger function, to create a new row, in a table, when a value is modified or created in another table. But the problem is that I need to insert in the other table, the primary key that provoked the trigger function.
Is there a way to do it?
Basically, when an insert or update will be done in table 1, I want to see in table 2 a new row, with one field filed with the value of the primary key of the row in table1 that provoked the trigger.
begin
INSERT INTO resultados_infocorp(id_user, Procesado)
VALUES (<PRIMARY_KEY>,false)
RETURN NEW;
End;
This is because if Procesado is false, thank to the id_user I will make some validations, but the ID of the user is necesary and I cant do it from the backend of my project, because I have many db inputs.
PD: The primary key of the new table is a sequence, this is the reason why I am not passing this arg.
CREATE TRIGGER resultados_infocorp_actualizar
AFTER INSERT OR UPDATE OF id_user, fb_id, numdocumento, numtelefono, tipolicencia, trabajoaplicativo
ON public.usuarios
FOR EACH ROW
EXECUTE PROCEDURE public.update_solicitudes_infocorp();
You have not shown the trigger definition. Still if you want the PK value then something like:
INSERT INTO resultados_infocorp(id_user, Procesado)
VALUES (NEW.pk_fld,false)
Where pk_fld is the name of your PK field. Take a look here:
https://www.postgresql.org/docs/current/plpgsql-trigger.html
for what is available to a trigger function. For the purpose of this question the important part is:
NEW
Data type RECORD; variable holding the new database row for INSERT/UPDATE operations in row-level triggers. This variable is null in statement-level triggers and for DELETE operations.

Is it possible to change a delete to an update using triggers?

Is there any way in Firebird to execute an UPDATE instead a DELETE through a trigger?
This is possible in Microsoft SQL Server by declaring the triggers as "INSTEAD".
The problem is that we have an application that uses a Firebird database and we want to prevent the deletes of records and mark then as "deleted" (a new field), but without showing any error to the user, and "cheating" the app.
You cannot do this with tables, but you can do it with views. Views can have triggers on insert, update and delete that modify the underlying table(s). See also Updatable Views in the Firebird 2.5 Language Reference.
In short, create a table for the data, add a view, add triggers that insert/update/delete through the view to the underlying table. Users can then use the view as if it is a table.
An example
I'm using Firebird 3, but this will work with minor modifications in Firebird 2.5 and earlier.
A table example_base:
create table example_base (
id bigint generated by default as identity constraint pk_example_base primary key,
value1 varchar(100),
deleted boolean not null default false
)
A view example:
create view example (id, value1)
as
select id, value1
from example_base
where not deleted;
Do not create the view with with check option, as this will disallow inserts as the absence of the deleted column in the view will prevent Firebird from checking the invariant.
Then add an insert trigger:
create trigger tr_example_insert before insert on example
as
begin
if (new.id is not null) then
-- Don't use identity
insert into example_base(id, value1) values (new.id, new.value1);
else
-- Use identity
-- mapping generated id to new context
-- this way it is available for clients using insert .. returning
insert into example_base(value1) values (new.value1)
returning id into :new.id;
end
The above trigger ensures the 'by default as identity' primary key of the underlying table is preserved, and allows insert into example .. returning to report on the generated id.
An update trigger
create trigger tr_example_update before update on example
as
begin
-- Consider ignoring modification of the id (or raise an exception)
update example_base
set id = new.id, value1 = new.value1
where id = old.id;
end
The above trigger allows modification of the primary key; you may want to consider just ignoring such a modification or even raising an exception.
And finally a delete trigger:
create trigger tr_example_delete before delete on example
as
begin
update example_base
set deleted = true
where id = old.id;
end
This trigger will mark the record in the base table as deleted.
To use this, just grant your users select, insert and update privileges to the view (and not the table).
The only caveat I'm aware of is that defining foreign keys will need to point to example_base, not to example, and the behavior of foreign keys will be slightly off. The record in the base table will continue to exist, so the foreign key will not block deletion. If that is something that is necessary, you will need to emulate constraint behavior (which could be tricky).
YES! It can be made on VIEWs.
That's the way I solved it.
If a View has a trigger, then the trigger is the responsible of making the real update or delete on the underlying table.... So... a DELETE trigger that makes an UPDATE to the table solved my problem.

SQL Server trigger thinks there's a duplicate in the table when there isn't

I'm a new SQL developer. After recommendations I have altered my trigger (for this task I need to use a trigger so can't avoid it), but I have re-altered my trigger. I want it to prevent a duplication in the Rentals table of the BikeID foreign key contained within it.
This is my code at the moment:
CREATE TRIGGER BikeNotAvailable
ON dbo.SA_Rental
AFTER INSERT
AS
IF EXISTS (SELECT *
FROM SA_Rental
INNER JOIN inserted i ON i.BikeID = dbo.SA_Rental.BikeID)
BEGIN
ROLLBACK
RAISERROR ('This bike is already being hired', 16, 1);
END
go
But when I enter the BikeID in the Rentals table, even though the BikeID is not present inside a row yet, it still raises the error - why? (I have also tested this on an empty table and it still raises the error)
Just some context on my data, the BikeID is a primary key from the 'Bike' table that is shared as a foreign key to the Rentals table, not sure if this has anything to do with the error.
Can someone please help me fix this trigger so it works.
Thanks.
Well, as it's an AFTER trigger, the trigger is run after the new record is added to the table (at least visible for your trigger).
Supposing that your table has an automatically generated ID column, you should exclude the inserted row from your check like this:
CREATE TRIGGER BikeNotAvailable ON dbo.SA_Rental
AFTER INSERT
AS
if exists ( select * from SA_Rental
inner join inserted i on i.BikeID=dbo.SA_Rental.BikeID
where SA_Rental.RentalID <> i.RentalID)
begin
rollback
RAISERROR ('This bike is already being hired', 16, 1);
end
go
A far simpler way to achieve what you are after is to create a unique index:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID);
This, of course, assumes that you delete the row from your table when the bike is no longer rented (as this is the implied logic in your post). If this is not the case, then we need more detail; what specifies on your table that the rental has completed?
If we assume you have a return date, and the return date is NULL when the bike is yet to be returned, then you would use a filtered index like so:
CREATE UNIQUE INDEX BikeRented ON SA_Rental (BikeID)
WHERE ReturnedDate IS NULL;

Interbase SQL trigger

Interbase, Sql, trigger. Can't really understand how to write a trigger of this kind: I have several tables. Each one has a document type, status and an unique number. One table in which i wish to create a trigger is a table that holds a file i post, a status of posting, a doctype corresponding to a table and a unique number linking to a record in the corresponding table. I want to change document status in the corresponding table based on the unique number to a certain status depending on the post result (status) that i change after posting. How can I do it?
Trigger T1 will be executed after update on TABLE1.
Trigger checks if TABLE1.StatusOfPosting changed to some value and depending on result updates TABLE2.DocumentStatus.
Depending on bussienes logic, maybe you will need and BEFORE DELETE trigger.
CREATE TRIGGER T1 FOR TABLE1 AFTER UPDATE POSITION 0
AS
BEGIN
IF (NEW.StatusOfPosting <> OLD.StatusOfPosting and NEW.StatusOfPosting=1) THEN
UPDATE TABLE2
SET TABLE2.DocumentStatus=1
WHERE TABLE2.UniqueNumber = TABLE1.UniqueNumber;
END

SQL delete orphan

Assuming that all foreign keys have the appropriate constraint, is there a simple SQL statement to delete rows not referenced anywhere in the DB?
Something as simple as delete from the_table that simply skip any rows with child record?
I'm trying to avoid manually looping through the table or adding something like where the_SK not in (a,b,c,d).
You might be able to use the extended DELETE statement in 10g that includes error logging.
First use DBMS_ERRLOG to create a logging table (which is just a copy of the original table with some additional prefixing columns: ORA_ERR_MESG$, ..., ORA_ERR_TAG$)
execute dbms_errlog.create_error_log('parent', 'parent_errlog');
Now, you can use the LOG ERRORS clause of the delete statement to capture all rows that have existing integrity constraints:
delete from parent
log errors into parent_errlog ('holding-breath')
reject limit unlimited;
In this case the "holding-breath" comment will go into the ORA_ERR_TAG$ column.
You can read the full documentation here.
If the parent table is huge and you're only looking to delete a few stray rows, you'll end up with a parent_errlog table that is essentially a duplicate of your parent table. If this isn't ok, you'll have to do it the long way:
Directly reference the child tables (following Tony's solution), or,
Loop through the table in PL/SQL and catch any exceptions (following Confusion's and Bob's solutions).
The easiest way may be to write an application or stored procedure that attempts to delete the rows in the table one-by-one and simply ignores the failures due to foreign key constraints. Afterwards, all rows not under a foreign key constraint should be removed. Depending on the required/possible performance, this may be an option.
No. Obviously you can do this (but I realise you would rather not):
delete parent
where not exists (select null from child1 where child1.parent_id = parent.parent_id)
and not exists (select null from child2 where child2.parent_id = parent.parent_id)
...
and not exists (select null from childn where childn.parent_id = parent.parent_id);
One way to do this is to write something like the following:
eForeign_key_violation EXCEPTION;
PRAGMA EXCEPTION_INIT(eForeign_key_violation, -2292);
FOR aRow IN (SELECT primary_key_field FROM A_TABLE) LOOP
BEGIN
DELETE FROM A_TABLE A
WHERE A.PRIMARY_KEY_FIELD = aRow.PRIMARY_KEY_FIELD;
EXCEPTION
WHEN eForeign_key_violation THEN
NULL; -- ignore the error
END;
END LOOP;
If a child row exists the DELETE will fail and no rows will be deleted, and you can proceed to your next key.
Note that if your table is large this may take quite a while.