2009-04-27 14 views
5

我有一個主鍵,我不想自動增量(由於各種原因),所以我正在尋找一種方法來簡單地增加該字段,當我插入。簡而言之,我的意思是沒有存儲過程並且沒有觸發器,所以只需一系列SQL命令(最好是一個命令)。只需簡單的SQL INSERT即可實現手動增量?

這是我迄今爲止嘗試:

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 

然而,在執行時,該命令的錯誤,說

「子查詢未在本 背景下僅允許標量表達式。在 允許」

所以,我怎麼能做到這一點/我究竟做錯了什麼?


編輯:由於它被指出作爲一個考慮因素,被插入到的表保證至少有一行已經被保證。

回答

9

你知道你會碰撞嗎?

你需要做這樣的事情,這可能會導致死鎖,所以要非常確定你想在這裏完成

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 [email protected] + 1 
end 

你會看到一堆這些

服務器:消息2627,級別14,狀態1,行7 違反PRIMARY KEY約束 'PK__Table1__3213E83F2962141D' 的。無法在對象'dbo.Table1'中插入重複鍵。 該聲明已被終止。

2

試試這個:

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

我不建議這樣做它的方式爲任意數量的原因,雖然(性能,安全交易等)

+0

你會推薦他們做什麼呢? – GernBlandston 2009-04-27 18:14:05

+0

由於id字段被索引,性能命中是否顯着?另外,如果包裹在一個事務中,它應該是原子性的,安全的,正確的? – cdeszaq 2009-04-27 18:14:13

+0

我只是不確定在執行SELECT MAX(id)..部分時表是否會被鎖定。如果沒有,那麼有可能兩個線程可以獲得相同的ID。知道SQL服務器好一點的人可以告訴你,如果這實際上是安全的 - 我的直覺是,它不是,但我真的不確定。 – 2009-04-27 18:33:51

0

如果你在做觸發,你可以確保它是一個「而不是」觸發器,做到在一對夫婦的語句:

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

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

你必須要小心的唯一的事情是併發性 - 如果兩行是inser在同一時間,他們可能會嘗試爲@next使用相同的值,導致衝突。

這是否完成你想要的?

+0

不,觸發器不能使用,除非沒有其他方法。 – cdeszaq 2009-04-27 18:15:13

0

這應該工作:

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; 
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(「表1 ')

1

這可能是因爲沒有記錄,所以子查詢返回NULL ...試試

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

做這樣的事情沒有一個IDENTITY(自動增量)列似乎很奇怪,這讓我質疑架構本身。我的意思是,嚴格來說,這是IDENTITY專欄的完美處境。如果你能解釋這個決定背後的推理,它可能會幫助我們回答你的問題。 =)

話雖如此,某些選項:

  • 使用INSTEAD OF觸發器用於此目的。所以,你會做你的INSERT(INSERT語句不需要傳入一個ID)。觸發器代碼將處理插入適當的ID。您需要使用由另一個回答者使用的WITH(UPDLOCK,HOLDLOCK)語法來鎖定觸發器的持續時間(在事務中隱式包裝)&以將鎖定類型從「共享」提升爲「更新「鎖定(IIRC)。
  • 你可以使用上面的想法,但有一個表,其目的是存儲插入到表中的最後一個最大值。所以,一旦表格設置完畢,你就不必再每次都做一個SELECT MAX(ID)。你只需增加表中的值即可。這是安全的,只要你使用適當的鎖定(如上所述)。同樣,這樣可以避免每次插入時重複進行表掃描。
  • 使用GUID而不是ID。跨數據庫合併表要容易得多,因爲GUID始終是唯一的(而跨數據庫的記錄將具有衝突的整數ID)。爲避免頁面拆分,可以使用順序的GUID。如果您可能需要執行數據庫合併,這只是有益的。
  • 使用存儲過程代替觸發器方法(由於某些原因應避免觸發器)。您仍然會遇到鎖定問題(以及可能出現的性能問題)。但是sprocs比動態SQL(在應用程序上下文中)更受歡迎,並且通常性能更高。

對不起漫步。希望有所幫助。

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 
1

我們有我們需要增加一個類似的情況,不能有縫隙號碼。 (如果使用標識值並且事務回滾,那麼該數字將不會被插入,並且您將有空隙,因爲標識值不會回滾。)

我們創建了一個單獨的表使用的電話號碼和0。

我們插入需要幾個步驟播種了。

--increment數 更新dbo.NumberTable 組數=數+ 1

--find什麼遞增的數目是 選擇@number =數目 從dbo.NumberTable

- 使用@number

提交 - 使用數 插入件插入dbo.MyTable或回滾

這會導致併發事務處理在一行中,因爲每個併發事務將等待,因爲NumberTable被鎖定。只要等待的事務獲得鎖定,它就會遞增當前值並將其從其他鎖定。當前值是最後一個使用的數字,如果事務回滾,則NumberTable更新也會回滾,因此不存在任何間隙。

希望有所幫助。

導致單個文件執行的另一種方法是使用SQL應用程序鎖。我們已經將這種方法用於更長時間的運行流程,例如系統間的數據同步,因此一次只能運行一個同步流程。

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