Insert new record into autonumbered table, and then use the autonumber in another table - sql

I'm writing a stored procedure to insert data from a form into two tables. One table has an autonumbered identity field. I need to insert the data into that table, find the newly created autonumber, and use that number to insert data into another table. So, to boil it down, I have a one-to-many link between the two tables and I need to make sure the identity field gets inserted.
Is this code the best way to do something like this, or am I missing something obvious?
CREATE PROCEDURE [dbo].[sp_Insert_CRT]
(
#TRACKING_ID int,
#CUST_NUM int,
#TRACKING_ITEM_ID int,
#STATEMENT_NUM nvarchar (200) = null,
#AMOUNT numeric (15, 2),
#BBL_ADJUSTED int = NULL,
#PAID_VS_BILLED int = NULL,
#ADJUSTMENT_TYPE int = NULL,
#ENTERED_BY nvarchar (10) = NULL,
#ENTERED_DATE date = NULL,
#AA_STATUS int = NULL
)
AS
BEGIN
-- Insert data into CRT_Main, where Tracking_ID is an autonumber field
INSERT into tbl_CRT_Main
(
-- TRACKING_ID
CUST_NUM
,TRACKING_ITEM_ID
,STATEMENT_NUM
,AMOUNT
)
VALUES
(
-- #TRACKING_ID
#CUST_NUM
,#TRACKING_ITEM_ID
,#STATEMENT_NUM
,#AMOUNT
)
-- Find the newly generated autonumber, and use it in another table
BEGIN TRANSACTION
DECLARE #TrackID int;
SELECT #TrackID = coalesce((select max(TRACKING_ID) from tbl_CRT_Main), 1)
COMMIT
INSERT into tbl_CRT_Admin_Adjustment
(
TRACKING_ID
,BBL_ADJUSTED
,PAID_VS_BILLED
,[ADJUSTMENT_TYPE]
,[ENTERED_BY]
,[ENTERED_DATE]
,AA_STATUS
)
VALUES
(
#TrackID
,#BBL_ADJUSTED
,#PAID_VS_BILLED
,#ADJUSTMENT_TYPE
,#ENTERED_BY
,#ENTERED_DATE
,#AA_STATUS
)
END

SELECT #TrackID = coalesce((select max(TRACKING_ID) from tbl_CRT_Main), 1)
No, don't do this. This will get you the maximum value of TRACKING_ID yes, but that doesn't mean that's the value that was created for your INSERT. If multiple INSERT statements were being run by different connections then very likely you would get the wrong value.
Instead, use SCOPE_IDENTITY to get the value:
SET #TrackID = SCOPE_IDENTITY();
Also, there is no need to wrap the above in an explicit transaction like you have with your SELECT MAX(). Instead, most likely, the entire batch in the procedure should be inside it's own explicit transaction, with a TRY...CATCH so that you can ROLLBACK the whole batch in the event of an error.

Related

Select row just inserted without using IDENTITY column in SQL Server 2012

