MSSQL Identity Insert without RESEED - sql

I have 4 databases in 4 different locations. I wrote a program to sync each other. The program is working fine for maximum of 2 locations. I will explain why.
Each database has identity primary key for each table. If I configured ONLY two databases. I can set identity for the first database as IDENTITY(1,1) and another as IDENTITY(-1,-1). But if there are multiple locations then I will have to follow some pattern for each database. Like,
1st - Identity(1,5)
2nd - Identity(2,5)
3rd - Identity(3,5)
........
........
Now my question is, when I synchronizing data in between each database. I use IDENTITY_INSERT ON keyword. After INSERTING rows to another database, SEED value will be changing to the MAX and it will break that pattern.
As an example, Database A has values like this,
1
6
11
16
Database B has values,
2
7
12
17
If I synced data from Database B to A. It(A) will SEED to 17 and the next value would be 22. The pattern will break at this point.
Someone asked the same question in some other forum. Here is the link.
https://www.sqlservercentral.com/forums/topic/identity-insert-without-reseed/page/2
But the solution is not working for me. They suggested to use "REPLICATION=TRUE;" in the connection string to avoid RESEED but that is not working for me.
How to solve this issue? I think I can do this by assigning range for each database but I would preferred to go for a sequence number.
Thanks.

If you have multiple servers with identity columns and you want to combine the data in a single place, then SQL Server offers multiple replication and distribution possibilities.
If I understand your question, then you can set the servers to use different ranges of values. One method is to start the identity columns to different values on each server:
identity(1, 1)
identity(1000000, 1)
This assumes you will never have more than 1000000 rows on each server.
A slightly different way is to put even numbers on one server and odd numbers on the other:
identity(1, 2)
identity(2, 2)
Both of these guarantee that the identity values will not conflict after "merging".

... mark the identity column as "not for replication"
ssms tab_1 (normal connection):
use tempdb
go
drop table if exists dbo.x;
drop table if exists dbo.y;
go
create table dbo.x(id int identity(1,5), a char(1) default('a'));
create table dbo.y(id int identity(1,5) not for replication, a char(1) default('a'));
go
insert into dbo.x(a)
values ('a'), ('a'), ('a');
insert into dbo.y(a)
values ('a'), ('a'), ('a');
go
select 'x' as tbl, * from dbo.x;
select 'y' as tbl, * from dbo.y;
go
ssms tab_2 (..Options-->Additional Connection Parameters--> add Replication=true;) :
set identity_insert dbo.x on;
insert into dbo.x(id, a)
output inserted.*
values (12, 'b');
set identity_insert dbo.x off;
select 'x' as tbl, * from dbo.x;
go
set identity_insert dbo.y on; --..not actually needed
insert into dbo.y(id, a)
output inserted.*
values (12, 'b');
set identity_insert dbo.y off; --..
select 'y' as tbl, * from dbo.y;
go
/*
--error for a "typical" insertion when in "Replication"
--:Explicit value must be specified for identity column in table 'y' either when IDENTITY_INSERT is set to ON
-- or when a replication user is inserting into a NOT FOR REPLICATION identity column.
insert into dbo.y(a)
values ('a')
*/
ssms tab_3 (normal connection):
insert into dbo.x(a)
values('z'), ('z');
go
insert into dbo.y(a)
values('z'), ('z');
go
select 'x' as tbl, * from dbo.x;
select 'y' as tbl, * from dbo.y;
go

Related

Reserve a range of values in a table in SQL Server

I want to reserve a range of values for the system like [1-10,000]. The user values should be inserted after 10,000. eg. There is a table that will have values inserted by the system & will also have values inserted by the user. So, when a system inserts, the id's assigned to it will have to be between 1 to 10,000. If the user inserts a value, the values can be anything greater than 10,000.
You can use check constraint to ensure the condition has been satisfied:
DROP TABLE IF EXISTS [dbo].[StackOverflow];
CREATE TABLE [dbo].[StackOverflow]
(
[Col01] INT
,CONSTRAINT [CH_StackOverflow] CHECK ([Col01] < 1 OR [Col01] > 10000)
);
INSERT INTO [dbo].[StackOverflow] ([Col01])
VALUES (-1);
INSERT INTO [dbo].[StackOverflow] ([Col01])
VALUES (1);
If the column must be populated automatically, you can use IDENTITY column like this:
DROP TABLE IF EXISTS [dbo].[StackOverflow];
CREATE TABLE [dbo].[StackOverflow]
(
[Col01] INT IDENTITY(10001, 1)
,[Col02] NVARCHAR(12)
);
INSERT INTO [dbo].[StackOverflow] ([Col02])
VALUES ('x');
INSERT INTO [dbo].[StackOverflow] ([Col02])
VALUES ('y');
SELECT *
FROM [dbo].[StackOverflow];
Lately, you can use SET IDENTITY_INSERT to add your special records.
If you need more control, you can use trigger - instead of/after INSERT and UPDATE. You can add more logic there - reject the user input or transform the user input. But it feels like an overkill and you need to be careful when creating triggers - always process rows in batches to ensure you are not affecting to much your CRUD performance.

