SQL Server: Reset Identity If Error Occurred - sql-server

I have the following table:
create table testTable
(
id int identity primary key,
string varchar(256),
constraint testConstraint unique(string)
)
with the following trigger:
create trigger testTrigger
on testTable
for delete as
begin
declare #max_ int
select #max_= max(id) from testTable
if #max_ is null
set #max_ = 0
DBCC CHECKIDENT ('testTable', RESEED, #max_)
end
Executing the following commands:
insert into testTable(string) values ('test') --identity = 1
insert into testTable(string) values ('test') --error thrown because duplicate key but **identity = 2**
insert into testTable(string) values ('test1') --due to erroneous identity increment identity = 3
The first insertion sets identity = 1, the second throws an error because of unique(string) constraint but identity is erroneous set to 2.
The question is how do I make it so that errors do not increment identity?
Is there a do-all be-all feature of SQL Server where the identity is ensured to be produced in a sequential fashion based on what's already in the column/table? Thus all edge cases such as this will be captured.
Thanks in advance (:

As others have mentioned, don't worry about gaps in the identity column coming from DELETEs.
If you absolutely need an increment number with no gaps in it, you could handle that on the SELECT side when needed.
For example, see ROW_NUMBER ... this can be used along with sorting on a timestamp column of your linking (something like SYSUTCDATETIME) to get the exact sequence of addition for rows, ORDER DESC, with ROW_NUMBER() in the query.
This will NOT be tied to the data, because if rows are DELETEd, these ROW_NUMBERS will change.

There shouldn't be any need to prevent gaps in an identity column. It sounds like you might be applying business logic to a surrogate key, which kind of defeats the purpose. You could do a sequence as Sean Lange points out in the comments.

Related

SQL Server Identity column inserts

I support a data replication product. I have a client who is very frustrated that SQL Server can't have a table with an Identity column that BOTH increments automatically when a row is added without providing a value for that column, and at the same time will accept/use a value when it is provided - and I might add, with both of those things happening continuously at a high rate and across hundreds of tables. They point to other databases that apparently can do this.
Everything I see online and my own experimentation seems to indicate that this simply can't be done in SQL Server, but I wanted to put it out there in case I'm just wrong and missing something. My only advice to them so far has been to switch to a Sequence (instead of Identity) and use it as a default value for the column. I've tested that and it works perfectly like they would want, but they are groaning at the idea of doing that for hundreds of tables. Thanks.
The point of an IDENTITY is that SQL Server is in control of it; you let SQL Server manage the value completely. What you really want is a SEQUENCE as a DEFAULT value.
CREATE TABLE dbo.SomeTable (ID int NOT NULL,
SomeColumn varchar(10));
GO
CREATE SEQUENCE dbo.SomeTableID START WITH 1 INCREMENT BY 1;
GO
ALTER TABLE dbo.SomeTable ADD CONSTRAINT PK_SomeTable PRIMARY KEY CLUSTERED (ID);
ALTER TABLE dbo.SomeTable ADD CONSTRAINT DF_SomeTableID DEFAULT NEXT VALUE FOR dbo.SomeTableID FOR ID;
GO
INSERT INTO dbo.SomeTable (SomeColumn)
VALUES ('abc'),('def');
GO
INSERT INTO dbo.SomeTable(ID,SomeColumn)
VALUES(3,'xyz');
GO
--Errors due to 3 already in use, but intended.
INSERT INTO dbo.SomeTable (SomeColumn)
VALUES ('abc');
GO
INSERT INTO dbo.SomeTable (SomeColumn)
VALUES ('def'); --4
GO
--Cleanup
DROP TABLE dbo.SomeTable;
DROP SEQUENCE dbo.SomeTableID;
db<>fiddle

SQL Server creating a Table with an IDENTITY COLUMN - uniqueness

In SQL Server, I have created a Table with an ID column that I have made an IDENTITY COLUMN,
EmployeeID int NOT NULL IDENTITY(100,10) PRIMARY KEY
It is my understanding, when I use the IDENTITY feature, it auto increments the EmployeeID. What I don't know/not sure is:
Is that IDENTITY number created, unique?
Does SQL search the entire column in the table to confirm the number created does not already exist?
Can I override that auto increment number manually?
If I did manually override that number, would the number I enter be checked to make sure it is not a duplicate/existing ID number?
Thanks for any help provided.
Is that IDENTITY number created, unique?
Yes, Identity property is unique
Does SQL search the entire column in the table to confirm the number created does not already exist? \
It need not, what this property does is, just incrementing the old value
Can I override that auto increment number manually?
Yes, you can. You have to use SET IDENTITY_INSERT TABLENAME ON
If I did manually override that number, would the number I enter be checked to make sure it is not a duplicate/existing ID number?
No, that won't be taken care by SQL Server, you will have to ensure you have constraints to take care of this
Below is a simple demo to prove that
create table #temp
(
id int identity(1,1)
)
insert into #temp
default values
go 3
select * from #temp--now id column has 3
set identity_insert #temp on
insert into #temp (id)
values(4)
set identity_insert #temp off
select * from #temp--now id column has 4
insert into #temp
default values
go
select * from #temp--now id column has 5,next value from the last highest
Updating info from comments:
Identity column will allow gaps once you reseed them,also you can't update them

Inserting a identity column value into another table

Good Morning. I have two tables, and one references the other. When I insert into the primary table, the primary key is auto-generated, viz Identity field. I need to insert this value into the second table.
I found out using the OUTPUT clause will give me the just inserted identity value, ans so I tried this.
insert into owners (pId)
insert into personal (firstName)
output inserted.pId
values ('fn')
It doesn't work though. I get an error:
Incorrect syntax near the keyword 'insert'
The personal table is the primary table, and the owners table contains the foreign key.
How can I do the required in SQL Server?
I've got stuck-up here for the past two days...
I think you just have your syntax slightly off - you can definitely take values inserted into the main table and use the OUTPUT clause to insert those into a secondary table.
INSERT INTO dbo.personal(firstName)
OUTPUT INSERTED.pId INTO dbo.owners(pId)
VALUES('fn')
This will insert a new row into personal and set the column firstName to fn. From that insert, the inserted row's identity column pId is then inserted into the other table, owners, as that table's pId column.
See the MSDN documentation on the OUTPUT clause for more details - you can either output any of the inserted values to the console (e.g. SQL Server Mgmt Studio), or you can output those values into a temporary or a permanent table.
Update: as 'dradu' has pointed out - this approach won't work in your case here, since the column in the owners table is part of a FK constraint (I had missed that point from your question). So you'll need to use some other way to do this - probably outputting the necessary information into a temporary table / table variable in your code
Try the following steps
1) Apply transaction level on insertion
2) Get last inserted id using Scope_Identity() function.
When you apply transaction level it will lock your tables and other/same user cannot insert the value in this time.
try this it will work for you.
Since OUTPUT clause cannot be used directly because of the foreign key, you could add the generated IDs into a temporary table, then insert those values into the owners table:
BEGIN TRANSACTION
CREATE TABLE #ids(ID INT)
INSERT INTO personal(firstName)
OUTPUT inserted.pid INTO #ids
SELECT 'A'
UNION SELECT 'B'
INSERT INTO owners(pid)
SELECT ID FROM #ids
COMMIT TRANSACTION
SCOPE_IDENTITY will work too, but it's limited to one value.
You can use the SCOPE_IDENTITY() function to return the identity value inserted.
DECLARE #id INT
INSERT INTO [Personal] (Colums ....) VALUES (this, that, stuff)
SET #id = SCOPE_IDENTITY()
INSERT INTO [Owners] (Colums ....) VALUES (#id ....)
I think Your option is to use SCOPE_IDENTITY() but the other closest to your option is IDENT_CURRENT(‘tablename’) so I thought, I post detail of detail of other identity options as well which might help you to understand your choice and might helpful some other time
##IDENTITY
It returns the last IDENTITY value produced on a connection, regardless of the table that produced the value, and regardless of the
scope of the statement that produced the value.
SCOPE_IDENTITY() It returns the last IDENTITY value produced on
a connection and by a statement in the same scope, regardless of the
table that produced the value.
IDENT_CURRENT(‘tablename’) It returns the last IDENTITY value
produced in a table, regardless of the connection that created the
value, and regardless of the scope of the statement that produced the
value.
Here is one simple example of using SCOPE_IDENTITY() to get recent Identity Value
http://msdn.microsoft.com/en-us/library/ms190315.aspx

Composite key in SQL Server

I am using Sql Server with Composite key. In this composite key contains one identity column i.e Auto incrment value. i want to generate in this column with duplicate values. How can i do this. Please give me a solution for this.
Thanks with Regards
Saravanan.M
The identity column cannot(corrected based on feedback from #AlexKuznetsov) should not have duplicates within the column itself - it is generally meant to be a unique column and a provide non-identifying value for each row.
If you are asking how to put values into the identity column that already exist in another column, you have to do the following:
Set IDENTITY_INSERT Schema.TableName ON
Insert Into TableName (PK1, PK2, IdentityCol1, OtherCol1, OtherCol2)
SELECT FirstCol, SecondCol, SecondCol, OtherColumn1, OtherColumn2
FROM SomeOtherTable
Set IDENTITY_INSERT Schema.TableName OFF
note that PK2 and IdentityCol1 both get the same value
It would be good if you could provide more context around what you are wanting to do and why? There are some good reasons to use composite keys, but if you're already using an identity field, why not make that your primary key?
Your auto-incrementing identity column should be left untouched and should continue to uniquely identify your rows. It is generally good practice to always have an abstract identity column as your primary key.
If you have 2 other values in your data model which uniquely identify your row, they should be in 2 other columns. If one of them is an auto-incrementing number then you can generate the value either in a stored proc which is used for all insertions or in an insert trigger.
Although this is not quite an answer, several answerers have made one and the same mistake, claiming that "You cant have an identity column with duplicates". In fact, identities may easily be not unique if you do not enforce their uniqueness by an index or constraint, as follows:
CREATE TABLE identityTest(i INT IDENTITY(1,1));
GO
INSERT identityTest DEFAULT VALUES;
INSERT identityTest DEFAULT VALUES;
SET IDENTITY_INSERT identityTest ON;
INSERT INTO identityTest(i)
SELECT i FROM identityTest;
SET IDENTITY_INSERT identityTest OFF;
SELECT i FROM identityTest;
i
-----------
1
2
1
2
GO
DROP TABLE identityTest;

How do I create a unique constraint that also allows nulls?

I want to have a unique constraint on a column which I am going to populate with GUIDs. However, my data contains null values for this columns. How do I create the constraint that allows multiple null values?
Here's an example scenario. Consider this schema:
CREATE TABLE People (
Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
Name NVARCHAR(250) NOT NULL,
LibraryCardId UNIQUEIDENTIFIER NULL,
CONSTRAINT UQ_People_LibraryCardId UNIQUE (LibraryCardId)
)
Then see this code for what I'm trying to achieve:
-- This works fine:
INSERT INTO People (Name, LibraryCardId)
VALUES ('John Doe', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');
-- This also works fine, obviously:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Marie Doe', 'BBBBBBBB-BBBB-BBBB-BBBB-BBBBBBBBBBBB');
-- This would *correctly* fail:
--INSERT INTO People (Name, LibraryCardId)
--VALUES ('John Doe the Second', 'AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA');
-- This works fine this one first time:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Richard Roe', NULL);
-- THE PROBLEM: This fails even though I'd like to be able to do this:
INSERT INTO People (Name, LibraryCardId)
VALUES ('Marcus Roe', NULL);
The final statement fails with a message:
Violation of UNIQUE KEY constraint 'UQ_People_LibraryCardId'. Cannot insert duplicate key in object 'dbo.People'.
How can I change my schema and/or uniqueness constraint so that it allows multiple NULL values, while still checking for uniqueness on actual data?
What you're looking for is indeed part of the ANSI standards SQL:92, SQL:1999 and SQL:2003, ie a UNIQUE constraint must disallow duplicate non-NULL values but accept multiple NULL values.
In the Microsoft world of SQL Server however, a single NULL is allowed but multiple NULLs are not...
In SQL Server 2008, you can define a unique filtered index based on a predicate that excludes NULLs:
CREATE UNIQUE NONCLUSTERED INDEX idx_yourcolumn_notnull
ON YourTable(yourcolumn)
WHERE yourcolumn IS NOT NULL;
In earlier versions, you can resort to VIEWS with a NOT NULL predicate to enforce the constraint.
SQL Server 2008 +
You can create a unique index that accept multiple NULLs with a WHERE clause. See the answer below.
Prior to SQL Server 2008
You cannot create a UNIQUE constraint and allow NULLs. You need set a default value of NEWID().
Update the existing values to NEWID() where NULL before creating the UNIQUE constraint.
SQL Server 2008 And Up
Just filter a unique index:
CREATE UNIQUE NONCLUSTERED INDEX UQ_Party_SamAccountName
ON dbo.Party(SamAccountName)
WHERE SamAccountName IS NOT NULL;
In Lower Versions, A Materialized View Is Still Not Required
For SQL Server 2005 and earlier, you can do it without a view. I just added a unique constraint like you're asking for to one of my tables. Given that I want uniqueness in column SamAccountName, but I want to allow multiple NULLs, I used a materialized column rather than a materialized view:
ALTER TABLE dbo.Party ADD SamAccountNameUnique
AS (Coalesce(SamAccountName, Convert(varchar(11), PartyID)))
ALTER TABLE dbo.Party ADD CONSTRAINT UQ_Party_SamAccountName
UNIQUE (SamAccountNameUnique)
You simply have to put something in the computed column that will be guaranteed unique across the whole table when the actual desired unique column is NULL. In this case, PartyID is an identity column and being numeric will never match any SamAccountName, so it worked for me. You can try your own method—be sure you understand the domain of your data so that there is no possibility of intersection with real data. That could be as simple as prepending a differentiator character like this:
Coalesce('n' + SamAccountName, 'p' + Convert(varchar(11), PartyID))
Even if PartyID became non-numeric someday and could coincide with a SamAccountName, now it won't matter.
Note that the presence of an index including the computed column implicitly causes each expression result to be saved to disk with the other data in the table, which DOES take additional disk space.
Note that if you don't want an index, you can still save CPU by making the expression be precalculated to disk by adding the keyword PERSISTED to the end of the column expression definition.
In SQL Server 2008 and up, definitely use the filtered solution instead if you possibly can!
Controversy
Please note that some database professionals will see this as a case of "surrogate NULLs", which definitely have problems (mostly due to issues around trying to determine when something is a real value or a surrogate value for missing data; there can also be issues with the number of non-NULL surrogate values multiplying like crazy).
However, I believe this case is different. The computed column I'm adding will never be used to determine anything. It has no meaning of itself, and encodes no information that isn't already found separately in other, properly defined columns. It should never be selected or used.
So, my story is that this is not a surrogate NULL, and I'm sticking to it! Since we don't actually want the non-NULL value for any purpose other than to trick the UNIQUE index to ignore NULLs, our use case has none of the problems that arise with normal surrogate NULL creation.
All that said, I have no problem with using an indexed view instead—but it brings some issues with it such as the requirement of using SCHEMABINDING. Have fun adding a new column to your base table (you'll at minimum have to drop the index, and then drop the view or alter the view to not be schema bound). See the full (long) list of requirements for creating an indexed view in SQL Server (2005) (also later versions), (2000).
Update
If your column is numeric, there may be the challenge of ensuring that the unique constraint using Coalesce does not result in collisions. In that case, there are some options. One might be to use a negative number, to put the "surrogate NULLs" only in the negative range, and the "real values" only in the positive range. Alternately, the following pattern could be used. In table Issue (where IssueID is the PRIMARY KEY), there may or may not be a TicketID, but if there is one, it must be unique.
ALTER TABLE dbo.Issue ADD TicketUnique
AS (CASE WHEN TicketID IS NULL THEN IssueID END);
ALTER TABLE dbo.Issue ADD CONSTRAINT UQ_Issue_Ticket_AllowNull
UNIQUE (TicketID, TicketUnique);
If IssueID 1 has ticket 123, the UNIQUE constraint will be on values (123, NULL). If IssueID 2 has no ticket, it will be on (NULL, 2). Some thought will show that this constraint cannot be duplicated for any row in the table, and still allows multiple NULLs.
For people who are using Microsoft SQL Server Manager and want to create a Unique but Nullable index you can create your unique index as you normally would then in your Index Properties for your new index, select "Filter" from the left hand panel, then enter your filter (which is your where clause). It should read something like this:
([YourColumnName] IS NOT NULL)
This works with MSSQL 2012
When I applied the unique index below:
CREATE UNIQUE NONCLUSTERED INDEX idx_badgeid_notnull
ON employee(badgeid)
WHERE badgeid IS NOT NULL;
every non null update and insert failed with the error below:
UPDATE failed because the following SET options have incorrect settings: 'ARITHABORT'.
I found this on MSDN
SET ARITHABORT must be ON when you are creating or changing indexes on computed columns or indexed views. If SET ARITHABORT is OFF, CREATE, UPDATE, INSERT, and DELETE statements on tables with indexes on computed columns or indexed views will fail.
So to get this to work correctly I did this
Right click [Database]-->Properties-->Options-->Other
Options-->Misscellaneous-->Arithmetic Abort Enabled -->true
I believe it is possible to set this option in code using
ALTER DATABASE "DBNAME" SET ARITHABORT ON
but i have not tested this
It can be done in the designer as well
Right click on the Index > Properties to get this window
Create a view that selects only non-NULL columns and create the UNIQUE INDEX on the view:
CREATE VIEW myview
AS
SELECT *
FROM mytable
WHERE mycolumn IS NOT NULL
CREATE UNIQUE INDEX ux_myview_mycolumn ON myview (mycolumn)
Note that you'll need to perform INSERT's and UPDATE's on the view instead of table.
You may do it with an INSTEAD OF trigger:
CREATE TRIGGER trg_mytable_insert ON mytable
INSTEAD OF INSERT
AS
BEGIN
INSERT
INTO myview
SELECT *
FROM inserted
END
It is possible to create a unique constraint on a Clustered Indexed View
You can create the View like this:
CREATE VIEW dbo.VIEW_OfYourTable WITH SCHEMABINDING AS
SELECT YourUniqueColumnWithNullValues FROM dbo.YourTable
WHERE YourUniqueColumnWithNullValues IS NOT NULL;
and the unique constraint like this:
CREATE UNIQUE CLUSTERED INDEX UIX_VIEW_OFYOURTABLE
ON dbo.VIEW_OfYourTable(YourUniqueColumnWithNullValues)
In my experience - if you're thinking a column needs to allow NULLs but also needs to be UNIQUE for values where they exist, you may be modelling the data incorrectly. This often suggests you're creating a separate sub-entity within the same table as a different entity. It probably makes more sense to have this entity in a second table.
In the provided example, I would put LibraryCardId in a separate LibraryCards table with a unique not-null foreign key to the People table:
CREATE TABLE People (
Id INT CONSTRAINT PK_MyTable PRIMARY KEY IDENTITY,
Name NVARCHAR(250) NOT NULL,
)
CREATE TABLE LibraryCards (
LibraryCardId UNIQUEIDENTIFIER CONSTRAINT PK_LibraryCards PRIMARY KEY,
PersonId INT NOT NULL
CONSTRAINT UQ_LibraryCardId_PersonId UNIQUE (PersonId),
FOREIGN KEY (PersonId) REFERENCES People(id)
)
This way you don't need to bother with a column being both unique and nullable. If a person doesn't have a library card, they just won't have a record in the library cards table. Also, if there are additional attributes about the library card (perhaps Expiration Date or something), you now have a logical place to put those fields.
Maybe consider an "INSTEAD OF" trigger and do the check yourself? With a non-clustered (non-unique) index on the column to enable the lookup.
As stated before, SQL Server doesn't implement the ANSI standard when it comes to UNIQUE CONSTRAINT. There is a ticket on Microsoft Connect for this since 2007. As suggested there and here the best options as of today are to use a filtered index as stated in another answer or a computed column, e.g.:
CREATE TABLE [Orders] (
[OrderId] INT IDENTITY(1,1) NOT NULL,
[TrackingId] varchar(11) NULL,
...
[ComputedUniqueTrackingId] AS (
CASE WHEN [TrackingId] IS NULL
THEN '#' + cast([OrderId] as varchar(12))
ELSE [TrackingId_Unique] END
),
CONSTRAINT [UQ_TrackingId] UNIQUE ([ComputedUniqueTrackingId])
)
You can create an INSTEAD OF trigger to check for specific conditions and error if they are met. Creating an index can be costly on larger tables.
Here's an example:
CREATE TRIGGER PONY.trg_pony_unique_name ON PONY.tbl_pony
INSTEAD OF INSERT, UPDATE
AS
BEGIN
IF EXISTS(
SELECT TOP (1) 1
FROM inserted i
GROUP BY i.pony_name
HAVING COUNT(1) > 1
)
OR EXISTS(
SELECT TOP (1) 1
FROM PONY.tbl_pony t
INNER JOIN inserted i
ON i.pony_name = t.pony_name
)
THROW 911911, 'A pony must have a name as unique as s/he is. --PAS', 16;
ELSE
INSERT INTO PONY.tbl_pony (pony_name, stable_id, pet_human_id)
SELECT pony_name, stable_id, pet_human_id
FROM inserted
END
You can't do this with a UNIQUE constraint, but you can do this in a trigger.
CREATE TRIGGER [dbo].[OnInsertMyTableTrigger]
ON [dbo].[MyTable]
INSTEAD OF INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Column1 INT;
DECLARE #Column2 INT; -- allow nulls on this column
SELECT #Column1=Column1, #Column2=Column2 FROM inserted;
-- Check if an existing record already exists, if not allow the insert.
IF NOT EXISTS(SELECT * FROM dbo.MyTable WHERE Column1=#Column1 AND Column2=#Column2 #Column2 IS NOT NULL)
BEGIN
INSERT INTO dbo.MyTable (Column1, Column2)
SELECT #Column2, #Column2;
END
ELSE
BEGIN
RAISERROR('The unique constraint applies on Column1 %d, AND Column2 %d, unless Column2 is NULL.', 16, 1, #Column1, #Column2);
ROLLBACK TRANSACTION;
END
END
CREATE UNIQUE NONCLUSTERED INDEX [UIX_COLUMN_NAME]
ON [dbo].[Employee]([Username] ASC) WHERE ([Username] IS NOT NULL)
WITH (ALLOW_PAGE_LOCKS = ON, ALLOW_ROW_LOCKS = ON, PAD_INDEX = OFF, SORT_IN_TEMPDB = OFF,
DROP_EXISTING = OFF, IGNORE_DUP_KEY = OFF, STATISTICS_NORECOMPUTE = OFF, ONLINE = OFF,
MAXDOP = 0) ON [PRIMARY];
this code if u make a register form with textBox and use insert and ur textBox is empty and u click on submit button .
CREATE UNIQUE NONCLUSTERED INDEX [IX_tableName_Column]
ON [dbo].[tableName]([columnName] ASC) WHERE [columnName] !=`''`;

Resources