如何确保我的SQL varchar字段是唯一的

时间:2014-01-14 03:37:40

标签: sql sql-server tsql sql-server-2008-r2

我确信这很简单,但我已经整晚都在忙碌而且现在卡住了。

我有一项功能可以克隆数据库中的记录,但我需要确保新名称字段在数据库中是唯一的。

例如,第一条记录是

[ProjectName]   [ResourceCount]
'My Project'         8

然后当我点击我想要的克隆时

 'My Project Cloned', 8

但是如果我再次按下该按钮,它应该会注意到克隆的名称存在而是吐出

'My Project Cloned 2', 8

这有意义吗?

我可以用临时表和游标来做,但必须有一个更好的方法来做到这一点?

使用SQL Server 2008 R2

解决方案需要完全基于T-SQL,但这发生在单个存储过程

4 个答案:

答案 0 :(得分:3)

因此,根据我对您的问题的理解,以下是我将如何处理它:

我的表:

CREATE TABLE [dbo].[deal]
(
[dealName] varchar(100),
[resourceCount] int
)

然后在dealName列上创建一个唯一索引:

CREATE UNIQUE NONCLUSTERED INDEX [UQ_DealName] ON [dbo].[deal]
(
[dealName] ASC
)

获得唯一索引后,您可以使用try / catch直接在T-SQL中处理任何异常,例如唯一约束违规(错误2601)

SET NOCOUNT ON;

DECLARE @dealName VARCHAR(100) = 'deal'
DECLARE @resourceCount INT = 8
DECLARE @count INT

BEGIN TRY
    BEGIN TRANSACTION
    INSERT INTO dbo.deal (dealName,resourceCount)
    VALUES (@dealName, @resourceCount)
    COMMIT TRANSACTION
END TRY
BEGIN CATCH
    IF @@ERROR = 2601
    BEGIN
        ROLLBACK TRANSACTION
        SET @count = (SELECT COUNT(dealName) FROM dbo.deal WHERE resourceCount = @resourceCount)
        SET @resourceCount = (SELECT resourceCount FROM dbo.deal WHERE dealName = @dealName)
        SET @dealName = @dealName + ' Cloned ' + CAST(@count AS VARCHAR(100))
        BEGIN TRANSACTION
            INSERT INTO dbo.deal (dealName,resourceCount)
            VALUES (@dealName,@resourceCount)
        COMMIT TRANSACTION
    END
END CATCH
SELECT * FROM dbo.deal

您可以轻松地将此代码放入一个过程中,只需尝试插入一个带有资源计数的交易名称,如果违反了唯一约束,它就会进入catch块,将您想要的信息附加到交易中找到原始交易的资源计数后命名,然后插入这些值。

它无论如何都不是防弹的,但我发现这种技术非常有用,不仅仅是为了强制执行唯一性,而且你可以使用类似的处理异常数的方式来处理死锁,主键违规和其他错误的加载,所有这些都是在T-SQL中。

答案 1 :(得分:2)

您可以在这两列上定义UNIQUE INDEXCONSTRAINT

CREATE UNIQUE INDEX IX_MyTable_ProjectName_ResourceCount
ON dbo.MyTable ([ProjectName], [ResourceCount]);

答案 2 :(得分:1)

确保价值是唯一的很容易:创建unique constraint。如果插入了唯一值,MSSQL将抛出异常,您可以在应用程序中恢复。

基于计数器(Proj1,Proj2等)创建一个唯一的名称更为复杂。

请注意,这最好在Web层中进行缓解,您可以在其中执行存在检查,并在尝试插入项目名称“已在使用中”之前通知用户。并且,如果这不是一个选项,那么确保唯一性的方法要比列举你所描述的计数简单得多。附加日期时间或指南可以使事情变得相对容易,并且会极大地(如果不是完全的话)避免竞争条件。

如果您绝对必须按照要求在t-sql中实现,那么在某处(即下面的“序列”表)中包含计数器列应该有助于最小化竞争条件。我怀疑即使使用以下示例,您也可能会在高频率呼叫下看到一些争用。

--setup
/*
    --your existing table
    create table dbo.Project 
    (
        [ProjectName] varchar(100) primary key, 
        [ResourceCount] int
    );

    --a new table to transactionally constrain the increment
    create table dbo.ProjectNameSequence (ProjectName varchar(100) primary key, Seq int);

    --cleanup
    --drop table dbo.ProjectNameSequence
    --drop table dbo.Project

*/

declare @ProjectName varchar(100), @ResourceCount int;

set @ProjectName = 'Test Project XX';
set @ResourceCount = 9;


merge dbo.ProjectNameSequence [d]
using (values(@ProjectName)) [s] (ProjectName) on 
      d.ProjectName = s.ProjectName
when matched then update set Seq += 1
when not matched then insert values(@ProjectName, 1)
output  @ProjectName + case inserted.Seq when 1 then '' else cast(inserted.Seq as varchar) end, 
        @ResourceCount
into dbo.Project;  


select * from dbo.Project

答案 3 :(得分:0)

我使用WHILE循环内的IF EXISTS来解决这个问题。

就我个人而言,我看不出这种方法有什么问题,但显然会考虑任何意见

DECLARE @NameInvalid varchar(100)
DECLARE @DealName varchar(100)
DECLARE @Count int
SET @Count = 1
SET @NameInvalid = 'true'

SELECT @DealName = DealName FROM Deal WHERE DealId = @DealId

--Ensure we get a unique deal name
WHILE( @NameInvalid = 'true')
BEGIN
    IF NOT EXISTS(SELECT DealName FROM Deal where DealName = @DealName + ' Cloned ' + cast(@Count as varchar(10)))
    BEGIN
        INSERT INTO Deal
            (DealName)
            SELECT @DealName + ' Cloned ' + cast(@Count as varchar(10))
                FROM Deal
                WHERE DealID = @DealId
            SET @NewDealId = @@IDENTITY
        SET @NameInvalid = 'false'
    END
    ELSE
    BEGIN
        SET @NameInvalid = 'true'
        SET @Count = @Count + 1
    END
END