Odd SQL Server 2012 IDENTITY issue

I've never seen this happen before, very odd.
I have a local SQL Server 2012 Express database that I'm developing against. Running a simple suite of tests using the TestDrive plugin and accessing the database with EF v5.
I just ran a test that inserts a record into the database. I had 9 rows in the table going from id 1-9. The next insert and the ID jumped by exactly 10000 !!!!
The Id column goes:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10009
I know failed inserts also increment the ID but I can guarantee that 10,000 didn't fail to insert in the 5 seconds between test runs ...
The table structure is really simple, a bunch of columns and one auto incrementing, identity column of type bigint (long), no SPs, triggers or any other programmatic content.
[Id] [bigint] IDENTITY(1,1) NOT NULL,
Very confusing, has anyone else seen this happening?
This blog post has some additional details. It looks like in 2012, identity is implemented as a sequence. And by default, a sequence has a cache. If the cache is lost you lose the sequence values in the cache.
The proposed solution is to create a sequence with no cache:
CREATE SEQUENCE TEST_Sequence
AS INT
START WITH 1
INCREMENT BY 1
NO CACHE
As far as I can see, the sequence behind an identity column is invisible. You can't change it's properties to disable caching.
To use this with Entity Framework, you could set the primary key's StoredGeneratedPattern to Computed. Then you could generate the identity server-side in an instead of insert trigger:
if exists (select * from sys.sequences where name = 'Sequence1')
drop sequence Sequence1
if exists (select * from sys.tables where name = 'Table1')
drop table Table1
if exists (select * from sys.triggers where name = 'Trigger1')
drop trigger Trigger1
go
create sequence Sequence1
as int
start with 1
increment by 1
no cache
go
create table Table1
(
id int primary key,
col1 varchar(50)
)
go
create trigger Trigger1
on Table1
instead of insert
as
insert Table1
(ID, col1)
select next value for Sequence1
, col1
from inserted
go
insert Table1 (col1) values ('row1');
insert Table1 (col1) values ('row2');
insert Table1 (col1) values ('row3');
select *
from Table1
If you find a better solution, let me know :)
If you will call "Checkpoint" command after each insert query, it will solve your problem.
For more information, please read out Checkpoint in SQL Server

Get IDENTITY value in the same T-SQL statement it is created in?

