short discussion on first suggested solution using TRIGGER AFTER INSERT
This solution is very problematic!
(1) If two INSERT actions will be execute at the same time then the calculation of S_NO can produce the same values. You might end up with duplicate values.
By default, SQL Server do not use TABLOCK when INSERT is executed unless you use hint or if you're doing certain kinds of bulk load operations. In order to use this solution you must use exclusive lock on the entire table. This will enforce only one insert at a time which means potentially dramatic reduce in performance in some cases.
Moreover, using AFTER TRIGGER means that you have two actions under the same transaction: INSERT+UPDATE. In this case the INSERT can be blocked by the UPDATE of another transaction and the UPDAT can be blocked by the INSERT of this transaction which lead to deadlock.
Note! in small tables you will not probably not noticed it as the lock will be on the page and all your data will be on the same page. But on real cases you will need to confirm a lock of the entire table as I mentioned above.
(2) In addition, what if the someone INSERT a row with Item_Code smaller than the maximum Item_Code in the table?!? The order of the S_NO should be changed according to the new order?!? This is something the OP should clarified.
(3) In @MelissaMa-MSFT original post he used recursive CTE which he change. This is good fix! since it make no sense to execute a query as such which includes loops ON THE ENTIRE TABLE which cost a lot of resource, each time that you insert new row. This calculation of the S_NO value each time from scratch is VERY problematic.
(4) This solution use COUNT() in order to find the next value which make no sense since rows maybe were deleted and the COUNT() will return low value which will produce a lower S_NO value from one that already exists.
Simple example: DELETE a row and next INSERT will create duplicate values
DELETE tbl_GRN_Detail WHERE Item_Code = 1
GO
insert into tbl_GRN_Detail(GRN_ID ,Item_Code,Item_Weight) values (1 ,1,5000)
GO
select * from tbl_GRN_Detail
GO
This can be fix in no time by using MAX instead of using COUNT.
short discussion on second suggested solution using FUNCTION and COMUTED COLUMN
This solution using commuted column does not have the same issues as first solution
With that being said, base the solution on FUNCTION will be execute on each row separately which is a real performance issue.
If you are not using PERSISTED then the data is not really stored in table. This is like a sub query and when you will SELECT multiple rows for example, then it will be executed including the entire aggregation action on the entire table. This might be a killing performance.
Unfortunately you cannot use PERSISTED since the column/function is is non-deterministic (which is another issue). Therefore, why do we need to use COMUTED COLUMN instead of finding the value of the S_NO on the fly when we need it. It is not like we can index by this column since it is not PERSISTED.
So what next?
For most cases I would recommend not to have this information in the table, but on the fly when we need it in the query level.
--> If you must have it stored in the table then you can use "INSTEAD OF TRIGGER" instead of "AFTER TRIGGER".
Why?
(1) Using INSTEAD OF TRIGGER we ignore the original INSERT and we execute a new INSERT query. This means that there is no need for two queries INSERT+DELETE.
(2) We can explicitly lock the table even if the user did not thought about it, simply by using the hint WITH (TABLOCK)
in our new INSERT query. This mean that we can keep consistency but we still get reduce of performance as we do eliminate the parallel INSERTs (as I said, for most cases I would not use any of this request and will suggest to find the S_NO on the fly in the query level).
--> We will NOT use COUNT but MAX in order to find the current maximum value of S_NO.
Here is a simple option using INSTEAD OF TRIGGER
drop table if exists tbl_GRN_Detail
GO
Create table tbl_GRN_Detail (GRN_ID int,Item_Code int,Item_Weight int,S_NO int)
GO
CREATE OR ALTER TRIGGER inserttbl_GRN_Detail ON tbl_GRN_Detail
INSTEAD OF INSERT AS BEGIN
SET NOCOUNT ON;
;WITH MyCTE01 AS (
SELECT t.GRN_ID,MS = ISNULL(MAX(t.S_NO),0)
FROM tbl_GRN_Detail t
GROUP BY t.GRN_ID
),
MyCTE02 AS (
SELECT i.GRN_ID ,i.Item_Code,i.Item_Weight, MS = ISNULL(t.MS,0)
FROM INSERTED i
LEFT JOIN MyCTE01 t ON i.GRN_ID = t.GRN_ID
)
insert into tbl_GRN_Detail WITH (TABLOCK) (GRN_ID ,Item_Code,Item_Weight, S_NO)
SELECT GRN_ID ,Item_Code,Item_Weight, MS + ROW_NUMBER() OVER (PARTITION BY GRN_ID ORDER BY Item_Code)
FROM MyCTE02
END
GO
Again! for most cases I would prefer not to use any "solution" but get the value of S_NO on the fly using my first answer in this thread.