Can SQL server instead of update force insert a new row - sql

Is it possible in SQL Server 2008+ to force an UPDATE statement on table to be transformed into INSERT statement, thus creating new row with the old and updated columns?

Yes this is a typical scenario for an INSTEAD OF UPDATE TRIGGER.
Create the following trigger on your table and it will insert a row for each update made on your table. you can have a bit more logic inside your trigger but this is just a basic definition just to give you some idea.
Inside your INSTEAD of UPDATE trigger you will have access to two system tables Inserted and deleted.
Inserted table will hold new values for the row that was being updated by the Update statement.
Deleted table will hold Old values for the row that was being updated by the Update statement.
Demo Trigger
CREATE TRIGGER tr_Table_Instead_Of_Update
ON TABLE_NAME
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO TABLE_NAME(Col1, Col2 , Col3)
SELECT Col1, Col2 , Col3
FROM inserted
END

Related

Trigger works based on type of insert SQL Server

I am working in SQL Server Management Studio v18, and I have the following trigger:
CREATE TRIGGER [dbo].[update_surface]
ON [dbo].[my_table]
FOR INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #surface_m2 REAL
SET #surface_m2 = (SELECT cast(round(CAST(Dimension1*Dimension2 as decimal)/ cast(1000000 as decimal),3,3) as decimal(10,3)) AS surface FROM my_table WHERE Surface_m2 IS NULL)
UPDATE dbo.my_table SET Surface_m2 = #surface_m2
END
I have two columns in my_table, which are Dimesion1 and Dimension2. I want that the trigger multiplies them, and set the result to other column in the same table, which is Surface_m2, whenever this column is null. The trigger does his function, but based on the type of insert I do:
If I insert a row in my_table by the graphic environment the trigger works as I wish. With each new row, Surface_m2 has his own result.
But if I insert by INSERT INTO my_table VALUES ().... (query) the trigger updates Surface_m2 column of all previous rows with the result of each new insert.
Why is the trigger working like that? Is there any other simple way to do what I am trying to do?
Thanks.
Insert trigger gives you actual values that are inserted in a special table called... "inserted".
So what you need to do is join this table against your main table and perform the logic needed, no variables required.
Something like this untested code
create trigger...
begin
UPDATE t
SET Surface_m2 = cast(round(CAST(t.Dimension1*t.Dimension2 as decimal)
/ cast(1000000 as decimal),3,3) as decimal(10,3))
from dbo.my_table t
inner join inserted i
ON i.ID = t.ID
WHERE t.Surface_m2 IS NULL
end
This begs the question though, why can't you just insert the Surface_m2 value yourself. Or even better, change Surface_m2 to be a computed column if it's always depends on Dimension1 and Dimension2

How to do these three things in a SQL Server transaction - 1. create table, 2.create trigger on table, 3. select from another table

