get last primary key-not identity- inserted using a trigger - sql-server

I have an insert trigger to insert the primary key(varchar) of a table.I want to get the last inserted primary key value.
these is my insert trigger
ALTER TRIGGER dbo.equipment_insert_pk
ON dbo.Equipment
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
declare #id int;
declare #cat varchar(2);
select #cat=CategoryID from INSERTED;
select #id=cast(max(right(EquipmentID,4)) as int) from Equipment where CategoryID=#cat;
set #id=isnull(#id,0)+1;
-- Insert statements for trigger here
insert into Equipment
select CategoryID+right('0000'+cast(#id as varchar(4)),4)
,CategoryID
,Location
,Detail
,'../BarcodeImage/'+CategoryID+right('0000'+cast(#id as varchar(4)),4)+'.jpeg' from INSERTED;
END
I tried to write a function to fetch the last inserted primary key value but it seems not to work..because there is no scope for "inserted" outside trigger.Help me..
I tried this code within trigger with output clause no result so far..
insert into Equipment
output INSERTED.EquipmentID into #Temp_Tbl
select CategoryID+right('0000'+cast(#id as varchar(4)),4)
,CategoryID
,Location
,Detail
,'../BarcodeImage/'+CategoryID+right('0000'+cast(#id as varchar(4)),4)+'.jpeg' from INSERTED;
Any help ??

ok. I bumped into this exact same problem a few weeks ago.
I've tried many different ways - to no avail. Long story short - I ended up adding a new column with the type of UNIQUEIDENTIFIER (guid)
So to use your example - let's say there's a column called guid in dbo.Equipment
Roughly, here's how you do it:
insert into Equipment
output INSERTED.EquipmentID, INSERTED.guid into #Temp_Tbl
select CategoryID+right('0000'+cast(#id as varchar(4)),4)
,CategoryID
,Location
,Detail
,'../BarcodeImage/'+CategoryID+right('0000'+cast(#id as varchar(4)),4)+'.jpeg'
,NEWID() -- new column
from INSERTED;
Then you can query dbo.Equipment using #Temp_Tbl.guid to get the inserted id.
For example:
SELECT EquipmentID FROM dbo.Equipment e WHERE EXISTS (SELECT * FROM #Temp_Tbl tt WHERE tt.guid = e.guid)

Related

SQL trigger with IDENTITY_INSERT

I have two tables: Table1 is all the companies, Table2 is companies whose name start with A.
Table1 company (companyId int, companyName varchar(50), companySize int)
Table2 companyStartWithA (companyId int, companyName varchar(50), companySize int)
What I want to do is to create a trigger so that when I insert/update/delete something in Table1, it will automatically do the same in Table2
My code:
CREATE TRIGGER A_TRG_InsertSyncEmp
ON company
AFTER INSERT
AS
BEGIN
INSERT INTO companyStartWithA
SELECT *
FROM INSERTED
WHERE inserted.companyName LIKE 'A%'
END
And I get an error:
An explicit value for the identity column in table 'companyStartWithA' can only be specified when a column list is used and IDENTITY_INSERT is ON.
What can I do?
Thanks
The problem is the fact that you're not explicitly specifying the column in the INSERT statement, and using a SELECT * to fill the data. Both are big no-no's - you should always explicitly specify the column that you want to insert into, and you should always explicitly specify the columns that you want to select. Doing so will fix this problem:
CREATE TRIGGER A_TRG_InsertSyncEmp
ON company
AFTER INSERT
AS
BEGIN
INSERT INTO companyStartWithA (companyName, companySize)
SELECT companyName, companySize
FROM INSERTED
WHERE inserted.companyName LIKE 'A%'
END
But as Sean Lange absolutely correctly commented - this should really be just a view rather than a separate table.....
CREATE VIEW dbo.CompanyStartsWithA
AS
SELECT companyId, companyName, companySize
FROM dbo.Company
WHERE Name LIKE 'A%'
and then you don't need any messy triggers or anything - just insert into dbo.Company and all companies with a name that starts with an A will be visible in this view....

I would like to know on how to convert Oracle triggers into SQL Server triggers

As I understand, SQL SERVER Triggers does not support FOR EACH ROW. Also I am aware that you have to use inserted tables and deleted tables. Other than that, I have no clue how to write SQL Server triggers. They look so different. Can some help please?
Below is the code for Oracle Triggers
create or replace TRIGGER Ten_Percent_Discount
BEFORE INSERT OR UPDATE ON Bookings
FOR EACH ROW
DECLARE CURSOR C_Passengers IS
SELECT StatusName
FROM Passengers
WHERE PassengerNumber = :NEW.Passengers_PassengerNumber;
l_status_name Passengers.StatusName%TYPE;
BEGIN
OPEN C_Passengers;
FETCH C_Passengers INTO l_status_name;
CLOSE C_Passengers;
Below is what I have written so far. I know I am using the inserted tables wrong
IF l_status_name = 'Regular'
THEN
:New.TotalCost := 0.90 * :New.TotalCost;
END IF;
END;
create TRIGGER Ten_Percent_Discount
ON Customer
FOR INSERT ,UPDATE
AS
DECLARE C_Passengers CURSOR FOR
SELECT StatusLevel
FROM Customer
WHERE CustomerID = inserted.CustomerID
Thanks for all the help in advance.
Table structure for customer
Table structure for Order
Below answer is only for reference purpose that you can use to build gradually towards final solution:
create table dbo.customer
(
customerid varchar(10),
firstname nvarchar(50),
statuslevel varchar(50)
)
go
create table dbo.customerorder
(
orderid varchar(10),
totalprice numeric(5,2),
productid varchar(10),
customerid varchar(10)
)
go
go
create trigger dbo.tr_customer on dbo.customer for insert,update
as
begin
update co
set co.totalprice = .9*co.totalprice
from dbo.customerorder co
inner join inserted i
on co.customerid = i.customerid
where i.statuslevel = 'Standard'
end
go
--test for above code
insert into dbo.customer values (1,'jayesh','')
insert into dbo.customerorder values (1,500.25,1,1)
insert into dbo.customerorder values (1,600.25,2,1)
select * from dbo.customer
select * from dbo.customerorder
update dbo.customer set statuslevel = 'Standard' where customerid = 1
select * from dbo.customer
select * from dbo.customerorder
But what I am pretty sure is that when customer is created for the first time, there will not be any orders to apply discounts on, so you will certainly need UPDATE Trigger as well.

how can I create a trigger in SQL Server to update after insert in a table

I created a trigger that after insert must update the table inserted:
alter trigger DispararInsertFactura1
on FacturaCabecera
after insert
as
BEGIN
Declare #numfac int;
select #numfac = NumFactura
FROM FacturaCabecera
WHERE id = (SELECT max(id) from FacturaCabecera);
update FacturaCabecera
set NumFactura=#numfac+1
where Id = (SELECT Id FROM INSERTED);
END
GO
But doesn't work, Did I make a mistake?
It is generally not allowed for a trigger to start a separate access to the same table it is defined on. The table is right smack in the middle of being altered (otherwise referred to as mutating).
"After" triggers are good for audit-type actions. Inserting an entry into a different table to describe the action that just took place.
"Before" triggers are good for verifying and possible altering the data stream before it goes to the table. This is what you want to do.
Unfortunately, SQL Server does not have "Before" triggers. It does, however, allow "Instead Of" triggers on tables. These triggers are not executed as part of the DML operation but before it begins. As the name implies, the trigger is executed in lieu of the DML operation. The trigger itself must initiate the operation or nothing happens. Defining an "Instead Of" trigger that does nothing is a good way to render a table or view read-only.
alter trigger DispararInsertFactura1
on FacturaCabecera
instead of insert as
declare #numfac int;
select Top 1 #numfac = NumFactura
from FacturaCabecera
order by id desc;
insert into FacturaCabecera( ..., NumFactura, ...)
select ..., IsNull( #numfac, 0 ) + 1, ...
from Inserted;
Notice that the trigger itself must execute the Insert statement, allowing it to alter what is being inserted.
I was thinking in this and I found the mistake, I didn't realize that the MAX number always would be a value NULL because the trigger will updated after of inserted in the table, (the value is NULL when I insert) so I changed this:
select #numfac = NumFactura
FROM Factura Cabecera
WHERE id = (SELECT max(id)
from Factura Cabecera);
for this that give me the number max second:
SELECT #numfac = NumFactura FROM FacturaCabecera WHERE ID = (SELECT MIN(id)
FROM (SELECT DISTINCT TOP (2) id FROM FacturaCabecera where
tipofactura='1' ORDER BY id DESC) T);
Thanks for you help
ALTER TRIGGER DispararInsertFactura1
on FacturaCabecera
INSTEAD OF INSERT AS
DECLARE #numfac int;
SELECT TOP 1 #numfac = NumFactura
FROM FacturaCabecera
ORDER BY id DESC;
INSERT INTO FacturaCabecera( __ , NumFactura, __)
SELECT __ , IsNull( #numfac, 0 ) + 1, __
FROM Inserted;
Trying this

is there an efficient way of transfering all the rows of one table to another table, one record at a time

requirement is to transfer all inserted records to one table to another. i've used a trigger to do it. i'm looping through all the inserted records and inserting to new table one record at a time as i have to increment a sequence number in the destination table. but this loop considarably slow when number of inserted rows increase. is there a better way of doing this.
Declare #maxpk int, #count int, #seq int
set #maxpk=(select max(refno) from inserted )
set #count=(select count(1) from inserted)
set #seq=((select max(seq_no) from dbase.dbo.destination))
while #count>0
begin
set #seq=(select #seq+1)
insert into dbase.dbo.destination(orderno,SEQ_NO,PRODUCT_ID,qty)
select ordernumber,#seq,productid ,quantity
from inserted where refno=#maxpk
set #count=(select #count-1)
set #maxpk=(select top 1 refno from inserted where refno<#maxpk)
end
refno is primary key of source table. is there a way to check the end of inserted records so i don't have to initialize and maintain a loop counter?
and can loop be executed for each record in inserted table so i don't have to find the next record to insert by comparing the value of primary key.
using mssql 2005
This should handle concurrency ok but I really think you need to revisit the design (e.g. make seq_no an IDENTITY column, then the system generates the unique values for you, and handles concurrency too).
CREATE TRIGGER dbo.SourceTrg ON dbo.Source
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #seq INT;
SET #seq = COALESCE((SELECT MAX(seq_no)
FROM dbase.dbo.Destination WITH (TABLOCKX, HOLDLOCK)), 0);
INSERT dbase.dbo.Destination(SEQ_NO, orderno, PRODUCT_ID, qty)
SELECT #seq + ROW_NUMBER() OVER (ORDER BY refno),
ordernumber, productid, quantity
FROM inserted;
END
GO

SQL Server: is it possible to get recently inserted identity column value without table variable

Let's say I have a table
my_table(id int identity(1,1) not null primary key, data varchar(100))
I want to write a procedure that inserts a new row into that table and returns id.
I tried
DECLARE #new_id INT;
SELECT #new_id = id FROM
(
INSERT INTO my_table(data) OUTPUT inserted.id VALUES ('test')
) as NewVal(id)
That code doesn't work (I got "A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed in a SELECT statement that is not the immediate source of rows for an INSERT statement."). However, if I use a table variable, I can do
DECLARE #new_id INT;
DECLARE #tmp_table TABLE(int id);
INSERT INTO #tmp_table
SELECT id FROM
(
INSERT INTO my_table(data) OUTPUT inserted.id VALUES ('test')
) as NewVal(id);
// OR
INSERT INTO my_table(data) OUTPUT inserted.id INTO #tmp_table VALUES ('test') ;
SELECT #new_id = id FROM #tmp_table;
Is it possible to achieve the same functionality without using table variable ?
UPDATE
Thanks for quick responses, +1 to everyone for solution with SCOPE_IDENTITY.
That's probably my fault, I should have asked the question clearly - I do use MERGE (an example would be much longer, so I posted INSERT instead) , not INSERT so SCOPE_IDENTITY doesn't really work for me.
A bit shorter version than nesting in a insert statement is using output...into.
declare #tmp_table table(actiontaken nvarchar(10), id int);
merge my_table
using (values ('test')) as S(data)
on 0=1
when not matched then
insert (data) values (S.data)
output $action, inserted.id into #tmp_table;
I do believe that you should use a table variable from the merge. The output may contain more than one row.
Yes, you can just return SCOPE_IDENTITY after the insert (this is safer than ##IDENTITY due to the scoping differences).
i.e.
INSERT my_table (data) VALUES ('test')
SELECT SCOPE_IDENTITY()
SELECT SCOPE_IDENTITY() will get the last inserted identity value in the scope.
If you use
select ##identity
you will get the last value entered into the identity column from anywhere.
If you want the value entered into the column from the script that just ran, you should use
select SCOPE_IDENTITY()
If you just want the last value inserted into the identity column for a given table from any statement in any session, you should use
select IDENT_CURRENT('tablename')
select ##identity or select SCOPE_IDENTITY() will return the value you're looking for

Resources