DTS Package Terminates because of Duplicate Key Row - sql-server

We have an old DTS Package that our SQL 2000 Server uses to push Employee records out to machines on our manufacturing floor.
Recently, we upgraded one of the machines, and it now is running SQL 2008 Express.
We have reconfigured the DTS Package to push the Employee records out to this new Server, but now we are getting this error message:
FETCH_EMPLOYEES:
The statement has been terminated. Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_tblUsers_OpID'.
If I remote into our SQL 2000 Server, I can Right-Click to execute each step of the DTS Package in succession with NO errors.
So, I log onto this machine's SQL 2008 Express instance to see if I can figure anything out.
Now I am looking at the FETCH_EMPLOYEES stored procedure:
PROCEDURE [dbo].[FETCH_EMPLOYEES] AS
DECLARE #OpID varchar(255)
DECLARE #Password varchar(50)
DECLARE Employee_Cursor CURSOR FOR
SELECT OpID, Password
FROM dbo.vw_Employees
OPEN Employee_Cursor
FETCH NEXT FROM Employee_Cursor
INTO #OpID,#Password
WHILE ##FETCH_STATUS = 0
BEGIN
insert into dbo.Users (OpID,Password,GroupID)
VALUES (#OpID,#Password,'GROUP01')
FETCH NEXT FROM Employee_Cursor
INTO #OpID,#Password
END
CLOSE Employee_Cursor
DEALLOCATE Employee_Cursor
I don't really understand Cursors, but I can tell that the data is being pulled from a view called vw_Employees and being inserted into the table dbo.Users.
The view vw_Employees is simple:
SELECT DISTINCT FirstName + ' ' + LastName AS OpID, Num AS Password
FROM dbo.EmployeeInfo
WHERE (Num IS NOT NULL) AND (FirstName IS NOT NULL)
AND (LastName IS NOT NULL) AND (Train IS NULL OR Train <> 'EX')
So, now it seems the problem must be from the table dbo.Users.
I did not see anything particularly attention getting with this, so I scripted this table using a CREATE TO Query Editor and got this information that I don't really understand:
CREATE TABLE [dbo].[Users](
[ID] [int] IDENTITY(1,1) NOT NULL,
[OpID] [nvarchar](255) NOT NULL,
[Password] [nvarchar](50) NOT NULL,
[GroupID] [nvarchar](10) NOT NULL,
[IsLocked] [bit] NOT NULL,
CONSTRAINT [PK_tblUsers] PRIMARY KEY CLUSTERED
(
[ID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[Users] WITH CHECK ADD CONSTRAINT [FK_tblUsers_tblGroups] FOREIGN KEY([GroupID])
REFERENCES [dbo].[Groups] ([GroupID])
GO
ALTER TABLE [dbo].[Users] CHECK CONSTRAINT [FK_tblUsers_tblGroups]
GO
ALTER TABLE [dbo].[Users] ADD CONSTRAINT [DF_tblUsers_IsLocked] DEFAULT ((0)) FOR [IsLocked]
GO
OK, I feel the problem is somewhere in this table definition, but I don't really understand what it is doing (after creating the basic table).
It has a CONSTRAINT section with lots of variables I do not understand, then it is altering these tables to add FOREIGN KEY and CONSTRAINTS.
My Question: Could someone help me understand what the error is telling me (other than there is some duplicate key violation).
What column could be throwing a duplicate key violation?
Did I include enough data and screenshots?
UPDATE:
Based on Comments, it sounds like this screenshot is needed.
In the Users table, there is a list of Indexes, and one called IX_tblUsers_OpID says it is Unique and Non-Clustered.
I think we have eliminated duplicate Op_ID values on our source data table EmployeeInfo by finding all of them with this script:
select num as 'Op_ID', count(num) as 'Occurrences'
from employeeInfo
group by num
having 1<count(num);
This should have gotten rid of all of my duplicates. Right?
We purchase manufacturing machines that come configured with PCs for storing local data. They supply these script I have posted up, so I cannot comment on why they picked what they did. We just run a job that pulls the data onto our server.

Having columns with unique values has always been of high value on any dataset. This constrain can be added to any column, or index.
The error you receive is very clear and very specific. It literally gives the answer.
The statement has been terminated. Cannot insert duplicate key row in object 'dbo.Users' with unique index 'IX_tblUsers_OpID'.
It says "NO duplicates.... UNIQUE index..." then it tells you the name of the constrain "IX_tblUsers_OpID".
Now keeping that in mind, you are trying to insert in that column values you craft on the fly by concatenating two strings; name, plus last name.
What are the chances to come up with two of them being "John Smith"? High, very high!
Possible solutions:
You may remove the constrain and allow duplicates.
Modify the query so the values that tries to insert are -indeed- unique.
Use 'WITH (IGNORE_DUP_KEY = ON)' Reference: index_option (Transact-SQL)

Another guy here at work found this hidden feature, which solves the immediate problem but could cause other unknown issues.
In the Users table designer view, we can Right-Click on the OpID column, select Indexes/Keys..., locate this created IX_tblUsers_OpID key and change it's Is Unique value:
That seemed to have made it so that the DTS Package will run, and that is what we have going on right now.
I went back to the original EmployeeInfo table on our SQL 2000 Server to check for duplicate OpID values using this script:
select FirstName + ' ' + LastName as 'OpID',
Count(FirstName + ' ' + LastName) as 'Occurrences'
from EmployeeInfo
group by FirstName + ' ' + LastName
having 1 < count(FirstName + ' ' + LastName)
...but there were no records returned.
I'm not sure why the DTS Package was failing or why we had to turn off the Unique feature.
If anyone, at some time down the road, comes up with a better fix for this, please post!

Related

MS SQL removes newly inserted row automatically straight after insert

I have a weird issue. In the MSSQL database I have multiple tables in that database but only two of them does this:
I insert a new row
get row count (which is increased by 1)
get row count again within seconds (this time it's decreased) and the new row is not in the table anymore
The query i use to insert row and get count:
INSERT INTO [dbo].[CSMobileMessages]
([MessageSID],[IssueID],[UserSent])
VALUES
('213',0,'blabla')
SELECT count([IDx])
FROM [dbo].[CSMobileMessages]
The SQL query returns "1 row affected" and i even get back the new row ID as well from the identity column. No errors at all. I checked in profiler which states 1 row inserted successfully and nothing else happened.
The table has no triggers. Index only on identity field (IDx), user used is "sa" with full access. Tried with different user but same happens.
The table is called "CSMobileMessages" so I created a new table:
CREATE TABLE [dbo].[CSMobileMessages2](
[IDx] [int] IDENTITY(1,1) NOT NULL,
[MessageSID] [varchar](50) NULL,
[IssueID] [int] NOT NULL,
[UserSent] [varchar](50) NULL,
CONSTRAINT [PK_CSMobileMessages2] PRIMARY KEY CLUSTERED
(
[IDx] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
ALTER TABLE [dbo].[CSMobileMessages2] ADD CONSTRAINT [DF_CSMobileMessages2_IssueID] DEFAULT ((0)) FOR [IssueID]
GO
I insert 1000 rows into the new table and it worked. So i delete the old table (CSMobileMessages) and rename the new table from CSMobileMessages2 to CSMobileMessages.
As soon as i do that, the inserted rows gets deleted and i will get the exact same row count for the new table what i had with the old one. Also i can't insert rows anymore. No services or any other software touches this table. However if i restart the server i can insert 1 new row and after it starts happening again.
Edit:
I uses MSSMS and connect to the database remotely but i tried locally on the server as well and same happens. A service used this table but i disabled it when this started few days ago. Before that the service ran happily for 1 year with no issue. I double checked to make sure, the service is turned off and no one connects to that table but me.
Has anyone ever seen this issue before and knows what causes it?
I gave up and the whole database was reset from few days old back up as a last try and it's working now as it supposed to. I don't set this question as answered because even it's fixed the problem I still have no idea what happened exactly as within my 20+ yrs coding i never seen anything like this before.
Thanks for everyone who tried to help with ideas!

Why does this insert statement not match the table?

I am using a Visual Studio 2015 Database Project to attempt to insert rows into database tables which have been created by the project. The Insert sql works in the same table in SSMS, but not when run via the project.
I get the error: Column name or number of supplied values does not match the table definition.
TSQL insert script:
if not exists (select top 1 1 from [dbo].[PricingGroupTypes]) begin
INSERT INTO [dbo].[PricingGroupTypes]
([Name])
VALUES
('User')
,('Product Format')
print 'Pricing Group Types added'
end
Table Definition (in DB project):
CREATE TABLE [dbo].[PricingGroupTypes] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (250) NOT NULL,
CONSTRAINT [PK_PricingGroupTypes] PRIMARY KEY CLUSTERED ([Id] ASC)
);
Table Definition (via ssms):
USE [Toyland]
GO
/****** Object: Table [dbo].[PricingGroupTypes] Script Date: 11/14/2016 11:05:05 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE TABLE [dbo].[PricingGroupTypes](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](250) NOT NULL,
CONSTRAINT [PK_PricingGroupTypes] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
Other inserts in the same script do not seem to fail, but the overall script will roll back so the database is empty after execution. And, like I said, the exact same script WORKS when I run it in SQL Server Management Studio.
Perhaps there's some Visual Studio setting I'm missing, but this is about the most straightforward bit of script in the entire project, so you can see why I'm perplexed.
In the end, there was a comment here suggesting it was perhaps a problem with some other part of my overall script generated by the project. This turned out to be the case. I have now resolved the issue and the code in the original question is unchanged.
Unfortunately I don't know, anymore, which other part of the big script was the culprit, since I had to go through and make a few other changes to get everything running smoothly, but there did not appear to be any problems with the SQL in my original question.
if not exists (select top 1 1 from [dbo].[PricingGroupTypes]) begin
INSERT INTO [dbo].[PricingGroupTypes]
([Name])
VALUES
('User')
,('Product Format')
print 'Pricing Group Types added'
end
Your problem is that you're trying to insert two values into one column. Trying to insert 'User' and 'Product Format' into [Name] will kick back an error because it needs a destination for product format. Update your insert statement to include the second column and it should go.

SQL Server getting unique records

I'm starting a project where I need to ensure that a large volume of users can obtain a promotional code for multiple promotions.
The codes have a monetary value attached to them so it is vital that only one code goes out to each user and no two users can ever receive the code.
My plan so far is to create a table in SQL Server known as code_pool and do a bulk insert of codes each time.
SQL Server 2005 table would look like this...
[id](int pk), [promo_code] varchar(150), [promotion_id](int fk)
Users would then retreive each code using a stored proc that would get the first record from the table for that promotion and then delete (or update) the record before returning the code as the result of the procedure.
My question is how do I ensure that the record is properly locked so that only one user may ever obtain each record and that no two users accessing the proc concurrently will ever receive the same code?
Do I need to lock the table/records and if so how would this stack up in a busy production environment?
One very handy built in data type for generating unique codes that are not easily guessible is the uniqueidentifier data type. You can use this to generate a unique code by making it have an auto-generated value (using the newid() function). Because GUIDs are in HEX and not generated sequentially unlike identity columns, it isn't possible to predict what codes have or will be generated which will make your process less vulnerable to someone just trying codes in sequence. The number of possible uniqueidentifiers is very large.
I've made the assumption that you will only want one promo code per person for each of your promos. The way you can do this in your database is by having a table, my example calls it PromoTest, which has a primary key on both of these columns which will ensure they remain unique. I didn't add a concept of 'Used' to indicate if the person has used the code but that's quite trivial to do.
To create your table with the primary key constraint and the auto-generated value run the following:
CREATE TABLE [dbo].[PromoTest](
[personid] [bigint] NOT NULL, [promocategory] [int] NOT NULL,
[promocode] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_PromoTest] PRIMARY KEY CLUSTERED (
[personid] ASC,
[promocategory] ASC )
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS
= ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
GO
ALTER TABLE [dbo].[PromoTest] ADD CONSTRAINT [DF_PromoTest_promocode] DEFAULT (newid()) FOR [promocode]
To then have a stored procedure that inserts a new promo code or selects the existing one is quite trivial, and due to the primary key constraint you cannot physically insert two codes of the same type for the same person.
The stored procedure can be defined as follows:
CREATE PROCEDURE GetOrCreatePromoCode
-- Add the parameters for the stored procedure here
#PersonId bigint,
#PromoCategory int,
#PromoCode uniqueidentifier OUT
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
IF (NOT EXISTS(SELECT PromoCode FROM PromoTest WHERE personid = #PersonId AND promocategory = #PromoCategory))
BEGIN
INSERT INTO PromoTest (personid, promocategory) VALUES (#PersonId, #PromoCategory)
END
SET #PromoCode = (SELECT PromoCode FROM PromoTest WHERE personid = #PersonId AND promocategory = #PromoCategory)
END
GO
don't you want to add a column e.g. in_use (int)? when you generate new promocode, in_use=0, when your stored proc obtains non-used promo code it selects first code where in_use = 0, and then updates it to 1
Why not use something similar, but like this:
Table UsedCodes
[id] int identity PK,
[userId] whatever,
[PromoId] int FK
Table Promotions
[PromoId] int pk,
[PromoCode] nvarchar
When a user gets a promo code, you would insert a value into used codes, and the promotion code delivered to them would be a concatenation of the promo code and the ID in the used codes table.
You could then also enforce a unique constraint on UserId | PromoId on the used codes table, to ensure only a single code per promo per user.
This has the advantage of retaining a record of the codes used, and reduces the complexity of needing your bulk insert, which could potentially introduce dups by accident. It also has the advantage of requiring no locking...
John P has given you an excellent answer but I find GUIDs are unwieldy for use as a voucher code due to the string length.
Have a look at the question how to generate a voucher code in c#? and of course my answer :) Although you are asking for a SQL solution you can probably adapt the ideas given there.
I would also recommend you do not delete the codes which have been used; how will you be sure the code presented by the customer was created by your system?

Sql Server 2005 Primary Key violation on an Identity column

I’m running into an odd problem, and I need some help trying to figure it out.
I have a database which has an ID column (defined as int not null, Identity, starts at 1, increments by 1) in addition to all the application data columns. The primary key for the table is the ID column, no other components.
There is no set of data I can use as a "natural primary key" since the application has to allow for multiple submissions of the same data.
I have a stored procedure, which is the only way to add new records into the table (other than logging into the server directly as the db owner)
While QA was testing the application this morning, they to enter a new record into the database (using the application as it was intended, and as they have been doing for the last two weeks) and encountered a primary key violation on this table.
This is the same way I've been doing Primary Keys for about 10 years now, and have never run across this.
Any ideas on how to fix this? Or is this one of those cosmic ray glitches that shows up once in a long while.
Thanks for any advice you can give.
Nigel
Edited at 1:15PM EDT June 12th, to give more information
A simplified version of the schema...
CREATE TABLE [dbo].[tbl_Queries](
[QueryID] [int] IDENTITY(1,1) NOT NULL,
[FirstName] [varchar](50) NOT NULL,
[LastName] [varchar](50) NOT NULL,
[Address] [varchar](150) NOT NULL,
[Apt#] [varchar](10) NOT NULL
... <12 other columns deleted for brevity>
[VersionCode] [timestamp] NOT NULL,
CONSTRAINT [PK_tbl_Queries] PRIMARY KEY CLUSTERED
(
[QueryID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
(also removed the default value statements)
The stored procedure is as follows
insert into dbo.tbl_Queries
( FirstName,
LastName,
[Address],
[Apt#]...) values
( #firstName,
#lastName,
#address,
isnull(#apt, ''), ... )
It doesn't even look at the identity column, doesn't use IDENTITY, ##scope_identity or anything similar, it's just a file and forget.
I am as confident as I can be that the identity value wasn't reset, and that no-one else is using direct database access to enter values. The only time in this project that identity insert is used is in the initial database deployment to setup specific values in lookup tables.
The QA team tried again right after getting the error, and was able to submit a query successfully, and they have been trying since then to reproduce it, and haven't succeeded so far.
I really do appreciate the ideas folks.
Sounds like the identity seed got corrupted or reset somehow. Easiest solution will be to reset the seed to the current max value of the identity column:
DECLARE #nextid INT;
SET #nextid = (SELECT MAX([columnname]) FROM [tablename]);
DBCC CHECKIDENT ([tablename], RESEED, #nextid);
While I don't have an explanation as to a potential cause, it is certinaly possible to change the seed value of an identity column. If the seed were lowered to where the next value would already exist in the table, then that could certainly cause what you're seeing. Try running DBCC CHECKIDENT (table_name) and see what it gives you.
For more information, check out this page
Random thought based on experience
Have you synched data with, say, Red Gate Data Compare. This has an option to reseed identity columns. It's caused issues for use. And another project last month.
You may also have explicitly loaded/synched IDs too.
Maybe someone insert some records logging into the server directly using a new ID explicity, then when the identity auto increment field reach this number a primary key violation happened.
But The cosmic ray is algo a good explanation ;)
Just to make very, very sure...you aren't using an IDENTITY_INSERT in your stored procedure are you? Some logic like this:
declare #id int;
Set #id=Select Max(IDColumn) From Sometable;
SET IDENTITY_INSERT dbo.SomeTable ON
Insert (IDColumn, ...others...) Values (#id+1, ...others...);
SET IDENTITY_INSERT dbo.SomeTable OFF
.
.
.
I feel sticky just typing it. But every once in awhile you run across folks that just never quite understood what an Identity column is all about and I want to make sure that this is ruled out. By the way: if this is the answer, I won't hold it against you if just delete the question and never admit that this was your problem!
Can you tell that I hire interns every summer?
Are you using functions like ##identity or scope_identity() in any of your procedures? if your table has triggers or multiple inserts you could be getting back the wrong identity value for the table you want
Hopefully that is not the case, but there is a known bug in SQL 2005 with SCOPE_IDENTITY():
http://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=328811
The Primary Key violation is not necessarily coming from that table.
Does the application touch any other tables or call any other stored procedures for that function? Are there any triggers on the table? Or does the stored procedure itself use any other tables or stored procedures?
In particular, an Auditing table or trigger could cause this.

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