I have a bigint PK column which is NOT an identity column, because I create the number in a function using different numbers. Anyway, I am trying to save this bigint number in a parameter #InvID, then use this parameter later in the procedure.
ScopeIdentity() is not working for me, it saved Null to #InvID, I think because the column is not an identity column. Is there anyway to select the record that was just inserted by the procedure without adding an extra ID column to the table?
It would save me a lot of effort and work if there is a direct way to select this record and not adding an id column.
insert into Lab_Invoice(iID, iDate, iTotal, iIsPaid, iSource, iCreator, iShiftID, iBalanceAfter, iFileNo, iType)
values (dbo.Get_RI_ID('True'), GETDATE(),
(select FilePrice from LabSettings), 'False', #source, #user, #shiftID, #b, #fid, 'Open File Invoice');
set #invID = CAST(scope_identity() AS bigint);
P.S. dbo.Get_RI_ID('True') a function returns a bigint.
Why don't you use?
set #invId=dbo.Get_RI_ID('True');
insert into Lab_Invoice(iID,iDate,iTotal,iIsPaid,iSource,iCreator,iShiftID,iBalanceAfter,iFileNo,iType)
values(#invId,GETDATE(),(select FilePrice from LabSettings),'False',#source,#user,#shiftID,#b,#fid,'Open File Invoice');
You already know that big id value. Get it before your insert statement then use it later.
one way to get inserted statement value..it is not clear which value you are trying to get,so created some example with dummy data
create table #test
(
id int
)
declare #id table
(
id int
)
insert into #test
output inserted.id into #id
select 1
select #invID=id from #id

SQL Merge Statement - Output into a scalar variable (SQL Server)

I'm getting my head around the MERGE statement in SQL server. I generally use it to insert/update a single row, which I realise isn't the only use, but it's something I seem to do quite often.
But what happens if you want to insert a value of 1, or update to increment the value and output the incremented value eg:
CREATE TABLE [Counter] (
[Key] VARCHAR(255) NOT NULL PRIMARY KEY,
[Value] INT NOT NULL
);
DECLARE #paramKey VARCHAR(255);
SET #paramKey = 'String';
MERGE [Counter] AS targt
USING (Values(#paramKey)) AS source ([Key])
ON (targt.[Key] = source.[Key])
WHEN MATCHED THEN
UPDATE SET Value = Value +1
WHEN NOT MATCHED THEN
INSERT ([Key], Value)
VALUES (source.[Key], 1);
-- but now I want the new value!
Is there a way of doing this? I notice the output clause in https://msdn.microsoft.com/en-gb/library/bb510625.aspx but it doesn't seem to work with scalars (I could output to a single row-ed table variable but that seems wrong):
-- using table variables but seems
DECLARE #paramKey VARCHAR(255), #value int;
SET #paramKey = 'String'
DECLARE #Tab table (
[Value] INT
)
MERGE Counter AS targt
USING (Values(#paramKey)) AS source ([Key])
ON (targt.[Key] = source.[Key])
WHEN MATCHED THEN
UPDATE SET Value = Value +1
WHEN NOT MATCHED THEN
INSERT ([Key], Value)
VALUES (source.[Key], 1)
OUTPUT inserted.[Value] INTO #Tab;
-- can now use #Tab as a single rowed table variable
Is there a better option?

Set Identity ON with a merge statement

I am inserting and deleting elements in a table, as a result, when I want to insert a new element, it takes a new id number, but this id is not taking the last id+1. For example: the last id is 5 and I inserted a 5 elements and deleted after that, the new id will take the value of 11, and I need 6. Here is my code
CREATE TABLE #FC
(
Code varchar(25),
Description varchar(50),
Category varchar(10),
CreatedDate datetime,
LastModifiedDate datetime
);
--Adding just one record
INSERT INTO #FC (Code, Description, Category, CreatedDate, LastModifiedDate)
VALUES ('DELETE_MEMBER', 'Delete Member', 'POLICY', #Now, #Now);
;
SET IDENTITY_INSERT [dbo].[Function_Code] ON;
MERGE
INTO [dbo].[Function_Code] AS T
USING #FC AS S
ON (T.Code = S.Code) AND (T.Description = S.Description) AND(T.Category = S.Category)
WHEN MATCHED THEN
UPDATE SET
[Code] = S.[Code]
, [Description] = S.Description
, [Category] = S.Category
, [CreatedDate] = S.CreatedDate
, [LastModifiedDate] = S.LastModifiedDate
WHEN NOT MATCHED THEN
INSERT (Code, Description, Category, CreatedDate, LastModifiedDate)
VALUES(S.Code, S.Description, S.Category, S.CreatedDate, S.LastModifiedDate)
;
SET IDENTITY_INSERT [dbo].[Function_Code] OFF;
An identity is a technical field that you should not handle yourself. If you want to manage the sequence yourself, then don't use an identity field.
Nevertheless, if you really want to do it, you'll have to reseed the table to the desired value :
DELETE YourTable
DECLARE #n INT;
SELECT #n = MAX(YourId) FROM YourTable
DBCC CHECKIDENT ('YourTable', RESEED, #n)
INSERT YourTable
What you are asking is dangerous. If you make a column an identity column, don't touch it, let sql server do its job. Otherwise you can start getting primary key errors. The identity column is ready to insert 11. You insert six through eleven in your code by running it multiple time and you can get a primary key error next time the identity tries to insert a row into the table.
As Thomas Haratyk said you can reseed your table. Or you can use:
select MAX(YourId) + 1 FROM YourTable
and insert that into your identity column if you are sure you will always insert an id that has already been used by the identity column.
However, if you are commonly overwriting the default identity behavior, it may be better to manage this column yourself because deleting from an identity column results in gaps by default.

inserting values from on table to another in sql server 2008 with one field should be incremented

declare #cid int
set #cid=(select ISNULL(MAX(cid),0)+1 from CustInfo)
insert into CustInfo(CID,CTypeId,CustNo,Regdate,
DOB,CCertID,CCertNo,CompId,PostedBy,PostedOn)
(select #cid,1,0,'2012-9-10',
dob,ccertid,ccertno,0,null,null
from updateCust3)
I have used above code to insert values from table updateCust3 to table UpdateCustInfo.
In this case the CID field should be incremented by one at each insert. I have used the above code but the cid doesn't seem to increase so the error is duplicate value for the primary key. So how can I increase the value of cid? Since the change in table property is not allowed I cannot use identity property.
try this:
declare #cid int
set #cid=(select ISNULL(MAX(cid),0)+1 from CustInfo)
insert into CustInfo(CID,CTypeId,CustNo,Regdate,
DOB,CCertID,CCertNo,CompId,PostedBy,PostedOn)
select #cid+row_number() over (order by (select 0)),1,0,'2012-9-10',
dob,ccertid,ccertno,0,null,null
from updateCust3)
Edit: As MikaelEriksson mentioned in the comment, this has the risk, if you users are simultaneously trying to update the table, it will error out..
have used a temp table to demonstrate. This is a better way to work to avoid errors when used by multiple users
DECLARE #Table TABLE
(
CTypeId INT identity (1,1)
,CustNo int
,DOB datetime
,Regdate datetime
,CCertID int
,CCertNo int
,CompId int
,PostedBy varchar(100)
,PostedOn datetime
)
INSERT #Table
select 1,0,'2012-9-10',
dob,ccertid,ccertno,0,null,null
from updateCust3

Adding max(value)+1 in new row, can this be a problem?

In a SQL Server table I have the following 2 columns:
RowId: primary key, numaric, identity column and auto insert.
MailId: Non key, numaric, non identity and non auto insert.
Mail Id can be duplicate. In case of new MailId I will check max(MailId)+1 and insert it in new row and in case of duplication value will be coming as parameter.
Logic looks fine but here is an issue, I was just considering (yet chacnes of accurance are ver low) In the same time there can be two different new MailId requests. Can this casue logical error ? For example when code checked max(MailId)+1 was 101 and I stored it in a variable but may be before next insert statment executs a new record inserted in table. Now max(MailId)+1 in table will be 102 but value in variable will be 101 ?
Any suggestion please I want to control this error chances as well.
EDIT
(I am not using identity(1,1) because I also have to pass custom values in it)
Why would you use a custom-rolled Identity field when there is such a great one already in SQL Server?
Just use INT Identity (1,1) for your ID field and it will automatically increment each time a row is inserted. It also handles concurrency much better than pretty much anything you could implement manually.
EDIT:
Sample of a manual ID value:
SET IDENTITY_INSERT MyTable ON
INSERT INTO MyTable (IdField, Col1, Col2, Col3,...)
VALUES
(1234, 'Col1', 'Col2', 'Col3',...)
SET IDENTITY_INSERT MyTable OFF
You need to include an explicit field list for the INSERT.
Use OUTPUT on your insert to be sure that you have the right value. If you insert and then select MAX, it is possible that someone could "sneak" in and end up with duplication. That is, you insert MAX + 1, at the same time someone else inserts MAX + 1 then you select MAX and they select MAX, you both have the same value. Whereas if you INSERT and use OUTPUT, you'll be sure that you're unique. This is rarely a problem, but if you have a lot of activity, it can happen (speaking from experience).
EDIT
USE AdventureWorks2008R2;
GO
DECLARE #MyTableVar table(
EmpID int NOT NULL,
OldVacationHours int,
NewVacationHours int,
ModifiedDate datetime);
UPDATE TOP (10) HumanResources.Employee
SET VacationHours = VacationHours * 1.25,
ModifiedDate = GETDATE()
OUTPUT inserted.BusinessEntityID,
deleted.VacationHours,
inserted.VacationHours,
inserted.ModifiedDate
INTO #MyTableVar;
--Display the result set of the table variable.
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate
FROM #MyTableVar;
GO
--Display the result set of the table.
SELECT TOP (10) BusinessEntityID, VacationHours, ModifiedDate
FROM HumanResources.Employee;
GO