I was asked if you could have an insert statement, which had an ID field that was an "identity" column, and if the value that was assigned could also be inserted into another field in the same record, in the same insert statement.
Is this possible (SQL Server 2008r2)?
Thanks.
You cannot really do this - because the actual value that will be used for the IDENTITY column really only is fixed and set when the INSERT has completed.
You could however use e.g. a trigger
CREATE TRIGGER trg_YourTableInsertID ON dbo.YourTable
AFTER INSERT
AS
UPDATE dbo.YourTable
SET dbo.YourTable.OtherID = i.ID
FROM dbo.YourTable t2
INNER JOIN INSERTED i ON i.ID = t2.ID
This would fire right after any rows have been inserted, and would set the OtherID column to the values of the IDENTITY columns for the inserted rows. But it's strictly speaking not within the same statement - it's just after your original statement.
You can do this by having a computed column in your table:
DECLARE #QQ TABLE (ID INT IDENTITY(1,1), Computed AS ID PERSISTED, Letter VARCHAR (1))
INSERT INTO #QQ (Letter)
VALUES ('h'),
('e'),
('l'),
('l'),
('o')
SELECT *
FROM #QQ
1 1 h
2 2 e
3 3 l
4 4 l
5 5 o
About the cheked answer:
You cannot really do this - because the actual value that will be used
for the IDENTITY column really only is fixed and set when the INSERT
has completed.
marc_s I suppose, you are not actually right. Yes, He can! ))
The way to solution is IDENT_CURRENT():
CREATE TABLE TemporaryTable(
Id int PRIMARY KEY IDENTITY(1,1) NOT NULL,
FkId int NOT NULL
)
ALTER TABLE TemporaryTable
ADD CONSTRAINT [Fk_const] FOREIGN KEY (FkId) REFERENCES [TemporaryTable] ([Id])
INSERT INTO TemporaryTable (FkId) VALUES (IDENT_CURRENT('[TemporaryTable]'))
INSERT INTO TemporaryTable (FkId) VALUES (IDENT_CURRENT('[TemporaryTable]'))
INSERT INTO TemporaryTable (FkId) VALUES (IDENT_CURRENT('[TemporaryTable]'))
INSERT INTO TemporaryTable (FkId) VALUES (IDENT_CURRENT('[TemporaryTable]'))
UPDATE TemporaryTable
SET [FkId] = 3
WHERE Id = 2
SELECT * FROM TemporaryTable
DROP TABLE TemporaryTable
More over, you can even use IDENT_CURRENT() as DEFAULT CONSTRAINT and it works instead of SCOPE_IDENTITY() for example. Try this:
CREATE TABLE TemporaryTable(
Id int PRIMARY KEY IDENTITY(1,1) NOT NULL,
FkId int NOT NULL DEFAULT IDENT_CURRENT('[TemporaryTable]')
)
ALTER TABLE TemporaryTable
ADD CONSTRAINT [Fk_const] FOREIGN KEY (FkId) REFERENCES [TemporaryTable] ([Id])
INSERT INTO TemporaryTable (FkId) VALUES (DEFAULT)
INSERT INTO TemporaryTable (FkId) VALUES (DEFAULT)
INSERT INTO TemporaryTable (FkId) VALUES (DEFAULT)
INSERT INTO TemporaryTable (FkId) VALUES (DEFAULT)
UPDATE TemporaryTable
SET [FkId] = 3
WHERE Id = 2
SELECT * FROM TemporaryTable
DROP TABLE TemporaryTable
You can do both.
To insert rows with a column "identity", you need to set identity_insert off.
Note that you still can't duplicate values!
You can see the command here.
Be aware to set identity_insert on afterwards.
To create a table with the same record, you simply need to:
create new column;
insert it with null value or other thing;
update that column after inserts with the value of the identity column.
If you need to insert the value at the same time, you can use the ##identity global variable. It'll give you the last inserted. So I think you need to do a ##identity + 1. In this case it can give wrong values because the ##identity is for all tables. So it'll count if the insert occurs in another table with identity.
Another solution is to get the max id and add one :) and you get the needed value!
use this simple code
`SCOPE_IDENTITY()+1
I know the original post was a long while ago. But, the top-most solution is using a trigger to update the field after the record has been inserted and I think there is a more efficient method.
Using a trigger for this has always bugged me. It always has seemed like there must be a better way. That trigger basically makes every insert perform 2 writes to the database, (1) the insert, and then (2) the update of the 2nd int. The trigger is also doing a join back into the table. This is a bit of overhead to have especially for a large database and large tables. And I suspect that as the table gets larger, the overhead of this approach does also. Maybe I'm wrong on that. But, it just doesn't seem like a good solution on a large table.
I wrote a function fn_GetIdent that can be used for this. It's funny how simple it is but really was some work to figure out. I stumbled onto this eventually. It turns out that calling IDENT_CURRENT(#variableTableName) from within a function that is called from the INSERT statements SET value assignment clause acts differently than if you call IDENT_CURRENT(#variableTableName) from the INSERT statement directly. And it makes it where you can get the new identity value for the record that you are inserting.
There is one caveat. When the identity is NULL (ie - an empty table with no records) it acts a little differently since the sys.identity_columns.last_value is NULL. So, you have to handle the very first record entered a little differently. I put code in the function to address that, and now it works.
This works because each call to the function, even within the same INSERT statement, is in it's own new "scope" within the function. (I believe that is the correct explanation). So, you can even insert multiple rows with one INSERT statement using this function. If you call IDENT_CURRENT(#variableTableName) from the INSERT statement directly, it will assign the same value for the newID in all rows. This is because the identity gets updated after the entire INSERT statement finishes processing (within the same scope). But, calling IDENT_CURRENT(#variableTableName) from within a function causes each insert to update the identity value with each row entered. But, it's all done in a function call from the INSERT statement itself. So, it's easy to implement once you have the function created.
This approach is a call to a function (from the INSERT statement) which does one read from the sys.identity_columns.last_value (to see if it is NULL and if a record exists) within the function and then calling IDENT_CURRENT(#variableTableName) and then returning out of the function to the INSERT statement to insert the row. So, it is one small read (for each row INSERTED) and then the one write of the insert which is less overhead than the trigger approach I think. The trigger approach could be rather inefficient if you use that for all tables in a large database with large tables. I haven't done any performance analysis on it compared to the trigger. But, I think this would be a lot more efficient, especially on large tables.
I've been testing it out and this seems to work in all cases. I would welcome feedback as to whether anyone finds where this doesn't work or if there is any problem with this approach. Can anyone can shoot holes in this approach? If so, please let me know. If not, could you vote it up? I think it is a better approach.
So, maybe being holed up due to COVID-19 out there, turned out to be productive for something. Thank you Microsoft for keeping me occupied. Anyone hiring? :) No, seriously, anyone hiring? OK, so now what am I going to do with myself now that I am done with this? :) Wishing everyone safe times out there.
Here is the code below. Wondering if this approach has any holes in it. Feedback welcomed.
IF OBJECT_ID('dbo.fn_GetIdent') IS NOT NULL
DROP FUNCTION dbo.fn_GetIdent;
GO
CREATE FUNCTION dbo.fn_GetIdent(#inTableName AS VARCHAR(MAX))
RETURNS Int
WITH EXECUTE AS CALLER
AS
BEGIN
DECLARE #tableHasIdentity AS Int
DECLARE #tableIdentitySeedValue AS Int
/*Check if the tables identity column is null - a special case*/
SELECT
#tableHasIdentity = CASE identity_columns.last_value WHEN NULL THEN 0 ELSE 1 END,
#tableIdentitySeedValue = CONVERT(int, identity_columns.seed_value)
FROM sys.tables
INNER JOIN sys.identity_columns
ON tables.object_id = identity_columns.object_id
WHERE identity_columns.is_identity = 1
AND tables.type = 'U'
AND tables.name = #inTableName;
DECLARE #ReturnValue AS Int;
SET #ReturnValue = CASE #tableHasIdentity WHEN 0 THEN #tableIdentitySeedValue
ELSE IDENT_CURRENT(#inTableName)
END;
RETURN (#ReturnValue);
END
GO
/* The function above only has to be created the one time to be used in the example below */
DECLARE #TableHasRows AS Bit
DROP TABLE IF EXISTS TestTable
CREATE TABLE TestTable (ID INT IDENTITY(1,1),
New INT,
Letter VARCHAR (1))
INSERT INTO TestTable (New, Letter)
VALUES (dbo.fn_GetIdent('TestTable'), 'H')
INSERT INTO TestTable (New, Letter)
VALUES (dbo.fn_GetIdent('TestTable'), 'e')
INSERT INTO TestTable (New, Letter)
VALUES (dbo.fn_GetIdent('TestTable'), 'l'),
(dbo.fn_GetIdent('TestTable'), 'l'),
(dbo.fn_GetIdent('TestTable'), 'o')
INSERT INTO TestTable (New, Letter)
VALUES (dbo.fn_GetIdent('TestTable'), ' '),
(dbo.fn_GetIdent('TestTable'), 'W'),
(dbo.fn_GetIdent('TestTable'), 'o'),
(dbo.fn_GetIdent('TestTable'), 'r'),
(dbo.fn_GetIdent('TestTable'), 'l'),
(dbo.fn_GetIdent('TestTable'), 'd')
INSERT INTO TestTable (New, Letter)
VALUES (dbo.fn_GetIdent('TestTable'), '!')
SELECT * FROM TestTable
/*
Result
ID New Letter
1 1 H
2 2 e
3 3 l
4 4 l
5 5 o
6 6
7 7 W
8 8 o
9 9 r
10 10 l
11 11 d
12 12 !
*/

SQL: Will setting IDENTITY_INSERT ON disable updating the table's identity table?

I'm currently working on a data migration project and for performance-related issues, I want to predefine a set of identities rather than letting the tables generate them.
I found it's not easy to add the identity property to a column, so I want to use IDENTITY_INSERT ON statement.
My question is: would this disable updates to the table's identity table (which is impacting performance), or do I need to truly remove the identity property of the column(s)?
It's very common for data migration scripts to have something like:
SET IDENTITY_INSERT [MyTable] ON
INSERT INTO [MyTable] ...
INSERT INTO [MyTable] ...
INSERT INTO [MyTable] ...
...
SET IDENTITY_INSERT [MyTable] OFF
While enabled, the field will not auto-increment for other inserts.
IDENTITY_INSERT has session scope, so only your session will be able to insert to the identity row explicitly. AND only one table in a session can have IDENTITY_INSERT ON at a time.
So what about performance? I don't actually have an answer to you question, but I have some code that should give you an answer. It's a modified version of something I found here:
/* Create a table with an identity value */
CREATE TABLE test_table
(
auto_id INT IDENTITY(1, 1),
somedata VARCHAR(50)
)
GO
/* Insert 10 sample rows */
INSERT INTO test_table
SELECT 'x'
GO 10
/* Get the current identity value (10) */
SELECT Ident_current('test_table') AS IdentityValueAfterTenInserts
GO
/* Disable the identity column, insert a row, enable the identity column. */
SET identity_insert test_table ON
INSERT INTO test_table(auto_id, somedata)
SELECT 50, 'x'
SET identity_insert test_table OFF
GO
/* Get the current identity value (50) */
SELECT Ident_current('test_table') AS IdentityValueAfterIdentityInsertWithIdentityEnabled
GO
/* Disable the identity column, insert a row, check the value, then enable the identity column. */
SET identity_insert test_table ON
INSERT INTO test_table(auto_id, somedata)
SELECT 100, 'x'
/*
Get the current identity value (?)
If the value is 50, then the identity column is only recalculated when a call is made to:
SET identity_insert test_table OFF
Else if the value is 100, then the identity column is recalculated constantly and your
performance problems remain.
*/
SELECT Ident_current('test_table') AS IdentityValueAfterIdentityInsertWithIdentityDisabled
SET identity_insert test_table OFF
GO
/* Get the current identity value (100) */
SELECT Ident_current('test_table') AS IdentityValueAfterIdentityInsertWithIdentityEnabled
GO
DROP TABLE test_table
I don't have a SQL SERVER handy to run this on, so let me know how it goes. Hope it helps.
Something to note about Identity columns with SET IDENTITY_INSERT ON.
Just checked on SQL 2012 you can't insert using the built in auto identity if you turn the option on.
Quick test below...
BEGIN TRY DROP TABLE #T END TRY BEGIN CATCH END CATCH;
CREATE TABLE #T (id int IDENTITY, Name varchar(50));
INSERT INTO #T (Name) VALUES ('Darren'); -- built in Identity format
SET IDENTITY_INSERT #T ON;
INSERT INTO #T (id, Name) VALUES (5, 'Jerry'); -- explicit format of identity
INSERT INTO #T (Name) VALUES ('Thomas'); -- TRY to use built in format
SET IDENTITY_INSERT #T OFF;
SELECT * FROM #T;
results with ...
(1 row(s) affected)
(1 row(s) affected)
Msg 545, Level 16, State 1, Line 11
Explicit value must be specified for identity column in table '#T__________________________________________________________________________________________________________________000000C34998' either when IDENTITY_INSERT is set to ON or when a replication user is inserting into a NOT FOR REPLICATION identity column.
(2 row(s) affected)

It seems SELECT MAX([ColumnName]) returns value from deleted records if [ColumnName] is auto increment int type

In SQL Server or maybe other databases, if a column is auto increment int type, the table can remember the highest value even if the record has been deleted.Let's say you have a table whose with some records previously deleted, and then you use SELECT MAX([ColumnName]), it might return a value associated with a deleted record. Has anyone seen scenarios like this?
This will not happen with SELECT MAX (as can be seen from the below script) in Sql Server.
CREATE TABLE TEST_TABLE (
ID INT IDENTITY (1,1),
Val INT
)
INSERT INTO TEST_TABLE (Val) SELECT 1
INSERT INTO TEST_TABLE (Val) SELECT 2
INSERT INTO TEST_TABLE (Val) SELECT 3
INSERT INTO TEST_TABLE (Val) SELECT 4
INSERT INTO TEST_TABLE (Val) SELECT 5
DELETE FROM TEST_TABLE WHERE Val >= 3
SELECT MAX(ID) MaxID
FROM TEST_TABLE
DROP TABLE TEST_TABLE
Output
MaxID
2
If you were looking for the Tables Identity Value, see #AdaTheDev answer.
Yes, that's by design. Used IDENTITY values are not re-assigned when a record is deleted.
To get the last identity value assigned, you'd need to use IDENT_CURRENT, which (quote)
Returns the last identity value generated for a specified table or view. The last identity value generated can be for any session and any scope.
e.g.
SELECT IDENT_CURRENT('TableName')
I'd say that if you're taking the MAX() on an identity/auto-increment column, then you're doing something wrong.
As #astander says, it's not behaviour I've observed under SQL Server.
You should treat such identifiers as opaque - that they happen to be structured as an integer is a mere convenience to you, with well known storage requirements, clustering behaviours, etc.
It is dev (not sysadmin's or DBA's) board - don't do this in production!
To check what is the next ID:
DBCC CHECKIDENT ('YourTableName', NORESEED)
Note that your next ID will be #MaxID+1:
DECLARE #MaxID INT;
SELECT #MaxID = COUNT(ID) FROM YourTableName;
DBCC CHECKIDENT('YourTableName', RESEED, #MaxID);