Hi guys I just have one quick question:
what happens when an insert statement fails on an identity column?
Is it possible that say for example that if I insert a row with an identity column, that identity column will be 1, and insert again but that fails and does not insert and data. Then try to insert again and that identity for that row is now 3?
Any advice will be much appreciated.
Thanks.
It depends on what the cause of the fail on data insert is. If for example the values are invalid (wrong types), then the identity value won't be incremented. However, if the first insert is successful, but is then removed (by a transaction failed and rolled back), then the identity value IS incremented.
-- Next identity value = 1
INSERT INTO Table1 (
field1)
VALUES ('a')
-- Next identity value = 2
BEGIN TRAN
INSERT INTO Table1 (
field1)
VALUES ('b')
-- Next identity value = 3
ROLLBACK TRAN
-- Next identity value = 3, although the insertion was removed.
INSERT INTO Table1 (
field1)
VALUES ('c')
-- Next identity value = 4
The first insert will have identity column value = 1, the second one fails, and the third one will have identity column value = 3.
Just because a column has an IDENTITY specification doesn't necessarily mean it's unique.
If you don't have a unique constraint (or a primary key constraint) on that column, you can definitely insert multiple identical values into rows for that column.
Typically, though, your IDENTITY columns will be the primary key (or at least have a UNIQUE constraint on them) and in that case, attempting to insert a value that already exists will result in an error ("unique constraint violation" or something like that)
In order to be able to insert specific values into an IDENTITY column you need to have the SET IDENTITY_INSERT (table name) ON - otherwise, SQL Server will prevent you from even specifying values for an IDENTITY column.
For illustration - try this:
-- create demo table, fill with values
CREATE TABLE IdentityTest (ID INT IDENTITY, SomeValue CHAR(1))
INSERT INTO IdentityTest(SomeValue) VALUES('A'), ('B'), ('C')
SELECT * FROM IdentityTest -- Output (1)
-- insert duplicate explicit values into table
SET IDENTITY_INSERT IdentityTest ON
INSERT INTO IdentityTest(ID, SomeValue) VALUES(1, 'Z'), (2, 'Y')
SET IDENTITY_INSERT IdentityTest OFF
SELECT * FROM IdentityTest -- Output (2)
-- add unique constraint
TRUNCATE TABLE dbo.IdentityTest
ALTER TABLE IdentityTest ADD CONSTRAINT UX_ID UNIQUE(ID)
INSERT INTO IdentityTest(SomeValue) VALUES('A'), ('B'), ('C')
SET IDENTITY_INSERT IdentityTest ON
INSERT INTO IdentityTest(ID, SomeValue) VALUES(1, 'Z') -- error message (3)
DROP TABLE IdentityTest
Output (1):
ID SomeValue
1 A
2 B
3 C
Output (2):
ID SomeValue
1 A
2 B
3 C
1 Z
2 Y
Error Message (3):
Msg 2627, Level 14, State 1, Line 9
Violation of UNIQUE KEY constraint 'UX_ID'. Cannot insert duplicate key in object 'dbo.IdentityTest'. The duplicate key value is (1).
One way is to prevent the insert if the row already exists.
IF (Not Exists (Select ID From Table Where SomeCol = #SomeVal)
Insert Into Table Values (#SomeVal)
Related
I have the table with the identity column. This is looks like identity column is duplicated. What are all the possibilities that column hold the duplicate values.
Table structure looks like below,
create table Table1(ID INT identity, value varchar(10))
if the column has no unique constraint, it is possible to get duplicate values in it.
One way to do this is like this
SET IDENTITY_INSERT Table1 ON
INSERT INTO Table1 (ID, value)
VALUES (1, 'hello')
SET IDENTITY_INSERT Table1 OFF
Another way is to reseed the table, check the answer of Satheesh on how to do that.
If you dont want that, make this column primary key or create an unique constraint on that column.
how to make a unique index :
CREATE UNIQUE NONCLUSTERED INDEX idx_Table1_ID
ON dbo.Table1(ID)
WHERE ID IS NOT NULL;
The WHERE ID IS NOT NULL; is needed in your example because you allow null values in the ID column, which I do not recommend
I am reasonably sure your table should look like this
Create table Table1 (
ID int identity not null,
Value varchar(10),
constraint PK_Table1_ID primary key (ID)
)
Of course and Identity Column can have Duplicate values.
But since the values are auto-populated by the Table itself, there is no chance that the Duplicates can be created by the system.
But you can add Duplicate values using the INSERT INTO Statement. or if you RESEED the identity column without removing the Existing values the system itself will create Duplicates.
because of The identity column in Not having a UNIQUE constraint by default.
see the below Example
CREATE TABLE Temp
(
SeqNo INT IDENTITY(1,1),
MyStr VARCHAR(10)
)
INSERT INTO Temp
VALUES('A')
Result
SeqNo MyStr
1 A
SET IDENTITY_INSERT TEMP ON
INSERT INTO TEMP(SeqNo,MyStr)
VALUES(1,'B')
SET IDENTITY_INSERT TEMP OFF
Result
SeqNo MyStr
1 A
1 B
Inserted Couple more records
INSERT INTO TEMP
VALUES('C')
INSERT INTO TEMP
VALUES('D')
Result
SeqNo MyStr
1 A
1 B
2 C
3 D
Perform identity Reseed and insert a New value
DBCC CHECKIDENT ('TEMP', RESEED, 1)
INSERT INTO TEMP
VALUES('E')
Final result
SeqNo MyStr
1 A
1 B
2 C
3 D
2 E
I came across a very odd behavior of PSQL (v11) recently:
If I create a table with an identity column, add some records and then delete the last row, this rows key will be reused!
create table "MyTable" (id identity not null, name varchar(50));
insert into "MyTable" (name) values ('Row 1');
insert into "MyTable" (name) values ('Row 2');
insert into "MyTable" (name) values ('Row 3');
select * from "MyTable"
so far so good
id name
=========== ============
1 Row 1
2 Row 2
3 Row 3
then when I delete "Row 3"
delete from "MyTable" where id = 3;
and add a new row
insert into "MyTable" (name) values ('Row 4');
I am surprised to get
id name
=========== ==============
1 Row 1
2 Row 2
!!! ---> 3 Row 4
Is there a way to change the server configuration to prevent this behavior. I would also be glad for any other suggestion to create a reliably unique integer in pervasive SQL.
As per the Autoincrement documentation (here), this seems to be expected behavior. Specifically, in the documentation, it says:
If you indicate that you want the database engine to assign the next
value by entering a zero (0) value in an insert or update, the
database simply finds the highest number, adds 1, and inserts the
resulting value.
Because the highest number is "2" in the example give, the next autoincrement value will be "3".
The only way to prevent this behavior within autoincrement usage would be to specify the value on insert.
If you really want something unique, you can use the UNIQUEIDENTIFIER described in the documentation here.
Here's an example:
create table "MyTable" (id identity not null, gid UNIQUEIDENTIFIER default newid(), name varchar(50));
insert into "MyTable" (name) values ('Row 1');
insert into "MyTable" (name) values ('Row 2');
insert into "MyTable" (name) values ('Row 3');
select * from "mytable";
delete from "MyTable" where id = 3;
insert into "MyTable" (name) values ('Row 4');
select * from "mytable";
And the results:
<<<<<<<<<<<<<<<<<<<<<<<<
id gid name
=========== ==================================== ==================================================
1 1F74A3E5-6EFC-4382-81DA-94F58710AD73 Row 1
2 B5FEFA7A-F85B-486A-A7CA-9615EAD1A601 Row 2
3 E327B2CF-D01D-4039-BB83-CAD966C72131 Row 3
<<<<<<<<<<<<<<<<<<<<<<<<
id gid name
=========== ==================================== ==================================================
1 1F74A3E5-6EFC-4382-81DA-94F58710AD73 Row 1
2 B5FEFA7A-F85B-486A-A7CA-9615EAD1A601 Row 2
3 66F0E55C-52CF-4183-87EE-2C0FDD6E45B6 Row 4
I have a SQL Server 2012 database with two tables:
CREATE TABLE Products
(
Id INT IDENTITY(1, 1) NOT NULL,
Code NVARCHAR(50) NOT NULL,
Name NVARCHAR(50) NOT NULL,
CONSTRAINT PK_Product
PRIMARY KEY CLUSTERED (Id ASC)
);
CREATE TABLE BlockedProductCodes
(
Code NVARCHAR(50) NOT NULL,
ReasonCode INT NOT NULL
CONSTRAINT PK_BlockedProductCodes
PRIMARY KEY CLUSTERED (Code ASC)
);
I want to be able to prevent products being inserted into the Products table if their product code exists in the BlockedProductCodes table.
The only way I could think of doing this was with a BEFORE INSERT trigger:
CREATE TRIGGER trg_Products_BEFORE_INSERT
ON Products
INSTEAD OF INSERT AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT Code
FROM BlockedProductCodes BPC
INNER JOIN inserted I ON BPC.Code = I.Code)
BEGIN
RAISERROR('The product has been blocked!', 16, 1);
END
ELSE
BEGIN
INSERT Product (Id, Code, Name)
SELECT Id, Code, Name
FROM INSERTED
END
SET NOCOUNT OFF;
END
But this caused an error with the identity column:
Cannot insert explicit value for identity column in table 'Products' when IDENTITY_INSERT is set to OFF
Can anyone suggest a way to fix this or a better approach?
Please note, this check is also made at the application level, but I want enforce that at the data table level.
Thanks.
Update: using check constraint
I have tried the following that seems to work..
CREATE FUNCTION dbo.IsCodeBlocked
(
#code nvarchar(50)
)
RETURNS BIT
WITH SCHEMABINDING
AS
BEGIN
DECLARE #ret bit
IF (#Code IN (SELECT Code FROM dbo.BlockedProductCodes))
SET #ret = 1
ELSE
SET #ret = 0
RETURN #ret
END
GO
ALTER TABLE Products
ADD CONSTRAINT CheckValidCode
CHECK (dbo.IsCodeBlocked(Code) = 0);
GO
insert Products (Code, Name) values ('xyz', 'Test #1')
go
insert Products (Code, Name) values ('abc', 'Test #2')
-- Fails with "The INSERT statement conflicted with the
-- CHECK constraint 'CheckValidCode'."
go
I am not sure if it is particularly 'safe' or performant. I will also test out the indexed view approach suggested by Damien.
One way you can implement this is by abusing an indexed view:
CREATE TABLE dbo.Products (
Id INT IDENTITY (1, 1) NOT NULL,
Code NVARCHAR(50) NOT NULL,
Name NVARCHAR(50) NOT NULL,
CONSTRAINT PK_Product PRIMARY KEY CLUSTERED (Id ASC)
);
GO
CREATE TABLE dbo.BlockedProductCodes (
Code NVARCHAR(50) NOT NULL,
ReasonCode INT NOT NULL
CONSTRAINT PK_BlockedProductCodes PRIMARY KEY CLUSTERED (Code ASC)
);
GO
CREATE TABLE dbo.Two (
N int not null,
constraint CK_Two_N CHECK (N > 0 and N < 3),
constraint PK_Two PRIMARY KEY (N)
)
GO
INSERT INTO dbo.Two(N) values (1),(2)
GO
create view dbo.DRI_NoBlockedCodes
with schemabinding
as
select
1 as Row
from
dbo.Products p
inner join
dbo.BlockedProductCodes bpc
on
p.Code = bpc.Code
inner join
dbo.Two t
on
1=1
GO
CREATE UNIQUE CLUSTERED INDEX IX_DRI_NoBlockedCodes on dbo.DRI_NoBlockedCodes (Row)
And now we attempt to insert:
INSERT INTO dbo.BlockedProductCodes (Code,ReasonCode) values ('abc',10)
GO
INSERT INTO dbo.Products (Code,Name) values ('abc','def')
And we get:
Msg 2601, Level 14, State 1, Line 42
Cannot insert duplicate key row in object 'dbo.DRI_NoBlockedCodes' with unique index 'IX_DRI_NoBlockedCodes'. The duplicate key value is (1).
The statement has been terminated.
So if that error message is acceptable to you, this could be one way to go. Note, that if you have a numbers table, you can use that instead of my dummy Two table.
The trick here is to construct the view in such a way so that, if there's ever a match between the Products and BlockedProductCodes tables, we produce a multi-row result set. But we've also ensured that all rows have a single constant column value and there's a unique index on the result - so the error is generated.
Note that I've used my convention of prefixing the table name with DRI_ when it exists solely to enforce an integrity constraint - I don't intend that anyone will ever query this view (indeed, as shown above, this view must always in fact be empty)
Suppose there is a table containing records of students from roll no 1 to 10. If we delete the record at roll number 4&5. Is it possible to enter new records at 4&5? Can anybody please help.
Yes, you can do that using SET IDENTITY_INSERT, given that for the StudentRollID column, IDENTITY(1, 1) is specified. This means that as each row is inserted into the table, SQL Server will automatically increment this value by 1 starting with the number 1.
-- 1 - Retrieve all of the data
-- from the dbo.Student table
SELECT *
FROM dbo.Student;
GO
-- 2 - Delete a single record
DELETE
FROM dbo.Student
WHERE StudentRollID = 4;
GO
-- 3 - Verify the record was deleted
SELECT *
FROM dbo.Student;
GO
-- 4 - Insert the deleted record
-- Insert fails
INSERT INTO [dbo].[Student]
([StudentRollID]
,[FirstName]
,[LastName]
,[CreateDate])
VALUES
(4
,'Bar'
,'Foo'
,'2014-12-04');
GO
-- 5 - Insert the deleted record
-- Insert succeeds
SET IDENTITY_INSERT [dbo].[Student] ON
INSERT INTO [dbo].[Student]
([StudentRollID]
,[FirstName]
,[LastName]
,[CreateDate])
VALUES
(4
,'Bar'
,'Foo'
,'2014-12-04');
SET IDENTITY_INSERT [dbo].[Student] OFF
GO
-- 6 - Verify the data
SELECT *
FROM dbo.Student;
GO
I have two tables with foreign key constraint on TableB on TablesAs KeyA column. I was doing manual inserts till now as they were only few rows to be added. Now i need to do a bulk insert, so my question if i insert multiple rows in TableA how can i get all those identity values and insert them into TableB along with other column values. Please see the script below.
INSERT INTO Tablea
([KeyA]
,[Value] )
SELECT 4 ,'StateA'
UNION ALL
SELECT 5 ,'StateB'
UNION ALL
SELECT 6 ,'StateC'
INSERT INTO Tableb
([KeyB]
,[fKeyA] //Get value from the inserted row from TableA
,[Desc])
SELECT 1 ,4,'Value1'
UNION ALL
SELECT 2 ,5,'Value2'
UNION ALL
SELECT 3 ,6, 'Value3'
You can use the OUTPUT clause of INSERT to do this. Here is an example:
CREATE TABLE #temp (id [int] IDENTITY (1, 1) PRIMARY KEY CLUSTERED, Val int)
CREATE TABLE #new (id [int], val int)
INSERT INTO #temp (val) OUTPUT inserted.id, inserted.val INTO #new VALUES (5), (6), (7)
SELECT id, val FROM #new
DROP TABLE #new
DROP TABLE #temp
The result set returned includes the inserted IDENTITY values.
Scope identity sometimes returns incorrect value. See the use of OUTPUT in the workarounds section.