只用简单的SQL INSERT就可以实现手动增量?

时间:2009-04-27 18:04:20

标签: sql sql-server sql-server-2005 language-agnostic insert

我有一个主键,我不想自动递增(由于各种原因),所以我正在寻找一种方法来简单地在我INSERT时增加该字段。简单地说,我的意思是没有存储过程和没有触发器,所以只需要一系列SQL命令(最好是一个命令)。

这是我到目前为止所尝试的内容:

BEGIN TRAN

INSERT INTO Table1(id, data_field)
VALUES ( (SELECT (MAX(id) + 1) FROM Table1), '[blob of data]');

COMMIT TRAN;

* Data abstracted to use generic names and identifiers

然而,执行时,命令错误,说

  

“此处不允许使用子查询   上下文。只有标量表达式   允许“

那么,我怎么能这样做/我做错了什么?


编辑:因为它被指出是一个考虑因素,所以要插入的表格已经保证至少有一行。

13 个答案:

答案 0 :(得分:10)

你明白你会发生碰撞吗?

你需要做这样的事情,这可能会导致死锁,所以要非常肯定你要在这里完成什么

DECLARE @id int
BEGIN TRAN

    SELECT @id = MAX(id) + 1 FROM Table1 WITH (UPDLOCK, HOLDLOCK)
    INSERT INTO Table1(id, data_field)
    VALUES (@id ,'[blob of data]')
COMMIT TRAN

为了解释碰撞事项,我提供了一些代码

首先创建此表并插入一行

CREATE TABLE Table1(id int primary key not null, data_field char(100))
GO
Insert Table1 values(1,'[blob of data]')
Go

现在打开两个查询窗口并同​​时运行

declare @i int
set @i =1
while @i < 10000
begin
BEGIN TRAN

INSERT INTO Table1(id, data_field)
SELECT MAX(id) + 1, '[blob of data]' FROM Table1

COMMIT TRAN;
set @i =@i + 1
end

你会看到一堆这些

服务器:Msg 2627,Level 14,State 1,Line 7 违反PRIMARY KEY约束'PK__Table1__3213E83F2962141D'。无法在对象'dbo.Table1'中插入重复键。 声明已经终止。

答案 1 :(得分:2)

请改为尝试:

INSERT INTO Table1 (id, data_field)
SELECT id, '[blob of data]' FROM (SELECT MAX(id) + 1 as id FROM Table1) tbl

我不建议这样做有多种原因(性能,交易安全等)

答案 2 :(得分:1)

可能是因为没有记录所以子查询返回NULL ...尝试

INSERT INTO tblTest(RecordID, Text) 
VALUES ((SELECT ISNULL(MAX(RecordID), 0) + 1 FROM tblTest), 'asdf')

答案 3 :(得分:1)

我不知道是否有人仍在寻找答案,但这是一个似乎有效的解决方案:

-- Preparation: execute only once
    CREATE TABLE Test (Value int)

CREATE TABLE Lock (LockID uniqueidentifier)
INSERT INTO Lock SELECT NEWID()

-- Real insert

    BEGIN TRAN LockTran

    -- Lock an object to block simultaneous calls.
    UPDATE  Lock WITH(TABLOCK)
    SET     LockID = LockID

    INSERT INTO Test
    SELECT ISNULL(MAX(T.Value), 0) + 1
    FROM Test T

    COMMIT TRAN LockTran

答案 4 :(得分:1)

我们有类似的情况,我们需要增加,并且不能在数字上有差距。 (如果您使用标识值并回滚事务,则不会插入该数字,并且您将有间隙,因为标识值不会回滚。)

我们为最后使用的号码创建了一个单独的表格,并将其播种为0。

我们的插入需要几个步骤。

- 增加数字 更新dbo.NumberTable set number = number + 1

- 找出增加的数字是什么 选择@number =数字 来自dbo.NumberTable

- 使用号码 使用@number

插入到dbo.MyTable中

提交或回滚

这导致同时事务在一行中处理,因为每个并发事务将等待,因为NumberTable被锁定。一旦等待事务获得锁定,它就会递增当前值并将其锁定。当前值是最后使用的数字,如果回滚事务,NumberTable更新也会回滚,因此没有间隙。

希望有所帮助。

导致单个文件执行的另一种方法是使用SQL应用程序锁。我们已经将这种方法用于更长时间运行的过程,例如在系统之间同步数据,因此一次只能运行一个同步过程。

答案 5 :(得分:0)

如果您在触发器中执行此操作,则可以确保它是“INSTEAD OF”触发器,并在几个语句中执行:

DECLARE @next INT
SET @next = (SELECT (MAX(id) + 1) FROM Table1)

INSERT INTO Table1
VALUES (@next, inserted.datablob)