I am trying to accomplish the following 3 simple tasks as a transaction (i.e. I need to lock old_table and new_table until the process completes).
Create a new table (new_table)
Add a trigger to old_table, which queues updates to new_table.
Select all the data from old_table and return it.
Note that I want these handled in a single transaction. I cannot allow inserts into old_table (and therefore triggered inserts into new_table) in between the trigger creation and the select on old_table.
My current closest attempt is this, but truthfully I feel that I am very far off from accomplishing my goal with this code. I have added the code just for reference of what I am trying, but I am mostly interested in non-specific answers that layout how to accomplish the above three comands in a transaction.
DROP PROCEDURE IF EXISTS dbo.BuildAll;
CREATE PROCEDURE dbo.BuildAll
AS
BEGIN
BEGIN TRANSACTION
DECLARE #TriggerCode VARCHAR(MAX)
CREATE TABLE dbo.new_table
(
status nvarchar(5),
type char(1),
col1 nvarchar(50),
col2 smallint
)
SELECT #TriggerCode = 'CREATE TRIGGER myTrigger
ON dbo.old_table FOR INSERT
AS
DECLARE #col1_new nvarchar(50)
DECLARE #col2_new smallint
SELECT #col1_new = col1 FROM inserted
SELECT #col2_new = col2 FROM inserted
IF #col1_new IS NOT NULL
BEGIN
INSERT INTO new_table (status, type, col1, col2)
SELECt "Q", "A", #col1, #col2 FROM inserted
END'
EXEC(#TriggerCode)
SELECT * FROM old_table
COMMIT
END
Going to suggest this an a possible solution you can try. This doesn't address the correctness of your actual trigger, you have two separate questions here really.
You don't need to encapsulate this entire process in a transaction.
Create your new table.
Create your trigger on old table, but disabled.
set transaction isolation level serializable
begin tran
go
create trigger <Name> on <Table> etc
go
disable trigger <Name> on <Table>
go
commit
Now in a transaction you can lock the old table against other activity while you work
begin tran
update oldtable with(tablockx) set column=column where id=0 /* block other processes from updating table, id=0 row doesn't exist */
query your data and process as required
enable trigger <Name> on <Table>
commit
This trigger code of yours is kinda odd .... you have a trigger on all three operations - yet it appears as if you're never using the values you fetch from the deleted pseudo table, and if the value from the inserted table is NULL, you're not doing anything inside your trigger - so you can really spare yourself the DELETE case - that'll never do anything....
Also, as mentioned in my comment - you Inserted pseudo table can easily contain multiple rows - but you're selecting from it as if you only ever expect it to contain a single row.
You should really rewrite your trigger code to handle the case of multiple rows in Inserted and make the whole thing properly set-based - something like this:
CREATE TRIGGER myTrigger
ON dbo.old_table
FOR INSERT, UPDATE
AS
INSERT INTO new_table (status, type, col1, col2)
SELECT 'Q', 'A', i.col1, i.col2
FROM Inserted i
Whether you need this on the UPDATE case at all - I cannot tell, you need to decide this. But basically: just select from the Inserted table, take the Col1 and Col2 values, and add the constant values 'Q' and 'A' to your insert to handle multiple rows properly. That should do it.

How to write a trigger to get before and after values in a single table

I have two tables transcriptTable and trancript_log.
I need to get before and after values in trancriptTable_log (OldValue,New,Value) when any update accrue on transcriptTable.
I have a trigger for before and after update but only one trigger at a time is executed.
Create trancriptTable_log table with required columns
create trigger below (one trigger is enough)
CREATE TRIGGER INSERTEDAndDELETEDTableExample
ON transcriptTable
FOR UPDATE
AS
BEGIN
--to get the old value (insert statement may vary depend on your need and table)
insert into trancriptTable_log (column_1) SELECT <your_column_name> FROM DELETED
--to get the new value
insert into trancriptTable_log values (column_2) SELECT <your_column_name> FROM INSERTED
END

How to write trigger to insert and update another table

I want to write trigger. When there is an entry in table1, table2 should get inserted with same values. And in some field in table1 is updated then respective field must get updated in table2. How can I know whether value is inserted or updated?
I am using inserted to insert value.
Please guide me.
Here is how you will know whether a value is inserted or updated in a trigger like this:
on INSERT, the inserted dynamic table is populated with the new values
on UPDATE, the inserted dynamic table is populated with the new values of the records that were updated, and the deleted dynamic table is poulated with the old values of the records that were updated
So basically, if the deleted table contains the id (assuming you have an id column) as in the inserted table, you can be assured that it was an UPDATE that caused the trigger. If the deleted table is empty, conversely, it was an INSERT.
use this trigger to solve your problem..
create TRIGGER [dbo].[insert_Assets_Tran]
ON [dbo].[AssetMaster]
AFTER INSERT , UPDATE
AS BEGIN
DECLARE #isnum TINYINT;
SELECT #isnum = COUNT(*) FROM inserted;
IF (#isnum = 1)
INSERT INTO AssetTransaction
select [AssetId],[Brandname],[SrNo],[Modelno],[Processor],[Ram],[Hdd],[Display],[Os],[Office],[Purchasedt]
,[Expirydt],[Vendor],[VendorAMC],[Typename],[LocationName],[Empid],[CreatedBy],[CreatedOn],[ModifiedBy]
,[ModifiedOn],[Remark],[AssetStatus],[Category],[Oylstartdt],[Oylenddt],[Configuration]
,[AStatus],[Tassign]
FROM inserted;
ELSE
RAISERROR('some fields not supplied', 16, 1)
WITH SETERROR;
END

How to Use FIRE_TRIGGERS in insert sql statement

I am trying to copy data from table "tb_A" to itself (with different primary key).
When "tb_A" table is insert new record, I have written a trigger to populate another table "tb_B" with one record.
I ran the following statement.
INSERT INTO [tb_A]
([NAME])
select top (20)[NAME] from [tb_A]
I was expected 20 new records in "tb_B". But I didn't.
Anyway I saw FIRE_TRIGGERS is using during bulk insert to overcome this issue.
is there is a any way to use it on inset statements too ? Please provide me example.
Gayan
Trigger code (copied from Gayan's comment to gbn's answer):
CREATE TRIGGER UpdatetbB ON [dbo].[tb_A] FOR INSERT
AS
DECLARE #AID as int
SELECT #AID = [ID] FROM inserted
INSERT INTO [tb_B]([IDA]) VALUES (#AID)
The reason your trigger did not work properly is because it is poorly designed. Triggers fire once for each insert even if you are inserting a million records. You havea trigger that makes the assumption it will owrk one record at a time. Anytime you set a value form inserted or deleted to a scalar variable the trigger is wrong and needs to be rewritten. Try something like this instead.
CREATE TRIGGER UpdatetbB ON [dbo].[tb_A] FOR INSERT
AS
INSERT INTO [tb_B]([IDA])
SELECT [ID] FROM inserted
FIRE_TRIGGERS is only for BULK INSERT (and bcp), not "standard" INSERT
I'd expect your trigger to look something like
CREATE TRIGGER TRG_tbA_I ON tb_A FOR INSERT
AS
SET NOCOUNT ON
INSERT tb_B (col1, col2, ...)
SELECT col1, col2, ... FROM INSERTED
GO
You use the special INSERTED table to get the list of new rows in tb_A, then INSERT from this into tb_B. This works for more than one row
If you add the trigger code then we can explain what went wrong.
Edit: your trigger will only read a single row (any row, no particular order) from INSERTED. It isn't set based like my rough example.