Insert distinct values into table with IDs - sql-server

Let's say I have the following table:
camp_1, camp_2
0, 048
00, 048
000, 042
000, 043
I now want to insert these values into a new table dim_promotion, which should look like this:
PromotionID, CampaignID, CouponID
1, 1, 1
2, 2, 1,
3, 3, 2,
4, 3, 3
I know how I can fill the tables (dim_campaigns and dim_coupons which stand behind CampaignID and CouponID) by doing this:
INSERT INTO [REPORTING].dbo.dim_campaigns
SELECT DISTINCT
camp_2 AS CampaignID
FROM [reporting2].[dbo].[reporting_rawdata_v2]
The primary keys for the respective three tables (dim_coupons, dim_campaigns and dim_promotion) are all set to "Identity Specification = YES"
So how do I then fill dim_promotion? What is the natural order? FIrst derive dim_campaigns and dim_coupons and then isnert this into dim_promotion or the other way round?
EDIT: I have the following DB model (only an excerpt) I am only referring to the yellow fields.
RIght now, we have only large table (rawdatatbl) where all data is stored (CampaignCode, CouponCode, Campaign Name, CouponName). This is not really efficient and that's why I want to compeltely restructure the model (see the screenshot). So, as currently the data does not consist of any IDs at all, I do need IDs to fill the new tables dim_campaigns and dim_coupons. This means: To fill dim_campaigns I would run a SELECT DISTINCT campaign_code on my current rawdatatbl and then Insert this into dim_campaigns (CampaignID is automatically filled by using auto_increment and CampaignName is filled with 'Dummy'). The same I could do for dim_coupons. But how can I then use this data to initialize the Dim_promotion table? Or what is the best process to transform my current data from rawdatatbl into IDs?

