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

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.

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.

Oracle database - determine what table triggered a trigger

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.

Immutable SQL columns

Is it possible to mark a column immutable in MSSQL?
Seems like it would be a useful DDL feature; once a value is set in a row ('row' being defined as a specific relation of values to a primary key), it could not be changed without deletion of the row.
Obviously (like most things) this is more than doable in the application layer, but half the fun of SQL DDL is error-checking your application code.
If the user doing the DML is not the owner of the objects and not "db_owner" in the database itself, you can just grant "insert" privilege, but not update privilege for that table:
Assuming a table with id, col1, col2
grant insert, select, delete on the_table to the_user;
grant update (id, col2) on the_table to the_user;
With these grants the_user can insert rows and supply values for all three columns. He can also update the id and the col2 column, but not the col1 column.
The db_owner (and possibly the creator/owner of the table) can always update all columns. I don't know if there is a way to revoke that privilege from those rolws.
It's possible, using an UPDATE TRIGGER like this:
CREATE TRIGGER trgAfterUpdateAsset ON dbo.Asset
FOR UPDATE AS
IF UPDATE(AssetTypeID) AND EXISTS (SELECT * FROM inserted i JOIN deleted d ON i.ID = d.ID WHERE i.AssetTypeID <> d.AssetTypeID)
BEGIN
RAISERROR ('AssetTypeID cannot change.', 16, 1);
ROLLBACK TRAN
END
(Note: The table has a Primary Key column, called ID).
I'm only rejecting the update if the value of AssetTypeID changes. So the column could be present in an update, and if it specified the old value, than it would pass through. (I needed this way)
No, there is no such feature in SQL Server.
The closest I can think about is an update trigger on the table that checks if the values in the specific column are the same for the INSERTED and DELETED logical tables and rejects the updates for the changed rows.
To my knowledge, this is not possible with DDL. However, you could implement BEFORE UPDATE triggers to meet your requirement. In the BEFORE UPDATE trigger, you could raise an exception or do whatever you want rather than update the row.
Another approach is to deny update rights to the table and create a stored procedure (which users do have the right to execute) that does not update the immutable field.

INSTEAD OF UPDATE TRIGGER for a table with foreign key

I get this error:
Cannot create INSTEAD OF DELETE or INSTEAD OF UPDATE TRIGGER 'trig_Income_Updater' on table 'MYBUDGET.tbl_Income'. This is because the table has a FOREIGN KEY with cascading DELETE or UPDATE.
I can use 'FOR UPDATE'. but how to make it to ignore the original update ?
To ignore the origional update you will need to utilize a RAISERROR and then ROLLBACK.
Here is an example under the "Using a DML AFTER trigger to enforce a business rule between the PurchaseOrderHeader and Vendor tables" section
Just update the fields you want and then "undo" the original update using the "inserted" and "deleted" temporary tables that are provided to the trigger.
For example (untested):
--Do the stuff you want
UPDATE table SET fields = values WHERE some condition
--Undo the original update (minus anything you WANT changed above)
UPDATE table SET unchangingfield = deleted.unchangingfield WHERE ID = deleted.ID
The "inserted" table will contain the new values, and the "deleted" table contains the values that are being changed. You can join, query and otherwise treat them as though they were actual tables.

Need some help with Sql Server and a simple Trigger

I wish to make a trigger but i'm not sure how to grab the data for whatever caused the trigger.
I have a simlpe table.
FooId INT PK NOT NULL IDENTITY
Name VARCHAR(100) NOT NULL
I wish to have a trigger so that when an UPDATE, INSERT or DELETE occurs, i then do the following.
Pseduocode
IF INSERT
Print 'Insert' & Name
ELSE IF UPDATE
Print 'Update' & FooId & Name
ELSE IF DELETE
Print 'Delete' & FooId & Name
Now, I know how to make a trigger for a table.
What i don't know how to do is figure out the values based on what the trigger type is.
Can anyone help?
Edit: Not sure if it helps, but db is Sql Server 2008
the pseudo table "inserted" contains the new data, and "deleted" table contains the old data.
You can do something like
create trigger mytrigger on mytable for insert, update, delete
as
if ( select count(*) from inserted ) > 0
-- insert or update
select FooId, Name from inserted
else
-- delete
select FooId, Name from deleted
To clarify all the comments made by others, on an insert, the inserted table contains data and deleted is empty. On a delete, the situation is reversed. On an update, deleted and inserted contain the "before" and "after" copy of any updated rows.
When you are writing a trigger, you have to account for the fact that your trigger may be called by a statement that effects more than one row at a time.
As others have pointed out, you reference the inserted table to get the values of new values of updated or inserted rows, and you reference the deleted table to get the value of deleted rows.
SQL triggers provide an implicitly-defined table called "inserted" which returns the affected rows, allowing you to do things like
UPDATE mytable SET mytimestamp = GETDATE() WHERE id IN (SELECT id FROM inserted)
Regarding your code sample, you'll want to create separate INSERT, UPDATE and DELETE triggers if you are performing separate actions for each.
(At least, this is the case in SQL Server... you didn't specify a platform.)
On 2008, there is also MERGE command. How do you want to handle it?
Starting from 2008, there are four commands you can modify a table with:
INSERT, UPDATE, DELETE, and MERGE:
http://blogs.conchango.com/davidportas/archive/2007/11/14/SQL-Server-2008-MERGE.aspx
http://sqlblogcasts.com/blogs/grumpyolddba/archive/2009/03/11/reasons-to-move-to-sql-2008-merge.aspx
What do you want your trigger to do when someone issues a MERGE command against your table?