我确信这很简单,但我已经整晚都在忙碌而且现在卡住了。
我有一项功能可以克隆数据库中的记录,但我需要确保新名称字段在数据库中是唯一的。
例如,第一条记录是
[ProjectName] [ResourceCount]
'My Project' 8
然后当我点击我想要的克隆时
'My Project Cloned', 8
但是如果我再次按下该按钮,它应该会注意到克隆的名称存在而是吐出
'My Project Cloned 2', 8
这有意义吗?
我可以用临时表和游标来做,但必须有一个更好的方法来做到这一点?
使用SQL Server 2008 R2
解决方案需要完全基于T-SQL,但这发生在单个存储过程
中答案 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 INDEX或CONSTRAINT:
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