one possible way is :-
---- insert your all distinct Campaign data
Insert Into dim_campaigns(CampaignCode,CampaignName)
Select Distinct
cm.CampaignCode
,cm.CampaignName
From rawdatatbl As cm
---- insert your all distinct Coupon data
Insert Into dim_coupons(CouponCode,CouponName)
Select Distinct
c.CouponCode
,c.CouponName
From rawdatatbl As c
Declare #Total Int
,#Inc Int
,#RowId Int
,#CampaignCode Varchar(100)
,#CouponCode Varchar(100)
----Primary keys
,#CampaignId Int
,#CouponId Int
,#PromotionID Int
Select #CampaignCode = ''
,#CouponCode = ''
----loop through one by one and take necessary action
Select #Total = Count(1)
,#Inc = 0
,#RowId = 0
From rawdatatbl As c With (Nolock)
While (#Inc < #Total)
Begin
Select Top 1
#RowId = [TableUniqueId]
,#CampaignCode = c.CampaignCode
,#CouponCode = c.CouponCode
---- others columns
From rawdatatbl As c
Where [TableUniqueId] > #RowId
Order By [TableUniqueId] Asc
Select #CampaignId = dc.CampaignId
From dim_campaigns As dc
Where dc.CampaignCode = #CampaignCode
Select #CouponId = dc.CouponId
From dim_coupons As dc
Where dc.CouponCode = #CouponCode
If (#CampaignId > 0 And #CouponId > 0)
Begin
Insert Into dim_promotion(CampaignID,CouponID)
Select #CampaignId,#CouponId
Select #PromotionID = ##Identity
---- other operation insert/update based on #PromotionID you can do here.
End
Select #Inc = #Inc + 1
,#CampaignCode = ''
,#CouponCode = ''
,#CampaignId = 0
,#CouponId = 0
End

Related

Merging records in a table and update related tables

I have a main table containing users that are linked to various other tables. Sometimes there are duplicates in this main table due to bad imported data and I would like to merge them. See the following tables.
Table: Users
UserID Username FirstName LastName
1 Main John Doe
2 Duplicate John Doo
Table: Records1
RecordID RecordName CreatedUserID UpdatedUserID
1 Test record 1 1 2
2 Test record 2 2 null
3 Test record 3 2 null
CreatedUserID and UpdatedUserID are foreign columns of Users.UserID.
So currently if I want to merge user 1 and 2, I would do it with these SQL statements:
UPDATE Records1 SET UpdatedUserID = 1 WHERE UpdatedUserID = 2
UPDATE Records1 SET CreatedUserID = 1 WHERE CreatedUserID = 2
DELETE FROM Users WHERE UserID = 2
This is just a sample subset but in reality, there are a LOT of related records tables for which I have to add additional SQL-Update statements.
I know I'm probably pushing my luck here, but is there perhaps a way to accomplish the above (update all related tables in a batch and delete the "duplicate" record) rather than updating each foreign field and each related table manually. The users table is basically the base table that links to all other tables so to create individual statements for each table is rather cumbersome so if a shortcut is available, that would be great.
is this helpful.?
Create Table Users(Id int, UserName varchar(10),FirstName varchar(10), LastName Varchar(10))
Create Table Records1(RecordID int, RecordName varchar(20), CreatedUserID int, UpdatedUserID int)
INSERT INTO Users
SELECT 1,'Main','John','Doe' Union All
SELECT 2,'Duplicate','John','Doo' Union All
SELECT 3,'Main3','ABC','MPN' Union All
SELECT 4,'Duplicate','ABC','MPT'
Insert into Records1
SELECT 1,'Test record 1',1,2 Union All
SELECT 2,'Test record 2',2,null Union All
SELECT 3,'Test record 3',2,null Union All
SELECT 1,'Test record 1',3,4 Union All
SELECT 2,'Test record 2',4,null Union All
SELECT 3,'Test record 3',4,null
Select u1.Id as CreatedUserID,U2.id as UpdatedUserID
Into #tmpUsers
from Users u1
JOIN Users u2
--This Conidition Should be changed based on the criteria for identifying Duplicates
on u1.FirstName=u2.FirstName and U2.UserName='Duplicate'
Where u1.UserName<>'Duplicate'
Update r
Set r.UpdatedUserID=u.CreatedUserID
From Records1 r
JOIN #tmpUsers u on r.CreatedUserID=u.CreatedUserID
Update r
Set r.CreatedUserID=u.CreatedUserID
From Records1 r
JOIN #tmpUsers u on r.CreatedUserID=u.UpdatedUserID
Delete from Users Where UserName='Duplicate'
Select * from Users
Select * from Records1
Drop Table #tmpUsers
Since the process of identifying duplicate accounts will be manual then there will (generally) be pairs of accounts to be processed. (I'm assuming that the Inspector can't tick off 15 user accounts as duplicates in your UI and submit the whole lot for processsing.)
A stored procedure like the following may be a good start:
create procedure MergeUsers
#RetainedUserId Int, -- UserId that is being kept.
#VictimUserId Int -- UserId that is to be removed.
as
begin
-- Validate the input.
-- Optional, but you may want some reality checks.
-- (Usernames are probably unique already, eh?)
declare #UsernameMatch as Int, #FirstNameMatch as Int, #LastNameMatch as Int, #EmailMatch as Int;
select
#UsernameMatch = case when R.Username = V.Username then 1 else 0 end,
#FirstNameMatch = case when R.FirstName = V.FirstName then 1 else 0 end,
#LastNameMatch = case when R.LastName = V.LastName then 1 else 0 end,
#EmailMatch = case when R.Email= V.Emailthen 1 else 0 end
from Users as R inner join
Users as V on V.UserId = #VictimUserId and R.UserId = #RetainedUserId;
if #UsernameMatch + #FirstNameMatch + #LastNameMatch + #EmailMatch < 2
begin
-- The following message should be enhanced to provide a better clue as to which user
-- accounts are being processed and what did or didn't match.
RaIsError( 'MergeUsers: The two user accounts should have something in common.', 25, 42 );
return;
end;
-- Update all of the related tables.
-- Using a single pass through each table and updating all of the appropriate columns may improve performance.
-- The case expression will only alter the values which reference the victim user account.
update Records1
set
CreatedUserId = case when CreatedUserId = #VictimId then #RetainedUserId else CreatedUserId end,
UpdatedUserId = case when UpdatedUserId = #VictimId then #RetainedUserId else UpdatedUserId end
where CreatedUserId = #VictimUserId or UpdatedUserId = #VictimUserId;
update Records2
set ...
where ...;
-- Houseclean Users .
delete from Users
where UserId = #VictimUserId;
end;
NB: Left as an exercise is adding try/catch and a transaction in the SP to ensure that the merge is an all or nothing operation.

SQL Server query to update and calculate a field at the same time

My query is as follows:
I have a table Named CALLS as given below:
CALL_NUMBER PART_NUMBER QTY ARRIVAL_DATE
A1 XXX1 5
B2 YYY2 25
C3 ZZZ3 120
D4 ZZZ3 80
E5 ZZZ3 25
And another table SHIPPING as given below:
PART_NUMBER QTY SHIP_DATE ARRIVAL_DATE
XXX1 100 26-Dec 28-Dec
YYY2 5 29-Dec 6-Jan
ZZZ3 200 29-Dec 18-Jan
Now my aim is to put in an arrival date in the calls table based on if the qty required is satisfied by the quantity shipped. If not then, no date should be given in the CALLS table.
I thought the update query below may work:
UPDATE CALLS
SET ARRIVAL_DATE = s.ARRIVAL_DATE
FROM
(
SELECT PART_NUMBER,QTY,ARRIVAL_DATE
FROM SHIPPING
)s
WHERE PART_NUMBER = s.PART_NUMBER
AND QTY < s.QTY
But then how do I subtract the assigned quantities from the remining ones in SHIPPING?
Kindly help me out with this one.
Correct me if wrong. You want to update ARRIVAL_DATE column from Calls table and if the ARRIVAL_DATE column updates then at the same time you want to reduce its QTY value from SHIPPING table.
If yes then this can only be done by looping through each rows in calls table and updating the shipping table after. I have used while loop instead of cursors as mentioned below :
declare #i int = (select count(*) from calls)
declare #j int = 0
declare #t table
(part_number varchar(10),qty int)
while #j <= #i
begin
update a
set a.arrival_date = b.arrival_date
output inserted.part_number,inserted.qty into #t
from
(select *,row_number() over (order by (select 1)) as rn
from calls
) as a
inner join shipping as b
on a.part_number = b.part_number
where a.qty < = b.qty and a.rn = (case when #j = 0 then 1 else #j end)
update a
set a.qty = a.qty - b.qty
from shipping as a
inner join #t as b
on a.part_number = b.part_number
delete from #t
set #j = #j + 1
end
Shouldn't you just have to subtract the QTY in CALLS from the QTY in SHIPPING?
(updated 12/16 with the following as the original update didn't lend itself well to what I'm proposing...take a look)
UPDATE CALLS
SET C.PART_NUMBER = S.PART_NUMBER,
C.QTY = SUM(C.QTY - S.QTY),
C.ARRIVAL_DATE = S.ARRIVAL_DATE
FROM SHIPPING S JOIN CALLS C ON C.PART_NUMBER = S.PART_NUMBER
WHERE C.QTY < S.QTY
Since you are only updating the ones with enough quantities in CALLS then all that satisfy updating the ARRIVAL_DATE field - unless I'm misunderstanding something.
Please, as always with SQL.. test this first somewhere before using on live data. :)

Check for Overlapping date on Insert/Update

I have a table which holds a list of dates and more data for a person. The table should never have any undeleted overlapping rows (Dates overlapping).
Is there a way I can put a check constraint on the table, to ensure that when I update or insert a row, that there's no overlapping details?
Below is a cut down version of my table. It has a deleted flag, and start/end dates. A 'Null' end date means it's ongoing.
I then provide some legal, and some not-so-legal inserts (and why they're legal and illegal).
DECLARE #Test TABLE
(
Id INT NOT NULL IDENTITY(1,1),
PersonID INT NOT NULL,
StartDate DATE NOT NULL,
EndDate DATE NULL,
Deleted BIT NOT NULL
)
INSERT INTO #Test
(PersonId, StartDate, EndDate, Deleted)
SELECT 1, '01-JAN-2015', '15-JAN-2015', 0 UNION ALL -- Valid
SELECT 1, '16-JAN-2015', '20-JAN-2015', 1 UNION ALL -- Valid and deleted
SELECT 1, '18-JAN-2015', NULL, 0 UNION ALL -- Valid
SELECT 2, '01-JAN-2015', NULL, 0 UNION ALL -- Valid.. never ending row.
SELECT 2, '18-JAN-2015', '30-JAN-2015', 0 UNION ALL -- Invalid! Overlaps above record.
SELECT 2, '20-JAN-2015', '30-JAN-2015', 1 UNION ALL -- Valid, as it's deleted (Still overlaps, though)
SELECT 3, '01-JAN-2015', '10-JAN-2015', 0 UNION ALL -- Valid
SELECT 3, '10-JAN-2015', NULL, 0 -- Invalid, as it overlaps the last and first days
SELECT * FROM #Test
I need to make sure that the table doesn't allow overlapping dates for the same person, for undeleted rows.
For the date range check, I will use the "(StartA <= EndB) and (EndA >= StartB)" formula, but unsure how to check this with a constraint, and across multiple rows.
I may need to do it with a Trigger, by checking the inserted.values to the exiting, and somehow, cancel if I find matches?
you cannot use a CHECK Constraint without adding additional columns.
you will have to create a Trigger to check if inserted date ranges are non overlapping. Something like this..
CREATE TRIGGER [dbo].[DateRangeTrigger]
ON [dbo].Test AFTER INSERT, UPDATE
AS
BEGIN
DECLARE #MaxDate DATE = '2999/12/31'
IF EXISTS (SELECT t.StartDate, t.EndDate FROM Test t
Join inserted i
On i.PersonID = t.PersonID
AND i.id <> t.Id
AND(
(i.StartDate > t.StartDate AND i.StartDate < ISNULL(t.EndDate,#MaxDate))
OR (ISNULL(i.EndDate,#MaxDate) < ISNULL(t.EndDate,#MaxDate) AND ISNULL(i.EndDate,#MaxDate) > t.StartDate)
OR (i.StartDate < t.StartDate AND ISNULL(i.EndDate,#MaxDate) > ISNULL(t.EndDate,#MaxDate))
)
WHERE t.Deleted = 0 AND i.Deleted = 0
)
BEGIN
RAISERROR ('Inserted date was within invalid range', 16, 1)
IF (##TRANCOUNT>0)
ROLLBACK
END
END
You can refer to one of these threads for more information
Enforcing unique date range fields in SQL Server 2008
Unique date range fields in SQL Server 2008
Here's a trigger-based approach:
CREATE TRIGGER [dbo].[trigPersonnel_PreventOverlaps]
ON [dbo].[Personnel]
AFTER INSERT, UPDATE
AS
BEGIN
IF EXISTS(
SELECT * FROM DateRange p
INNER JOIN inserted i ON i.PersonID = p.PersonID
AND i.Id != p.Id AND i.Deleted = 0
AND (
(p.StartDate <= i.StartDate
AND (i.StartDate <= p.EndDate OR p.EndDate IS NULL))
OR (p.StartDate <= i.EndDate
AND (i.EndDate <= p.EndDate OR p.EndDate IS NULL))
)
WHERE p.Deleted = 0
)
--RAISEERROR if you want
ROLLBACK
END
Note - it will roll back the whole transaction, so you'll need to perform inserts individually to ensure good ones don't get thrown out.
If you need something to comb through a bulk insert and pick out the bad ones, you'll need something more complex.

T-SQL Grouping Sets of Information

I have a problem which my limited SQL knowledge is keeping me from understanding.
First the problem:
I have a database which I need to run a report on, it contains configurations of a users entitlements. The report needs to show a distinct list of these configurations and a count against each one.
So a line in my DB looks like this:
USER_ID SALE_ITEM_ID SALE_ITEM_NAME PRODUCT_NAME CURRENT_LINK_NUM PRICE_SHEET_ID
37715 547 CultFREE CultPlus 0 561
the above line is one row of a users configuration, for every user ID there can be 1-5 of these lines. So the definition of a configuration is multiple rows of data sharing a common User ID with variable attributes..
I need to get a distinct list of these configurations across the whole table, leaving me just one configuration set for every instance where > 1 has that configuration and a count of instances of that configuration.
Hope this is clear?
Any ideas?!?!
I have tried various group by's and unions, also the grouping sets function to no avail.
Will be very greatful if anyone can give me some pointers!
Ouch that hurt ...
Ok so problem:
a row represents a configurable line
users may be linked to more than 1 row of configuration
configuration rows when grouped together form a configuration set
we want to figure out all of the distinct configuration sets
we want to know what users are using them.
Solution (its a bit messy but the idea is there, copy and paste in to SQL management studio) ...
-- ok so i imported the data to a table named SampleData ...
-- 1. import the data
-- 2. add a new column
-- 3. select all the values of the config in to the new column (Configuration_id)
--UPDATE [dbo].[SampleData]
--SET [Configuration_ID] = SALE_ITEM_ID + SALE_ITEM_NAME + [PRODUCT_NAME] + [CURRENT_LINK_NUM] + [PRICE_SHEET_ID] + [Configuration_ID]
-- 4. i then selected just the distinct values of those and found 6 distinct Configuration_id's
--SELECT DISTINCT [Configuration_ID] FROM [dbo].[SampleData]
-- 5. to make them a bit easier to read and work with i gave them int values instead
-- for me it was easy to do this manually but you might wanna do some trickery here to autonumber them or something
-- basic idea is to run the step 4 statement but select into a new table then add a new primary key column and set identity spec on it
-- that will generate u a bunch of incremental numbers for your config id's so u can then do something like ...
--UPDATE [dbo].[SampleData] sd
--SET Configuration_ID = (SELECT ID FROM TempConfigTable WHERE Config_ID = sd.Configuration_ID)
-- at this point you have all your existing rows with a unique ident for the values combined in each row.
-- so for example in my dataset i have several rows where only the user_id has changed but all look like this ...
--SALE_ITEM_ID SALE_ITEM_NAME PRODUCT_NAME CURRENT_LINK_NUM PRICE_SHEET_ID Configuration_ID
--54101 TravelFREE TravelPlus 0 56101 1
-- now you have a config id you can start to work on building sets up ...
-- each user is now matched with 1 or more config id
-- 6. we use a CTE (common table expression) to link the possibles (keeps the join small) ...
--WITH Temp (ConfigID)
--AS
--(
-- SELECT DISTINCT SD.Configuration_Id --SD2.Configuration_Id, SD3.Configuration_Id, SD4.Configuration_Id, SD5.Configuration_Id,
-- FROM [dbo].[SampleData] SD
--)
-- this extracts all the possible combinations using the CTE
-- on the basis of what you told me, max rows per user is 6, in the result set i have i only have 5 distinct configs
-- meaning i gain nothing by doing a 6th join.
-- cross joins basically give you every combination of unique values from the 2 tables but we joined back on the same table
-- so its every possible combination of Temp + Temp (ConfigID + ConfigID) ... per cross join so with 5 joins its every combination of
-- Temp + Temp + Temp + Temp + Temp .. good job temp only has 1 column with 5 values in it
-- 7. uncomment both this and the CTE above ... need to use them together
--SELECT DISTINCT T.ConfigID C1, T2.ConfigID C2, T3.ConfigID C3, T4.ConfigID C4, T5.ConfigID C5
--INTO [SETS]
--FROM Temp T
--CROSS JOIN Temp T2
--CROSS JOIN Temp T3
--CROSS JOIN Temp T4
--CROSS JOIN Temp T5
-- notice the INTO clause ... this dumps me out a new [SETS] table in my db
-- if i go add a primary key to this and set its ident spec i now have unique set id's
-- for each row in the table.
--SELECT *
--FROM [dbo].[SETS]
-- now here's where it gets interesting ... row 1 defines a set as being config id 1 and nothing else
-- row 2 defines set 2 as being config 1 and config 2 and nothing else ... and so on ...
-- the problem here of course is that 1,2,1,1,1 is technically the same set as 1,1,1,2,1 from our point of view
-- ok lets assign a set to each userid ...
-- 8. first we pull the distinct id's out ...
--SELECT DISTINCT USER_ID usr, null SetID
--INTO UserSets
--FROM SampleData
-- now we need to do bit a of operating on these that's a bit much for a single update or select so ...
-- 9. process findings in a loop
DECLARE #currentUser int
DECLARE #set int
-- while theres a userid not linked to a set
WHILE EXISTS(#currentUser = SELECT TOP 1 usr FROM UserSets WHERE SetId IS NULL)
BEGIN
-- figure out a set to link it to
SET #set = (
SELECT TOP 1 ID
FROM [SETS]
-- shouldn't really do this ... basically need to refactor in to a table variable then compare to that
-- that way the table lookup on ur main data is only 1 per User_id
WHERE C1 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C2 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C3 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C4 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
AND C5 IN (SELECT DISTINCT Configuration_id FROM SampleData WHERE USER_ID = #currentUser)
)
-- hopefully that worked
IF(#set IS NOT NULL)
BEGIN
-- tell the usersets table
UPDATE UserSets SET SetId = #set WHERE usr = #currentUser
set #set = null
END
ELSE -- something went wrong ... set to 0 to prevent endless loop but any userid linked to set 0 is a problem u need to look at
UPDATE UserSets SET SetId = 0 WHERE usr = #currentUser
-- and round we go again ... until we are done
END
SELECT
USER_ID,
SALE_ITEM_ID, ETC...,
COUNT(*) WhateverYouWantToNameCount
FROM TableNAme
GROUP BY USER_ID

How do I get the "Next available number" from an SQL Server? (Not an Identity column)

Technologies: SQL Server 2008
So I've tried a few options that I've found on SO, but nothing really provided me with a definitive answer.
I have a table with two columns, (Transaction ID, GroupID) where neither has unique values. For example:
TransID | GroupID
-----------------
23 | 4001
99 | 4001
63 | 4001
123 | 4001
77 | 2113
2645 | 2113
123 | 2113
99 | 2113
Originally, the groupID was just chosen at random by the user, but now we're automating it. Thing is, we're keeping the existing DB without any changes to the existing data(too much work, for too little gain)
Is there a way to query "GroupID" on table "GroupTransactions" for the next available value of GroupID > 2000?
I think from the question you're after the next available, although that may not be the same as max+1 right? - In that case:
Start with a list of integers, and look for those that aren't there in the groupid column, for example:
;WITH CTE_Numbers AS (
SELECT n = 2001
UNION ALL
SELECT n + 1 FROM CTE_Numbers WHERE n < 4000
)
SELECT top 1 n
FROM CTE_Numbers num
WHERE NOT EXISTS (SELECT 1 FROM MyTable tab WHERE num.n = tab.groupid)
ORDER BY n
Note: you need to tweak the 2001/4000 values int the CTE to allow for the range you want. I assumed the name of your table to by MyTable
select max(groupid) + 1 from GroupTransactions
The following will find the next gap above 2000:
SELECT MIN(t.GroupID)+1 AS NextID
FROM GroupTransactions t (updlock)
WHERE NOT EXISTS
(SELECT NULL FROM GroupTransactions n WHERE n.GroupID=t.GroupID+1 AND n.GroupID>2000)
AND t.GroupID>2000
There are always many ways to do everything. I resolved this problem by doing like this:
declare #i int = null
declare #t table (i int)
insert into #t values (1)
insert into #t values (2)
--insert into #t values (3)
--insert into #t values (4)
insert into #t values (5)
--insert into #t values (6)
--get the first missing number
select #i = min(RowNumber)
from (
select ROW_NUMBER() OVER(ORDER BY i) AS RowNumber, i
from (
--select distinct in case a number is in there multiple times
select distinct i
from #t
--start after 0 in case there are negative or 0 number
where i > 0
) as a
) as b
where RowNumber <> i
--if there are no missing numbers or no records, get the max record
if #i is null
begin
select #i = isnull(max(i),0) + 1 from #t
end
select #i
In my situation I have a system to generate message numbers or a file/case/reservation number sequentially from 1 every year. But in some situations a number does not get use (user was testing/practicing or whatever reason) and the number was deleted.
You can use a where clause to filter by year if all entries are in the same table, and make it dynamic (my example is hardcoded). if you archive your yearly data then not needed. The sub-query part for mID and mID2 must be identical.
The "union 0 as seq " for mID is there in case your table is empty; this is the base seed number. It can be anything ex: 3000000 or {prefix}0000. The field is an integer. If you omit " Union 0 as seq " it will not work on an empty table or when you have a table missing ID 1 it will given you the next ID ( if the first number is 4 the value returned will be 5).
This query is very quick - hint: the field must be indexed; it was tested on a table of 100,000+ rows. I found that using a domain aggregate get slower as the table increases in size.
If you remove the "top 1" you will get a list of 'next numbers' but not all the missing numbers in a sequence; ie if you have 1 2 4 7 the result will be 3 5 8.
set #newID = select top 1 mID.seq + 1 as seq from
(select a.[msg_number] as seq from [tblMSG] a --where a.[msg_date] between '2023-01-01' and '2023-12-31'
union select 0 as seq ) as mID
left outer join
(Select b.[msg_number] as seq from [tblMSG] b --where b.[msg_date] between '2023-01-01' and '2023-12-31'
) as mID2 on mID.seq + 1 = mID2.seq where mID2.seq is null order by mID.seq
-- Next: a statement to insert a row with #newID immediately in tblMSG (in a transaction block).
-- Then the row can be updated by your app.

Resources