Here is the solution that works with Multiple Rows.
Thanks to jFun for the work done for the single row insert, but the trigger is not really safe to use like that.
OK, Assuming this table:
create table TestingTransactions (
id int identity,
transactionNo int null,
contract_id int not null,
Data1 varchar(10) null,
Data2 varchar(10) null
);
In my case I needed "transactionNo" to always have the correct next value for each CONTRACT. Important for me in a legacy financial system is that there are no gaps in the transactionNo numbers.
So, we need the following trigger for to ensure referential integrity for the transactionNo column.
CREATE TRIGGER dbo.Trigger_TransactionNo_Integrity
ON dbo.TestingTransactions
INSTEAD OF INSERT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Discard any incoming transactionNo's and ensure the correct one is used.
WITH trans
AS (SELECT F.*,
Row_number()
OVER (
ORDER BY contract_id) AS RowNum,
A.*
FROM inserted F
CROSS apply (SELECT Isnull(Max(transactionno), 0) AS
LastTransaction
FROM dbo.testingtransactions
WHERE contract_id = F.contract_id) A),
newtrans
AS (SELECT T.*,
NT.minrowforcontract,
( 1 + lasttransaction + ( rownum - NT.minrowforcontract ) ) AS
NewTransactionNo
FROM trans t
CROSS apply (SELECT Min(rownum) AS MinRowForContract
FROM trans
WHERE T.contract_id = contract_id) NT)
INSERT INTO dbo.testingtransactions
SELECT Isnull(newtransactionno, 1) AS TransactionNo,
contract_id,
data1,
data2
FROM newtrans
END
GO
OK, I'll admit this is a pretty complex trigger with just about every trick in the book here, but this version should work all the way back to SQL 2005. The script utilises 2 CTE's, 2 cross applies and a Row_Num() over to work out the correct "next" TransactionNo for all of the rows in Inserted
.
It works using an instead of insert
trigger and discards any incoming transactionNo and replaces them with the "NEXT" transactionNo.
So, we can now run these updates:
delete from dbo.TestingTransactions
insert into dbo.TestingTransactions (transactionNo, Contract_id, Data1)
values (7,213123,'Blah')
insert into dbo.TestingTransactions (transactionNo, Contract_id, Data2)
values (7,333333,'Blah Blah')
insert into dbo.TestingTransactions (transactionNo, Contract_id, Data1)
values (333,333333,'Blah Blah')
insert into dbo.TestingTransactions (transactionNo, Contract_id, Data2)
select 333 ,333333,'Blah Blah' UNION All
select 99999,44443,'Blah Blah' UNION All
select 22, 44443 ,'1' UNION All
select 29, 44443 ,'2' UNION All
select 1, 44443 ,'3'
select * from dbo.TestingTransactions
order by Contract_id,TransactionNo
We are updating single rows, and multiple rows with mixed contract numbers - but the correct TransactionNo overrides the passed in value and we get the expected result:
id transactionNo contract_id Data1 Data2
117 1 44443 NULL Blah Blah
118 2 44443 NULL 1
119 3 44443 NULL 2
120 4 44443 NULL 3
114 1 213123 Blah NULL
115 1 333333 NULL Blah Blah
116 2 333333 Blah Blah NULL
121 3 333333 NULL Blah Blah
I am interested in people's opinions regarding concurrency. I am pretty certain the two CTEs will be treated as a single pass so, I am 99.99% certain the referential integrity will always be maintained.