Automatic bulk insert / update with multiple variables - sql-server

Hej,
Update
Thank you guys for your answers and hints. I understand that my first attempt was wrong - so maybe the better approach is to describe my problem in words rather than trying a shitty trigger.
Table A is a list of all clients. For earch client exists multiple orders (next to other not needed information in that table):
CLIENT
ORDER
OPTIONAL
A
1
NO
A
2
YES
A
3
NO
B
16818
YES
B
342
YES
I need to insert all OPTIONAL=NO orders into table B in an automatic bulk process. It is possible that an order is changed from OPTIONAL=NO to OPTIONAL=YES and therefore i need the solutions for not only inserts but updates.
Thank you very much!

Best practice for triggers:
Don't create them unless you have no other option.
It's usually best to separate insert and update triggers, sometimes they can be combined.
NOCOUNT stops spurious messages going back to the client, XACT_ABORT means any error automatically rolls back the transaction.
Check if there are any relevant rows, if not bail out early.
Also check if a column is present in the update statement using the UPDATE() function if relevant (this doesn't mean the row has actually changed)
In an UPDATE trigger, make sure you compare inserted and deleted rows for actual changes.
Most important: be aware that inserted and deleted may contain multiple rows.
CREATE TRIGGER Trg_TableA_OPTIONAL_ins
ON TableA
AFTER INSERT
AS
SET NOCOUNT, XACT_ABORT ON;
IF (NOT EXISTS (SELECT 1 FROM inserted WHERE OPTIONAL = 'NO'))
RETURN;
INSERT TableB (CLIENT, [ORDER])
SELECT CLIENT, [ORDER]
FROM inserted
WHERE OPTIONAL = 'NO';
GO
CREATE TRIGGER Trg_TableA_OPTIONAL_upd
ON TableA
AFTER UPDATE
AS
SET NOCOUNT, XACT_ABORT ON;
IF (NOT EXISTS (SELECT 1 FROM inserted))
RETURN;
INSERT TableB (CLIENT, [ORDER])
SELECT CLIENT, [ORDER]
FROM (
SELECT CLIENT, [ORDER], OPTIONAL
FROM inserted
WHERE OPTIONAL = 'NO'
EXCEPT
SELECT CLIENT, [ORDER], OPTIONAL
FROM deleted
) i
EXCEPT
SELECT CLIENT, [ORDER]
FROM TableB;
DELETE FROM b
FROM TableB b
JOIN (
SELECT CLIENT, [ORDER], OPTIONAL
FROM inserted
WHERE OPTIONAL = 'YES'
EXCEPT
SELECT CLIENT, [ORDER], OPTIONAL
FROM deleted
) i
ON TableB.CLIENT = Source.CLIENT AND TABLEB.[ORDER] = Source.[ORDER];
GO

Related

(SQL) Trying to Make A Trigger That Maps Data To a Historical Table Generically With Error Checking

I have two tables at the current moment, located in two different schema dbo.table1 and historyschema.table1history. I have created a trigger that moves all historical data from table1 (upon insert, delete, and update) to table1history.
I originally tried to design the trigger to transfer the information with a INSERT INTO [historyschema].[table1history] select #columns...
Explicitly stating all columns to transfer, but since the two tables have the exact same columns (except history has an extra creation_timestamp and creation_status) I wanted to add them generically with a select * statement. I did this because then when the database has a column added I don't want to have to go back and manually change the trigger every time.
I need a try/catch statement in case there is an error but it doesn't work because the error obtained isn't caught by try/catch (The Error Obtained is Implicit Conversion because of wrongly aligned tables). I was wondering if there is a way to iterate through the columns of table1 and make sure the columns are formatted in such a way where the information will not create an error when being put into table1history.
CREATE TRIGGER [dbo].[table1_History_Change]
ON [dbo].[table1]
AFTER UPDATE, INSERT, DELETE
AS
DECLARE #Now AS DateTIME = GETDATE()
SELECT *
INTO #ModifiedTemp
FROM
(SELECT 'I' *, creation_status, #Now creation_timestamp
FROM inserted
UNION
SELECT *, 'D' creation_status, #Now creation_timestamp
FROM deleted) SubQuery
IF (SELECT COUNT(*) FROM #ModifiedTemp WHERE creation_status = 'D') > 0
AND (SELECT COUNT(*) FROM #ModifiedTemp WHERE creation_status = 'I') > 0
BEGIN
DELETE FROM #ModifiedTemp
WHERE creation_status = 'D'
UPDATE #ModifiedTemp
SET creation_status = 'U'
END
BEGIN TRY
INSERT INTO [HistorySchema].[Table1History]
SELECT *
FROM #ModifiedTemp
END TRY
BEGIN CATCH
PRINT 'History unsuccessfully saved (ERROR: bundle_procedure_change)'
DROP TABLE #ModifiedTemp
RETURN
END CATCH
DROP TABLE #ModifiedTemp
(This is my first posts so if you have any criticisms to help me to improve they are appreciated :).)
NOTE: If the tables are formatted incorrectly I want the trigger to simply not create the history but follow through with the transaction on the main table.
Your entire trigger can be simplified to something close to this. I don't know what columns you really have. You should NEVER use * here because if your base table changes your trigger is broken. Be explicit with columns.
CREATE TRIGGER [dbo].[table1_History_Change] ON [dbo].[table1]
after update, insert, delete as
set nocount on;
INSERT INTO [HistorySchema].[Table1History]
(
CreationStatus
, creation_timestamp
, Col1
)
select CreationStatus = case when i.ID is null then 'D' when d.ID is null then 'I' else 'U' end
, creation_timestamp = getdate()
, isnull(i.Col1, d.Col2) --this will get the new values for inserted and updated or the current value for a delete
from inserted i
full outer join deleted d on d.ID = i.ID

Trigger reverses the changes made - SQL Server

I'm working on an E-commerce system where I have an order table that stores all the information regarding an order. The orders go through different stages: Open, Verified, In Process, etc. And I'm keeping counts of these orders at different stages e.g. Open Orders 95, Verified 5, In Process 3, etc.
When a new order is inserted in the table, I have a trigger that increments the Open Orders by 1. Similarly, I have a trigger for updates which checks the order's previous stage and the next to decrement and increment accordingly.
The INSERT trigger is working fine as described above. But the UPDATE trigger has a weird behavior that it makes the desired changes to the Counts but then reverses the changes for some reason.
For instance, upon changing the status of an order from Open to Verified, the ideal behavior would be to decrement Open Orders by 1 and increment Verified Orders by 1. The trigger currently performed the desired action but then for some reason restores the previous value.
Here's a snippet of my trigger where I check if the order previously belonged to the Open status and is now being updated to Verified status:
IF ##ROWCOUNT = 0 RETURN
DECLARE #orderID VARCHAR(MAX) -- orderID of the order that is being updated
DECLARE #storeID VARCHAR(MAX) -- storeID of the store the order belongs to
SELECT TOP 1
#orderID = i.id,
#storeID = i.storeID
FROM
inserted AS i
INNER JOIN deleted AS d
ON i.id = d.id
-- IF from Open Order
IF EXISTS (
SELECT *
FROM
deleted
WHERE
orderStatus = 'Open' AND
id = #orderID
)
BEGIN
-- IF to Verified Order
IF EXISTS (
SELECT *
FROM
inserted
WHERE
orderStatus = 'Verified' AND
id = #orderID
)
BEGIN
UPDATE order_counts
SET
open_orders = open_orders - ##ROWCOUNT,
verified_orders = verified_orders + ##ROWCOUNT
WHERE storeID = #storeID
END
END
EDIT:
Here's some extra information which will be helpful in light of the first comment on the question:
I have a lot of records in the table so using COUNT() again and again has a lot of impact on the overall performance. This is why I'm keeping counts in a separate table. Also, I've written the trigger in a way that it handles both single record/multi record changes. I only check one row because I know in case of multiple records they will all be going through the same change of status. Hence, the decrement/increment of ##ROWCOUNT
If you can tolerate a slightly different representation of the order counts, I'd strongly suggest using an indexed view instead1:
create table dbo.Orders (
ID int not null,
OrderStatus varchar(20) not null,
constraint PK_Orders PRIMARY KEY (ID)
)
go
create view dbo.OrderCounts
with schemabinding
as
select
OrderStatus,
COUNT_BIG(*) as Cnt
from
dbo.Orders
group by OrderStatus
go
create unique clustered index IX_OrderCounts on dbo.OrderCounts (OrderStatus)
go
insert into dbo.Orders (ID,OrderStatus) values
(1,'Open'),
(2,'Open'),
(3,'Verified')
go
update dbo.Orders set OrderStatus = 'Verified' where ID = 2
go
select * from dbo.OrderCounts
Results:
OrderStatus Cnt
-------------------- --------------------
Open 1
Verified 2
This has the advantage that, whilst behind the scenes SQL Server is doing something very similar to running triggers, this code has been debugged thoroughly and is correct.
In your current attempted trigger, one further reason why the trigger is currently broken is that ##ROWCOUNT isn't "sticky" - it doesn't remember the number of rows that were affected by the original UPDATE when you're running other statements inside your trigger that also set ##ROWCOUNT.
1You can always stack a non-indexed view atop this view and perform a PIVOT if you really want the counts in a single row and in multiple columns.
The reason for this behavior is the use of ##ROWCOUNT multiple times while in reality once the results from ##ROWCOUNT is fetched, the results are cleared. Instead get the results into variable and use that variable across the trigger. Check the below scenario for the same.
CREATE DATABASE Test
USE Test
CREATE TABLE One
(
ID INT IDENTITY(1,1)
,Name NVARCHAR(MAX)
)
GO
CREATE TRIGGER TR_One ON One FOR INSERT,UPDATE
AS
BEGIN
PRINT ##ROWCOUNT
SELECT ##ROWCOUNT
END
UPDATE One
SET Name = 'Name4'
WHERE ID = 3
RESULTS :-
The Print ##ROWCOUNT statement would give a value of 1, where as the select ##ROWCOUNT would give the value of 0

How to prevent updates to a table, with an exception for one situation

I have a table that contains records that can become part of a bill. I can tell which ones are already part of a bill because the table has a BillId column that gets updated by the application code when that happens. I want to prevent updates to any record that has a non-null BillId. I'm thinking that the following should take care of that:
CREATE TRIGGER [Item_Update_AnyBilled]
ON [dbo].[Item]
FOR UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE #AnyBilled BIT;
SELECT TOP(1) #AnyBilled = 1
FROM inserted i
JOIN deleted d ON i.ItemId = d.ItemId
WHERE d.BillId IS NOT NULL;
IF COALESCE(#AnyBilled, 0) = 1 BEGIN
RAISERROR(2870486, 16, 1); -- Cannot update a record that is part of a bill.
ROLLBACK TRANSACTION;
END;
END;
However, there is one more wrinkle. The Item table also has a DATETIME Modified column, and a trigger that updates it.
CREATE TRIGGER [dbo].Item_Update_Modified
ON [dbo].[Item]
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE a
SET Modified = getdate()
FROM Item a JOIN inserted i ON i.ItemId = a.ItemId
END
With these triggers in place, adding an Item to a Bill always causes the RAISERROR to fire. Presumably because when the BillId is populated, Item_Update_AnyBilled lets it through because the deleted.BillId is NULL, but the Item_Update_Modified then gets executed, and that secondary change causes Item_Update_AnyBilled to get executed again, and this time deleted.BillId is no longer NULL.
How can I prevent updates to the Item table except in the case where the BillId is being populated or when the only change is to the Modified column?
I'd prefer a solution that didn't require me to compare the inserted and deleted values of every column (or use COLUMNS_UPDATED()) as that would create a maintenance issue (someone would have to remember to update the trigger any time a new column is added to or deleted from the table). I am using SQL Server 2005.
Why not use an INSTEAD OF trigger? It requires a bit more work (namely a repeated UPDATE statement) but any time you can prevent work, instead of letting it happen and then rolling it back, you're going to be better off.
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS
(
SELECT 1 FROM inserted i
JOIN deleted AS d ON i.ItemId = d.ItemId
WHERE d.BillId IS NULL -- it was NULL before, may not be NULL now
)
BEGIN
UPDATE src
SET col1 = i.col1 --, ... other columns
ModifiedDate = CURRENT_TIMESTAMP -- this eliminates need for other trigger
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON i.ItemId = src.ItemId
AND (criteria to determine if at least one column has changed);
END
ELSE
BEGIN
RAISERROR(...);
END
END
GO
This doesn't fit perfectly. The criteria I've left out is left out for a reason: it can be complex to determine if a column value has changed, as it depends on the datatype, whether the column can be NULL, etc. AFAIK the built-in trigger functions can only tell if a certain column was specified, not whether the value actually changed from before.
EDIT considering that you're only concerned about the other columns that are updated due to the after trigger, I think the following INSTEAD OF trigger can replace both of your existing triggers and also deal with multiple rows updated at once (some without meeting your criteria):
CREATE TRIGGER [dbo].[Item_BeforeUpdate_AnyBilled]
ON [dbo].[Item]
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE src SET col1 = i.col1 --, ... other columns,
ModifiedDate = CURRENT_TIMESTAMP
FROM dbo.Item AS src
INNER JOIN inserted AS i
ON src.ItemID = i.ItemID
INNER JOIN deleted AS d
ON i.ItemID = d.ItemID
WHERE d.BillID IS NULL;
IF ##ROWCOUNT = 0
BEGIN
RAISERROR(...);
END
END
GO

trigger not updating sql

Although this is completed successfully on completion, it is not having the desired update.
CREATE TRIGGER Trigger1
On dbo.[table1]
FOR UPDATE
AS
Declare #Id int;
SELECT #Id = Issue_Id FROM dbo.[table1]
INSERT INTO dbo.[storage]
SELECT Id, Title, project, Problem
FROM dbo.[table2]
WHERE Id = #Id
Is there something I am doing wrong or that I can't use variables within the scope of a trigger?
Many thanks
To support multirow updates
CREATE TRIGGER Trigger1 On dbo.[table1] FOR UPDATE
AS
SET NOCOUNT ON
INSERT INTO dbo.[storage]
SELECT t.Id, t.Title, t.project, t.Problem
FROM dbo.[table2] t
JOIN INSERTED I ON t.ID = I.ID
GO
If table2 is actually table1 (which makes more sense: how is table1 related to storage and table2?)...
CREATE TRIGGER Trigger1 On dbo.[table1] FOR UPDATE
AS
SET NOCOUNT ON
INSERT INTO dbo.[storage]
SELECT Id, Title, project, Problem
FROM INSERTED
GO
To handle multple updates and the inserted table in one go:
CREATE TRIGGER Trigger1
On dbo.[table1]
FOR UPDATE
AS
INSERT INTO dbo.[storage]
SELECT Id, Title, project, Problem
FROM dbo.[table2] t2
JOIN Inserted i ON i.Issue_ID = t2.Id
Please go through the below suggestion.
Instead of the below line
SELECT #Id = Issue_Id FROM dbo.[table1]
It had to be following.
SELECT Issue_Id FROM Inserted
Following is the updated one.
CREATE TRIGGER Trigger1
On dbo.[table1]
FOR UPDATE
AS
SET NOCOUNT ON
Declare #Id int;
With CTE as
(
SELECT Issue_Id FROM Inserted I
Inner Join [table1] T on T.Issue_Id = I.Issue_Id
)
INSERT INTO dbo.[storage]
SELECT Id, Title, project, Problem
FROM dbo.[table2]
Inner Join CTE c on c.Issue_Id = Id
For more information
In SQL server the records which are being inserted / modified or deleted occupies themselves in two temporary tables available in a DML trigger. These tables are INSERTED and DELETED. The INSERTED table has inserted or updated records. The DELETED table has the old state of the records being updated or deleted.
The others have correctly answered that you should be using inserted and a join, to build a proper trigger. But:
Based on your comments to other's answers - you should never attempt to access any resource outside of your own database from a trigger, let along from another server.
Try to decouple the trigger activity from the cross server activity - say have your trigger add a row to a queue table (or use real service broker queues), and have an independent component be responsible for servicing these requests.
Otherwise, if there are any e.g. network issues, not only does your trigger break, but it forces a rollback for the original update also - it makes your local database unusable.
This also means that the independent component can cope with timeouts, and perform appropriate retries, etc.
below line should be removed
SELECT #Id = Issue_Id FROM dbo.[table1]
It should be following.
SELECT Issue_Id FROM Inserted

Instead of trigger in SQL Server loses SCOPE_IDENTITY?

I have a table where I created an INSTEAD OF trigger to enforce some business rules.
The issue is that when I insert data into this table, SCOPE_IDENTITY() returns a NULL value, rather than the actual inserted identity.
Insert + Scope code
INSERT INTO [dbo].[Payment]([DateFrom], [DateTo], [CustomerId], [AdminId])
VALUES ('2009-01-20', '2009-01-31', 6, 1)
SELECT SCOPE_IDENTITY()
Trigger:
CREATE TRIGGER [dbo].[TR_Payments_Insert]
ON [dbo].[Payment]
INSTEAD OF INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
IF NOT EXISTS(SELECT 1 FROM dbo.Payment p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo)
) AND NOT EXISTS (SELECT 1 FROM Inserted p
INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
WHERE (i.DateFrom <> p.DateFrom AND i.DateTo <> p.DateTo) AND
((i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo))
)
BEGIN
INSERT INTO dbo.Payment (DateFrom, DateTo, CustomerId, AdminId)
SELECT DateFrom, DateTo, CustomerId, AdminId
FROM Inserted
END
ELSE
BEGIN
ROLLBACK TRANSACTION
END
END
The code worked before the creation of this trigger. I am using LINQ to SQL in C#. I don't see a way of changing SCOPE_IDENTITY to ##IDENTITY. How do I make this work?
Use ##identity instead of scope_identity().
While scope_identity() returns the last created id in the current scope, ##identity returns the last created id in the current session.
The scope_identity() function is normally recommended over the ##identity field, as you usually don't want triggers to interfer with the id, but in this case you do.
Since you're on SQL 2008, I would highly recommend using the OUTPUT clause instead of one of the custom identity functions. SCOPE_IDENTITY currently has some issues with parallel queries that cause me to recommend against it entirely. ##Identity does not, but it's still not as explicit, and as flexible, as OUTPUT. Plus OUTPUT handles multi-row inserts. Have a look at the BOL article which has some great examples.
I was having serious reservations about using ##identity, because it can return the wrong answer.
But there is a workaround to force ##identity to have the scope_identity() value.
Just for completeness, first I'll list a couple of other workarounds for this problem I've seen on the web:
Make the trigger return a rowset. Then, in a wrapper SP that performs the insert, do INSERT Table1 EXEC sp_ExecuteSQL ... to yet another table. Then scope_identity() will work. This is messy because it requires dynamic SQL which is a pain. Also, be aware that dynamic SQL runs under the permissions of the user calling the SP rather than the permissions of the owner of the SP. If the original client could insert to the table, he should still have that permission, just know that you could run into problems if you deny permission to insert directly to the table.
If there is another candidate key, get the identity of the inserted row(s) using those keys. For example, if Name has a unique index on it, then you can insert, then select the (max for multiple rows) ID from the table you just inserted to using Name. While this may have concurrency problems if another session deletes the row you just inserted, it's no worse than in the original situation if someone deleted your row before the application could use it.
Now, here's how to definitively make your trigger safe for ##Identity to return the correct value, even if your SP or another trigger inserts to an identity-bearing table after the main insert.
Also, please put comments in your code about what you are doing and why so that future visitors to the trigger don't break things or waste time trying to figure it out.
CREATE TRIGGER TR_MyTable_I ON MyTable INSTEAD OF INSERT
AS
SET NOCOUNT ON
DECLARE #MyTableID int
INSERT MyTable (Name, SystemUser)
SELECT I.Name, System_User
FROM Inserted
SET #MyTableID = Scope_Identity()
INSERT AuditTable (SystemUser, Notes)
SELECT SystemUser, 'Added Name ' + I.Name
FROM Inserted
-- The following statement MUST be last in this trigger. It resets ##Identity
-- to be the same as the earlier Scope_Identity() value.
SELECT MyTableID INTO #Trash FROM MyTable WHERE MyTableID = #MyTableID
Normally, the extra insert to the audit table would break everything, because since it has an identity column, then ##Identity will return that value instead of the one from the insertion to MyTable. However, the final select creates a new ##Identity value that is the correct one, based on the Scope_Identity() that we saved from earlier. This also proofs it against any possible additional AFTER trigger on the MyTable table.
Update:
I just noticed that an INSTEAD OF trigger isn't necessary here. This does everything you were looking for:
CREATE TRIGGER dbo.TR_Payments_Insert ON dbo.Payment FOR INSERT
AS
SET NOCOUNT ON;
IF EXISTS (
SELECT *
FROM
Inserted I
INNER JOIN dbo.Payment P ON I.CustomerID = P.CustomerID
WHERE
I.DateFrom < P.DateTo
AND P.DateFrom < I.DateTo
) ROLLBACK TRAN;
This of course allows scope_identity() to keep working. The only drawback is that a rolled-back insert on an identity table does consume the identity values used (the identity value is still incremented by the number of rows in the insert attempt).
I've been staring at this for a few minutes and don't have absolute certainty right now, but I think this preserves the meaning of an inclusive start time and an exclusive end time. If the end time was inclusive (which would be odd to me) then the comparisons would need to use <= instead of <.
Main Problem : Trigger and Entity framework both work in diffrent scope.
The problem is, that if you generate new PK value in trigger, it is different scope. Thus this command returns zero rows and EF will throw exception.
The solution is to add the following SELECT statement at the end of your Trigger:
SELECT * FROM deleted UNION ALL
SELECT * FROM inserted;
in place of * you can mention all the column name including
SELECT IDENT_CURRENT(‘tablename’) AS <IdentityColumnname>
Like araqnid commented, the trigger seems to rollback the transaction when a condition is met. You can do that easier with an AFTER INSTERT trigger:
CREATE TRIGGER [dbo].[TR_Payments_Insert]
ON [dbo].[Payment]
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
IF <Condition>
BEGIN
ROLLBACK TRANSACTION
END
END
Then you can use SCOPE_IDENTITY() again, because the INSERT is no longer done in the trigger.
The condition itself seems to let two identical rows past, if they're in the same insert. With the AFTER INSERT trigger, you can rewrite the condition like:
IF EXISTS(
SELECT *
FROM dbo.Payment a
LEFT JOIN dbo.Payment b
ON a.Id <> b.Id
AND a.CustomerId = b.CustomerId
AND (a.DateFrom BETWEEN b.DateFrom AND b.DateTo
OR a.DateTo BETWEEN b.DateFrom AND b.DateTo)
WHERE b.Id is NOT NULL)
And it will catch duplicate rows, because now it can differentiate them based on Id. It also works if you delete a row and replace it with another row in the same statement.
Anyway, if you want my advice, move away from triggers altogether. As you can see even for this example they are very complex. Do the insert through a stored procedure. They are simpler and faster than triggers:
create procedure dbo.InsertPayment
#DateFrom datetime, #DateTo datetime, #CustomerId int, #AdminId int
as
BEGIN TRANSACTION
IF NOT EXISTS (
SELECT *
FROM dbo.Payment
WHERE CustomerId = #CustomerId
AND (#DateFrom BETWEEN DateFrom AND DateTo
OR #DateTo BETWEEN DateFrom AND DateTo))
BEGIN
INSERT into dbo.Payment
(DateFrom, DateTo, CustomerId, AdminId)
VALUES (#DateFrom, #DateTo, #CustomerId, #AdminId)
END
COMMIT TRANSACTION
A little late to the party, but I was looking into this issue myself. A workaround is to create a temp table in the calling procedure where the insert is being performed, insert the scope identity into that temp table from inside the instead of trigger, and then read the identity value out of the temp table once the insertion is complete.
In procedure:
CREATE table #temp ( id int )
... insert statement ...
select id from #temp
-- (you can add sorting and top 1 selection for extra safety)
drop table #temp
In instead of trigger:
-- this check covers you for any inserts that don't want an identity value returned (and therefore don't provide a temp table)
IF OBJECT_ID('tempdb..#temp') is not null
begin
insert into #temp(id)
values
(SCOPE_IDENTITY())
end
You probably want to call it something other than #temp for safety sake (something long and random enough that no one else would be using it: #temp1234235234563785635).

Resources