你唯一需要注意的是并发 - 如果同时插入两行,他们可能会尝试为@next使用相同的值,从而导致冲突。

这会达到你想要的效果吗?

答案 6 :(得分:0)

declare @nextId int
set @nextId = (select MAX(id)+1 from Table1)

insert into Table1(id, data_field) values (@nextId, '[blob of data]')

commit;

但也许更好的方法是使用标量函数getNextId('table1')

答案 7 :(得分:0)

做这种没有IDENTITY(自动增量)列的事情似乎很奇怪,这让我对架构本身提出质疑。我的意思是,严肃地说,这是IDENTITY专栏的完美情况。如果你解释这个决定背后的原因,它可能会帮助我们回答你的问题。 =)

话虽如此,有些选择是:

  • 为此目的使用INSTEAD OF触发器。所以,你要做INSERT(INSERT语句不需要传入ID)。触发器代码将处理插入适当的ID。您需要使用另一个回答者使用的WITH(UPDLOCK,HOLDLOCK)语法来保持锁定的持续时间(在事务中隐式包装)&amp;将锁定类型从“共享”提升为“更新”锁定(IIRC)。
  • 您可以使用上述想法,但有一个表,其目的是存储插入表中的最后一个最大值。因此,一旦设置了表,您就不再需要每次都执行SELECT MAX(ID)。您只需增加表中的值即可。如果您使用适当的锁定(如上所述),这是安全的。同样,每次INSERT时都会避免重复的表扫描。
  • 使用GUID而不是ID。跨数据库合并表要容易得多,因为GUID始终是唯一的(而跨数据库的记录将具有冲突的整数ID)。为避免页面拆分,可以使用顺序GUID。这只有在您可能需要进行数据库合并时才有用。
  • 使用存储过程来代替触发方法(因为出于某种原因应避免使用触发器)。您仍然遇到锁定问题(以及可能出现的性能问题)。但是,sprocs比动态SQL(在应用程序的上下文中)更受欢迎,而且通常性能更高。

抱歉漫无边际。希望有所帮助。

答案 8 :(得分:0)

对此有何批评?适合我。

DECLARE @m_NewRequestID INT
        , @m_IsError BIT = 1
        , @m_CatchEndless INT = 0

WHILE @m_IsError = 1
    BEGIN TRY
        SELECT  @m_NewRequestID = (SELECT ISNULL(MAX(RequestID), 0) + 1 FROM Requests)

        INSERT INTO Requests (  RequestID
                                , RequestName
                                , Customer
                                , Comment
                                , CreatedFromApplication)
            SELECT  RequestID = @m_NewRequestID
                    , RequestName = dbo.ufGetNextAvailableRequestName(PatternName)
                    , Customer = @Customer
                    , Comment = [Description]
                    , CreatedFromApplication = @CreatedFromApplication
                FROM    RequestPatterns
                WHERE   PatternID = @PatternID

        SET @m_IsError = 0
    END TRY
    BEGIN CATCH
        SET @m_IsError = 1
        SET @m_CatchEndless = @m_CatchEndless + 1
        IF @m_CatchEndless > 1000
            THROW 51000, '[upCreateRequestFromPattern]: Unable to get new RequestID', 1
    END CATCH

答案 9 :(得分:0)

如何创建一个单独的表来维护计数器?它的性能将比MAX(id)好,因为它将是O(1)。 MAX(id)最多为O(lgn),具体取决于实现方式。

然后,当您需要插入时,只需锁定计数器表以读取计数器并递增计数器。然后,您可以释放锁并使用递增的计数器值插入到表中。

答案 10 :(得分:0)

有一个单独的表格,您可以在其中保存最新的 ID,并为每笔交易获取一个新的 ID。 它可能有点慢,但应该可以工作。

DECLARE @NEWID INT
BEGIN TRAN
UPDATE TABLE SET ID=ID+1
SELECT @NEWID=ID FROM TABLE
COMMIT TRAN

PRINT @NEWID -- Do what you want with your new ID

答案 11 :(得分:0)

没有任何事务范围的代码(我在我的工程师课程中使用它作为练习):

-- Preparation: execute only once
CREATE TABLE increment (val int);
INSERT INTO increment VALUES (1);

-- Real insert
DECLARE @newIncrement INT;

UPDATE increment
SET    @newIncrement = val,
       val = val + 1;

INSERT INTO Table1 (id, data_field)
SELECT @newIncrement, 'some data';

答案 12 :(得分:-1)

这应该有效:

INSERT INTO Table1 (id, data_field)
SELECT (SELECT (MAX(id) + 1) FROM Table1), '[blob of data]';

或者这个(替代其他平台的LIMIT):

INSERT INTO Table1 (id, data_field)
SELECT TOP 1
    MAX(id) + 1, '[blob of data]'
FROM
   Table1
ORDER BY
   [id] DESC;