Odd SQL Server 2012 IDENTITY issue - sql

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

Related

MSSQL Identity Insert without RESEED

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

SQL Server : setting the value to the next highest id number when the table is not set to auto increment

I am using SQL Server 2008 and some times SQL Server 2012 and I need to do an insert on a pre-existing database where the schema does not seem to have the id column set for auto-increment.
So for example, here is a basic table My_Table that I have:
[Id] [Name]
-------------
1 JOHN
2 BOB
3 SALLY
I need to do an insert to get the MAX value of the last Id and then use this value for my new insert statement. I believe this is the best approach.
So for example the "pseudo syntax" for the insert would be:
INSERT INTO My_Table
VALUES(MAX(Id) + 1, 'MIKE');
How is the best way to do this when the Id is not set in the table schema to auto increment?
Note: I am using Microsoft SQL Server.
Thanks for any advice.
I recommend changing the structure of the table to use an identity column. The table definition should be:
create table my_table (
id int identity(1, 1) primary key,
. . .
);
This is the right solution. If you can't do that, you can express the logic as:
insert to my_table (id, name)
select coalesce(1 + max(id), 0), 'Mike'
from my_table;
However, this suffers from race conditions. Two threads could attempt an insert at the same time and end up with the same id. Avoiding such race conditions is why you want the database to do the work.
If you are in control of all inserts into the table, you can use a sequence as well.
You could create another table with an IDENTITY column:
CREATE TABLE ID_Insert (
ID INT IDENTITY(234, 1) primary key,
Val smallint null
)
(Where your seed value will be MAX(ID) + 1)
Insert any value into this table:
Insert ID_Insert(Val) values(NULL)
Use SCOPE_IDENTITY() to get the ID of the inserted value and use that in your insert into your other table.
NOTE: I have not tested this, but it gets around all the issues raised so far, so any criticism is welcome.
If you can't change the table structure so that the Id is an IDENTITY column, then this is probably your best bet:
SET XACT_ABORT ON;
BEGIN TRANSACTION;
DECLARE #maxId int = (SELECT MAX(id) FROM my_table);
INSERT INTO my_table (id, name)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) + #maxId, name
FROM my_other_table;
COMMIT TRANSACTION;
This works for batch inserts, not only single name inserts.

Reseeding Identity Column to Original Value

Is there any way to reseed an identity column to it's original value? Currently I have this IDENTITY(1,1) as my table definition. I've tried reseeding it using this method:
DBCC CHECKIDENT (Table1, reseed, 1)
But I noticed, that SQL actually reseeds it to current seed + the new seed value, so when I insert new rows it starts the auto-increment count at 2 instead of 1. I've also tried making the new reseed value 0, but then all the rows I insert after that end up being 1 and the column doesn't auto-increment anymore.
By the way, if it helps, I need this column to restart its auto-increment count at zero because I need for the count to restart every month. I have trigger after insert set up for this, but the reseeding is giving me some trouble.
The following statement:
DBCC CHECKIDENT('YourTableName', RESEED, 1);
Tells the database something akin to:
"Hey database! The highest identity value in the YourTableName table is #1, so the next inserted record should use identity value #2."
This explains is why your table starts with identity value 2 when you RESEED it using your statement.
More specifically Microsoft's help for DBCC CHECKIDENT() indicates:
Current identity value is set to the new_reseed_value. If no rows have been inserted to the table since it was created, or all rows have been removed by using the TRUNCATE TABLE statement, the first row inserted after you run DBCC CHECKIDENT uses new_reseed_value as the identity. Otherwise, the next row inserted uses new_reseed_value + the current increment value.
If the table is not empty, setting the identity value to a number less than the maximum value in the identity column can result in one of the following conditions:
If a PRIMARY KEY or UNIQUE constraint exists on the identity column, error message 2627 will be generated on later insert operations into the table because the generated identity value will conflict with existing values.
If a PRIMARY KEY or UNIQUE constraint does not exist, later insert operations will result in duplicate identity values.
It sounds like you want to change your table's initial starting seed value from 1 to 0. With this in mind, you need to modify the table's definition to use:
IDENTITY(0,1);
I hope this helps. Good luck.
Not really an answer, but too long for a comment and does cover the question.
I've also tried making the new reseed value 0, but then all the rows I insert after that end up being 1 and the column doesn't auto-increment anymore.
I can't reproduce this behaviour:
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
CREATE TABLE dbo.T(ID INT IDENTITY(1,1) NOT NULL);
INSERT dbo.T OUTPUT inserted.* DEFAULT VALUES;
-- OUTPUTS 1
DELETE dbo.T;
DBCC CHECKIDENT ('dbo.T', RESEED, 0);
INSERT dbo.T OUTPUT inserted.* DEFAULT VALUES;
-- OUTPUTS 1, I.E. RESEED TO 0 HAS WORKED FINE
INSERT dbo.T OUTPUT inserted.* DEFAULT VALUES;
-- OUTPUTS 2, SHOWS INCREMENT IS WORKING AS EXPECTED
IF OBJECT_ID(N'dbo.T', 'U') IS NOT NULL
DROP TABLE dbo.T;
It is worth noting however that for an empty table the identity will start at 0 if you use 0 as the RESEED value, i.e.
TRUNCATE TABLE dbo.T;
DBCC CHECKIDENT ('dbo.T', RESEED, 0);
INSERT dbo.T OUTPUT inserted.* DEFAULT VALUES;
Will output 0 as the ID inserted.
I believe the only way to do this would be to select all your data into a 2nd table.
Then you can issue the command truncate table Table1.
This will reset the identity column.
Then you re-insert all your data back from temporary holding into the main table (without inserting the old identity).
Example...
NB: run each code block one at a time, checking counts along the way, don't run the whole thing -- and I've left the "dangerous" bits commented out just in case
/** select all columns but NOT the id into a temp location (dont use a # table) **/
SELECT col1, col2, col3, col4, col5
INTO tmpTable
FROM Table1
/** before truncation, double check counts in both match!! **/
-- SELECT COUNT(2) FROM tmpTable
-- SELECT COUNT(2) from Table1
-- TRUNCATE TABLE Table1
INSERT INTO Table1
SELECT col1, col2, col3, col4, col5
FROM tmpTable
/** before you drop temp table, make sure Table1 is ok! **/
-- SELECT COUNT(2) FROM tmpTable
-- SELECT COUNT(2) from Table1
-- DROP TABLE tmpTable
Here is a quick test to demonstrate -- I insert 8 rows, delete one, and re-insert. They are correctly re-numbered.
IF OBJECT_ID('DBO.TEST') IS NOT NULL DROP TABLE TEST
IF OBJECT_ID('DBO.TMP') IS NOT NULL DROP TABLE TMP
CREATE TABLE TEST ([ID] INT IDENTITY(1,1), [SOMESTRING] NVARCHAR(50))
INSERT INTO TEST([SOMESTRING])
SELECT 'HI' UNION
SELECT 'THESE' UNION
SELECT 'ARE' UNION
SELECT 'SOME' UNION
SELECT 'STRINGS' UNION
SELECT 'JUST' UNION
SELECT 'TO' UNION
SELECT 'DEOMONSTRATE'
SELECT *
FROM TEST
DELETE FROM TEST
WHERE SOMESTRING='SOME'
SELECT SOMESTRING
INTO TMP
FROM TEST
TRUNCATE TABLE TEST
INSERT INTO TEST
SELECT SOMESTRING
FROM TMP
SELECT * FROM TEST

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 !
*/

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);