Set Identity ON with a merge statement - sql

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.

Related

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

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.

Trouble updating log with triggers using SQL Server

I am trying to create a trigger with a higher difficulty that would let me create a log after updating rows in alumns table
| Alumn_ID | Name | Courses | Favourite_Course
1 Peter 5 Math
And this would be the result if for example someone updated the number of courses from 5 to 6.
| Log_ID | Alumn_ID | Note | NoteID | Change_Date | Last_Change_Date
1 1 Fields were Updated Note 1 2018-04-23 00:00:00.000 2018-03-23 00:00:00.000
Here is my current trigger
ALTER TRIGGER [LOG]
ON ALUMNS
AFTER UPDATE
AS
BEGIN
DECLARE #Note VARCHAR(50)
DECLARE #Alumn_ID varchar;
SELECT #Alumn_ID= INSERTED.Alumn_ID FROM INSERTED
SET #Note = 'Fields were updated'
INSERT INTO Alumn_Log (Log_ID, Alumn_ID, Note, NoteID, Change_Date)
SELECT Log_ID, i.Alumn_ID, #Note, NoteID, GETDATE(); FROM INSERTED i
END
My problem is:
How do i create the Log ID and the Note ID that i can't take from INSERTED i?
My second problem is, how do i insert the current date? when i try to execute the query it tells me that i can't use that variable in INSERTS.
My third problem, is how can i put the "Last change date"?
Fourth, is there a way to type an specific Note for example if only the name was changed it should say "Name was changed"?
Finally, The Note ID would be Varchar not identity and every note ID needs to be different
This is the current and only error that's preventing me from running the Query:
This is what i get Msg 273, level 16, state 1, procedure Log_Trigger, line 19 [Batch Start Line 0] me time stamp Use INSERT with a list of columns to exclude the timestamp column or insert DEFAULT in the timestamp column.
Here is how I would approach it.
How do i create the Log ID and the Note ID that i can't take from
INSERTED i?
The Log Id can be an AutoIdentity column. An INT column with IDENTITY INSERT.
The Note ID can be an Auto Incremented Computed column (shown in the code below). You would probably need to introduce a new column that serves as a prefix.
My second problem is, how do i insert the current date? when i try to
execute the query it tells me that i can't use that variable in
INSERTS.
GETDATE()?
My third problem, is how can i put the "Last change date"?
You can have a join with INSERTED and get the value from the log from a previous row. Shown in the code.
Fourth, is there a way to type an specific Note for example if only
the name was changed it should say "Name was changed"?
That would depend on finding the nature of the update on which column. This is more of a business question than a technical question.
Finally, The Note ID would be Varchar not identity and every note ID needs to be different
Now, the code (the entire schema)
CREATE TABLE LOG(
Log_ID INT IDENTITY(1, 1) NOT NULL,
Alumn_ID INT,
NOTE VARCHAR(200),
PREFIX VARCHAR(30),
NOTEID AS([PREFIX] + RIGHT('0000000' + CAST(Log_ID AS VARCHAR(7)), 7)) PERSISTED,
CHANGEDATE DATETIME,
LASTCHANGEDATE DATETIME
);
CREATE TABLE ALUMN(
Alumn_ID INT,
NAME VARCHAR(50),
COURSES INT,
FAVORITE_COURSE VARCHAR(50)
);
CREATE TRIGGER[trg_LOG]
ON ALUMN
AFTER UPDATE
AS
BEGIN
DECLARE #Note VARCHAR(50)
--DECLARE #Alumn_ID VARCHAR(50)
DECLARE #Lastchange DATETIME
--SELECT #Alumn_ID = INSERTED.Alumn_ID FROM INSERTED
SET #Note = 'Fields were updated'
SELECT #Lastchange = CHANGEDATE FROM LOG l
INNER JOIN INSERTED i ON l.Alumn_ID = i.Alumn_ID
--INNER JOIN ALUMN
INSERT INTO LOG(Alumn_ID, Note, Prefix, CHANGEDATE, LASTCHANGEDATE)
SELECT i.Alumn_ID, #Note, 'AUP', GETDATE(), #Lastchange FROM INSERTED i
END
how do i insert the current date? when i try to execute the query it
tells me that i can't use that variable in INSERTS.
SELECT Log_ID, i.Alumn_ID, #Note, NoteID, GETDATE(); FROM INSERTED i
Take the semi-colon out of the line above.
SELECT Log_ID, i.Alumn_ID, #Note, NoteID, GETDATE() FROM INSERTED i

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

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

SQL Returned more than one Value

Im sure this is simple and i will kick myself when i find out but ive been sitting on this
problem for the last hour and im getting annoyed with it, could anyone help me.
So i am trying to enter a DeviceId and ConfigurationId Column the values are the primary keys from a Device table and a Configuration table. Yes thats really all the problem is.
I tried using (SELECT ID FROM DeviceId) But that comes up with the error,
Subquery returned more than 1 value
Here is the code i am using the GETDATE()'s are just place holders at the moment and the C.Values are me shredding some XML into the table.
INSERT INTO [Container].[dsc].[DeviceConfiguration]
( DateInserted,
DeviceId,
ConfigurationId,
DateRegistered,
DateRemoved,
OperatingSystemInstallDate,
OperatingSystemSerialNumber
)
SELECT GETDATE(),
<This will need to be DeviceId>,
<This will need to be the ConfigurationId>,
GETDATE(),
GETDATE(),
C.value('#OSInstallDate', 'datetime'),
C.value('#OSSerialNumber', 'nvarchar(125)')
FROM [test].[HardwareComponent] CROSS APPLY
HardwareComponent.ComponentXmlData.nodes('OSData')AS T(C)
WHERE HardwareComponent.TypeId = 7
Edit:
More info sorry,
The 2 columns are set as foreign keys.
ALTER TABLE [dsc].[DeviceConfiguration]
WITH CHECK
ADD CONSTRAINT FK_DeviceConfiguration_Device
FOREIGN KEY (DeviceId)
REFERENCES [dsc].[Device](Id);
GO
ALTER TABLE [dsc].[DeviceConfiguration]
WITH CHECK
ADD CONSTRAINT FK_DeviceConfiguration_Configuration
FOREIGN KEY (ConfigurationId)
REFERENCES [dsc].[Configuration](Id);
GO
You would need to have primary key ID field in DeviceConfiguration table, and than add below lines after your insert query.
Declare #DeviceId int
Declare #ConfigurationId int
SELECT #DeviceId = DeviceId FROM DeviceConfiguration WHERE ID = ##IDENTITY
SELECT #ConfigurationId = ConfigurationId FROM DeviceConfiguration WHERE ID = ##IDENTITY
And modify your select query as below;
SELECT GETDATE(),
#DeviceId,
#ConfigurationId,
GETDATE(),
GETDATE(),
C.value('#OSInstallDate', 'datetime'),
C.value('#OSSerialNumber', 'nvarchar(125)')
FROM [test].[HardwareComponent] CROSS APPLY
HardwareComponent.ComponentXmlData.nodes('OSData')AS T(C)
WHERE HardwareComponent.TypeId = 7
In the end i counted the rows in the previous table i needed,
DECLARE #DeviceId = (SELECT COUNT(Id) FROM .....)
...
...
...
SELECT
GETDATE(),
(ROW_NUMBER()OVER(ORDER BY Id) % #DeviceId) AS DeviceId,
......
And that worked, not quite what i was after but it does the job.