PostgreSQL Master Details Transaction Handling using Npgsql - sql

Supposing I Have these tables:
CREATE TABLE master
master_id serial,
master_desc character varying
CREATE TABLE details
details_masterrefid int,
details_desc character varying
CONSTRAINT master_detail_fkey(details_masterrefid)
REFERENCES master(master_id) MATCH SIMPLE ON UPDATE NO ACTION ON DELETE NO ACTION
Then I have a code in C# which basically will insert the header first to the master table and then loop through the details and insert it in the details table.
By doing this, I have to insert to the master table first then commit the transaction, if its successful, get the ID and use it to insert in the details table. Now, the problem is, if something went wrong in the details and the insertion is not successful, I want to rollback the data inserted on the master table. But since its already committed, I cant roll it back. It should be all or nothing. Both the master table and details table.
The only solution I can think of is by allowing the foreign key field in the details to be nullable then if everything is successful, update the foreign key field to its respective value. Any suggestion on how do it better? I dont know if its an efficient solution.

You could use single statement CTE:
WITH ins1 AS (
INSERT INTO master (cols...)
VALUES (..) -- insert into master
RETURNING master_id -- get generated id
)
INSERT INTO details (...)
SELECT master_id, ... -- insert into child table
FROM ins1;
Rextester Demo
Another method is to wrap everything with single transaction:
BEGIN TRAN
INSERT INTO master ...
INSERT INTO child ...
COMMIT;
That way you will get all-or-nothing behaviour.

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.

Using Trigger to insert record in another table, AND write back the ID into original record ..?

I have a simple trigger setup, which is used to insert records into a RentJournal table, whenever there is a record inserted in the UnitAGA table.
The RentJournal table has a primary key ID column named RentJournalID, which is auto incrementing. The UnitAGA table also has a nullable foreign key column named RentJournalID, which links each UnitAGA entry, to its corresponding entry in RentJournal table (which is inserted through the Trigger below).
Problem is that currently this Trigger is only inserting values into RentJournal table. But now I want to also fetch the ID assigned for each RentJournal entry through this Trigger, and write that into the corresponding UnitAGA record, whose insert actually triggered the Trigger in the first place. How do I do this ?
The Trigger code as of right now is this:
USE [RentDB]
GO
ALTER TRIGGER [RTS].[InsertRentJournalEntry]
ON [RTS].[UnitAGA]
AFTER INSERT
AS
BEGIN
INSERT INTO RTS.RentJournal
(UnitId, AdjustmentType, EffectiveDate, ReferenceFormNo)
SELECT
UnitId, 'AGA', EffectiveDate, ReferenceFormNo FROM inserted
END
Have a look at the INSERT logical table that is available in insert triggers:
DML triggers use the deleted and inserted logical (conceptual) tables. They are structurally similar to the table on which the trigger is defined, that is, the table on which the user action is tried. The deleted and inserted tables hold the old values or new values of the rows that may be changed by the user action. For example, to retrieve all values in the deleted table, use: SELECT * FROM deleted
http://technet.microsoft.com/en-us/library/ms189799.aspx
Then use ##IDENTITY to get the value of the identity column on your RentJournal table.
So you should be able to do something like:
update INSERTED set RentJournalID = ##IDENTITY

Firebird - using before insert trigger for a master table to sum detail records

I have two tables a master and detail. the detail table records are created automatically using a trigger after insert new master record.
But I need to create Before Insert or Update trigger (call it T1) for the master table to do some calculations based on fields from master record and sum from its detail records.
My problem to be able to do my calculations in T1 I need to insert details records first but of course the detail records has foreign key constraint to the master table ID which prevent this action so what do you think the best approach to achieve this task ?
I think the best approach is to use a stored procedure that does all the work...
something like this:
create procedure insert_record(id integer, ...);
as
begin
/* this inserts master and through triggers creates detail */
insert into master (id, ... )
values (:id, ...);
/* calculate values */
select sum(...) from detail
where id = :id
into :calculation;
/* usa calculated value to update master table */
update master
set calculated_value = :calculation
here id = :id;
end

insert data in multiple tables

hi i have a problem to insert data in multiple tables. i have define primary key & reference key in tables now i want to insert data in both tables in single query.......how can i do this...........???????
Your question isn't exactly clear on what the particular problem is. I can see three possibilities:
1. You want to insert into two tables wiht a single INSERT statement
2. You want to do two inserts, but without anything else being able to 'get in the middle'
3. You want to insert into one table, then get the primary key to insert into the second table
The answer to 1. is simple:
You can't.
The answer to 2. is simple too:
BEGIN TRANSACTION
INSERT INTO <table1> (a,b,c) VALUES (1,2,3)
INSERT INTO <table2> (a,b,c) VALUES (1,2,3)
COMMIT TRANSACTION
The answer to 3. is has several possibilities. Each depending on exactly what you want to do. Most likely you want to use SCOPE_IDENTITY() but you may also want to look up ##identity and IDENT_CURRENT() to understand the various different options and complexities.
BEGIN TRANSACTION
INSERT INTO <dimension_table> (name)
VALUES ('my new item')
INSERT INTO <fact_table> (item_id, iteam_value)
VALUES (SCOPE_IDENTITY(), 1)
COMMIT TRANSACTION
This is what transactions are meant for. Standard SQL does not permit a single statement inserting into multiple tables at once. The correct way to do it is:
-- begin transaction
insert into table 1 ...
insert into table 2 ...
commit
Does your language support the INSERT ALL construct? If so, that is the best way to do this. In fact it's the only way. I posted an example of this construct in another SO thread (that example syntax comes from Oracle SQL).
The other option is to build a transactional stored procedure which inserts a record into the primary key table followed by a record into the referencing table.
And 1 of your choice to do that is use ORM (like Hibernate, NHibernate) the you make your object and set other relation to it and finally just save the main object , like:
A a;
B b;
C c;
a.set(b);
a.set(c);
DAO.saveOrUpdate(a);
you must notice your DAO.saveOrUpdate(a); line of code just work with hibernate but it insert data into 3 table A, B, C.