Multiple SQL transaction in one with unique key - sql

I need to revert 3 SQL inserts, but since I need the key generated in the first insert, I don't not know how to do the transaction.
Example:
Begin Transaction
Insert to TABLE A - get the ID generated
Insert to Table B the ID from Table A to FK
Insert to Table C
Commit transaction.
doing in Node js:
const rs = await pool
.request()
.query(`BEGIN TRANSACTION;Insert....
but I need to run in 3 requests. Is this possible?

Related

Trigger: How does the inserted table work? How to access its rows?

I have the following table
Data --Table name
ID -- Identity column
PCode -- Postal Code
I created the following trigger:
CREATE TRIGGER Trig
ON Data
FOR INSERT
AS
BEGIN
Select * from inserted
END
And inserted the following values
INSERT INTO Data VALUES (125)
INSERT INTO Data VALUES (126)
INSERT INTO Data VALUES (127)
It shows this:
But I was expecting something like this:
After the 1st insertion, the trigger is executed -> one row is shown in the inserted table.
After the 2nd insertion, the trigger is executed -> two rows are shown in the inserted table.
After the 3rd insertion, the trigger is executed -> three rows are shown in the inserted table.
According to msdn.microsoft all the rows inserted are in this table.
How can I access the inserted table so that I can see all the expected rows and not separately?
You can not. From the Use the inserted and deleted Tables article on microsoft.com, you can read:
The inserted table stores copies of the affected rows during INSERT and UPDATE statements.
That means that the inserted table will only contain rows for the current INSERT or UPDATE statement.
If you do want to see all rows for several such INSERT or UPDATE statements, you will have to store these rows in a table you created yourself.
There are 2 table available in a trigger, the inserted and the deleted. Each update on table XXX is actually a delete row X from XXX then an insert of row X in table XXX. So the inserted inside the trigger is a copy of what got inserted. You can do a lot with a trigger, but triggers are dangerous.
For example, on a performance gig, I found a huge SP being run by a trigger, we dropped it and the database came back online. Or another example, if you do a trigger wrong to audit logins, you can down the server.
As TT mentioned, if you want to see all the inserted records then you need to change your Trigger to something like this:
CREATE TRIGGER Trig
ON Data
FOR INSERT
AS
BEGIN
Select * into "tablename"
from
(Select * from inserted) Ins
END

Records in deleted table that are not in the delete statement?

We have a large number of databases with the same schema, which each have a table with triggers to sync records with another table in a central database. When the table is updated, inserted into, or deleted from, the table in the central database also has a record updated, inserted, or deleted.
We've been having records mysteriously disappear from the table in the central database. When researching the problem I found that when the insert/delete trigger fires there are records in the deleted table that are not from the current delete statement. They aren't even records in the same database. They look like the old values record for update statements on the same table in another database.
All the information I could find says records in the deleted table should be from the statement that caused the trigger to fire.
Can anyone explain why I'm seeing this behavior instead?
EDIT: This is what the insert/delete trigger looks like:
DECLARE #TenantID INT
SELECT #TenantID = ID FROM [CentralDB]..Tenants WHERE db = DB_Name()
INSERT INTO [CentralDB].[dbo].[TenantUsers]
(..snipped list of columns...)
SELECT
...snipped list of columns...
FROM inserted
WHERE UserNameID NOT IN (0,6)
DELETE FROM [CentralDB]..TenantUsers WHERE UserNameID in
(SELECT UserNameID FROM DELETED WHERE UserNameID NOT IN (0,1,6))
And the update trigger:
DECLARE #TenantID INT
SELECT #TenantID = ID FROM [CentralDB]..Tenants WHERE db = DB_Name()
UPDATE [CentralDB].[dbo].[TenantUsers]
SET ...snipped list of columns...
FROM INSERTED i
WHERE i.UserNameID = TenantUsers.UserNameID
AND i.UserNameID NOT IN (0,6)
You've probably done this but if records are being deleted which ought not to be then i'd go round the db's (or write a script too) and check the triggers which contain the delete statements only fire for inserts and deletes.. Maybe there is a rouge trigger which fires on update and executes the delete command?
Its a long shot..
Other than this i would check there are no other triggers in the chain which can delete from the central db table.
there appear to be no obvious issues with the trigger design

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

SQL Query Execution more than once?

I have 2 tables (A,B), and 1 query
My query is something like this
Read From A
Update B with this data from A
Using the updated table B, set final value of A.
Example execution can be find in below question:
Proper way to keep a single data in sql server?
Now since all the process is connected, this query should not be executed twice at the same time, or by 2 different users until the process ends. How do I prevent this ? Or does it already work securely like this?
Use transaction lock :
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
GO
BEGIN TRANSACTION
--select * from A
-- update B ....
--update A
WAITFOR DELAY '00:00:02' -- tables remain locked for 2 secs hh:mm:ss
commit TRANSACTION
during the transaction execution, any try to read or write from/to tables will timeout...
EDIT :
u must use, some lock to lock the db while updating. http://msdn.microsoft.com/en-us/library/ms173763.aspx
psedo code for u:
int x=(select val from tableB)+1
query="update tableB set tableB.field="+x+"where......."
if query executed successfully:
update tableA
I hope that your table A and B must be having some Primary Key eg EmployeeID. In such case a simple solution is to create a table (say Lock_Table) which keeps a record of the EmployeeID beign modified.
So here you would need to go like this:
BEGIN TRANSACTION
1- Read EmployeeID From A
2- Check if EmployeeID already exists in Lock_Table. If Yes then Quit Else insert that EmployeeID in Lock_Table
3- Update B with this data(EmployeeID in this case) from A
4- Using the updated table B, set final value of A.
5- Delete this EmployeeID from the Lock_Table
COMMIT TRANSACTION
On any error ROLLBACK the Transaction.
Hope it helps.

what is the correct syntax for creating a database trigger for insert, modify and delete

i have what seems like a basic scenario for a db trigger in SQL server and i am running into an issue.
i have table Users (id, name, phone, etc) and i have tables UsersHistory (id, user_id action, fields, timestamp)
i want a database trigger where anytime inserts, updates or deletes into Users, i want a new record created in UsersHistory with the user id and the action that was done (insert new, updated fields, deleted id. Basically an audit log table.
this is how far i got, but i can't figure out how to:
Get the id on modify and deletes and also
How to get a list of fields that have changed and the action that was committed (insert, delete, update)
CREATE TRIGGER Update_Users_History
ON Users
AFTER INSERT,DELETE,UPDATE
AS
BEGIN
-- Insert statements for trigger here
insert into UsersHistory (user_id, [action], [fields], timestamp)
select max(id) as user_id, {action ??},{fields??} getdate() from Users)
END
GO
any suggestions?
The easiest might be to just simply create three triggers - one for each operation:
CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
INSERT INTO dbo.UserHistory............
END
CREATE TRIGGER trgUserDelete
ON dbo.User AFTER DELETE
AS BEGIN
INSERT INTO dbo.UserHistory............
END
CREATE TRIGGER trgUserUpdate
ON dbo.User AFTER UPDATE
AS BEGIN
INSERT INTO dbo.UserHistory............
END
That way, things are simple and you easily understand what you're doing, plus it gives you the ability to turn off a trigger for a single operation, if you e.g. need to insert or delete a huge list of items.
Inside the trigger, you have two "pseudo-tables" - Inserted (for INSERT and UPDATE) and Deleted (for UPDATE and DELETE). These pseudo tables contain the values for the newly inserted values (or the updated ones in UPDATE), or the ones that were deleted (for DELETE) or have been updated (the old values, before the update, for the UPDATE operation).
You need to be aware that a trigger will be called once even if you update a huge number of rows, e.g. Inserted and Deleted will typically contain multiple rows.
As a sample, you could write a "AFTER INSERT" trigger like this (just guessing what your table structure might be....):
CREATE TRIGGER trgUserInsert
ON dbo.User AFTER INSERT
AS BEGIN
INSERT INTO
dbo.UserHistory(UserID, Action, DateTimeStamp, AuditMessage)
SELECT
i.UserID, 'INSERT', getdate(), 'User inserted into table'
FROM
Inserted i
END
You are looking for a way to find out which "action" this trigger caused? I don't see any way to do this - another reason to keep the three trigger separate. The only way to find this out would be to count the rows in the Inserted and Updated tables:
if both counts are larger than zero, it's an UPDATE
if the Inserted table has rows, but the Deleted does not, it's an INSERT
if the Inserted table has no rows, but the Deleted does, it's a DELETE
You're also looking for a "list of fields that were updated" - again, you won't have any simple solution, really. You could either just loop through the fields in the "Users" table that are of interest, and check
IF UPDATE(fieldname) ......
but that gets a bit tedious.
Or you could use the COLUMNS_UPDATED() function - this however doesn't give you a nice list of column names, but a VARBINARY in which each column is basically one bit, and if it's turned on, that column was updated. Not very easy to use.....
If you really want to create a single, big trigger, this could serve as a basis - it detects what operation has caused the trigger to fire, and will insert entries into your User_History table:
CREATE TRIGGER trgUser_Universal
ON dbo.Users
AFTER INSERT, UPDATE, DELETE
AS BEGIN
DECLARE #InsHasRows BIT = 0
DECLARE #DelHasRows BIT = 0
IF EXISTS(SELECT TOP 1 * FROM INSERTED)
SET #InsHasRows = 1
IF EXISTS(SELECT TOP 1 * FROM DELETED)
SET #DelHasRows = 1
DECLARE #TriggerAction VARCHAR(20)
IF #InsHasRows = 1 AND #DelHasRows = 1
SET #TriggerAction = 'UPDATE'
ELSE
IF #InsHasRows = 1
SET #TriggerAction = 'INSERT'
ELSE
SET #TriggerAction = 'DELETE'
IF #InsHasRows = 1
INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
SELECT i.UserId, #TriggerAction, null, getdate()
FROM INSERTED i
ELSE
INSERT INTO dbo.UsersHistory(user_id, [action], [fields], timestamp)
SELECT d.UserId, #TriggerAction, null, getdate()
FROM DELETED d
END
I haven't included the figuring out which fields have been updated part just yet - that's left as an exercise to the reader :-)
Does that help at all?
There are two "tables" that are used in the trigger. One is DELETED and one is INSERTED. When you delete a row, that row is captured in the DELETED table. When you insert a row, that row is captured in the INSERTED table. When you update a row, the old row is in the DELETED table, and the new row is in the INSERTED table. The DELETED and INSERTED tables have the same schema as the table on which you are adding the trigger.
You might check out this solution that will create a query for you that will make all the auditing triggers you want, as well as the table in which to store the audits, excluding any selected tables. It will only do UPDATE triggers, but could easily be modified to make INSERT and DELETE triggers as well.