2010-08-10 3 views
2

При добавлении элемента в мою базу данных мне нужно его для автоматического определения значения для поля DisplayOrder. Идентификация (auto-increment) будет идеальным решением, но мне нужно иметь возможность программно изменять (UPDATE) значения столбца DisplayOrder, а Identity, похоже, не позволяет этого. На данный момент, я использую этот код:SQL Server - автоинкремент, который позволяет операторам UPDATE

CREATE PROCEDURE [dbo].[AddItem] 

AS 

DECLARE @DisplayOrder INT 

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1 

INSERT INTO [dbo].[MyTable] (DisplayOrder) VALUES (@DisplayOrder) 

Это хороший способ сделать это, или есть лучше/простой способ?

+0

** Я только столкнулся с проблемой, с помощью этого метода: ** возвращает MAX NULL, если в таблице нет строк. Поэтому эта процедура не может добавить первую строку, так как она генерирует ошибку! – asmo

+0

Обнаружили ошибку здесь: http://stackoverflow.com/questions/1688715/select-maxx-is-returning-null-how-can-i-make-it-return-0 – asmo

ответ

2

Решение этой проблемы с "Inside Microsoft SQL Server 2008: T-SQL, запрашивающие"

CREATE TABLE dbo.Sequence(
val int IDENTITY (10000, 1) /*Seed this at whatever your current max value is*/ 
) 

GO 

CREATE PROC dbo.GetSequence 
@val AS int OUTPUT 
AS 
BEGIN TRAN 
    SAVE TRAN S1 
    INSERT INTO dbo.Sequence DEFAULT VALUES 
    SET @val=SCOPE_IDENTITY() 
    ROLLBACK TRAN S1 /*Rolls back just as far as the save point to prevent the 
         sequence table filling up. The id allocated won't be reused*/ 
COMMIT TRAN 

Или другой альтернативы из та же книга, которая легче распределяет диапазоны. (Вы должны были бы рассмотреть вопрос о том, чтобы назвать это внутри или вне сделки - внутри будет блокировать другие параллельные транзакции, пока первый не совершает)

CREATE TABLE dbo.Sequence2(
val int 
) 

GO 

INSERT INTO dbo.Sequence2 VALUES(10000); 

GO 

CREATE PROC dbo.GetSequence2 
@val AS int OUTPUT, 
@n as int =1 
AS 
UPDATE dbo.Sequence2 
SET @val = val = val + @n; 

SET @val = @val - @n + 1; 
0

Одна вещь, которую вы должны сделать, это добавить команды, чтобы ваша процедура выполнялась как транзакция, в противном случае две вставки, запущенные одновременно, могли создавать две строки с одинаковым значением в DisplayOrder.

Этого достаточно легко достичь: добавить

begin transaction 

в начале вашей процедуры и

commit transaction 

в конце.

+1

Этот ответ не кажется Ответ на вопрос. – bobs

+2

Он отвечает на вопрос о том, можно ли улучшить предлагаемый способ. –

+0

Обтекание 'SELECT' и' INSERT' в транзакции не блокирует 2 одновременных выбора, считывая максимальное значение –

2

Вы можете установить свой увеличивающий столбец, чтобы использовать свойство идентификации. Затем в процессах, которые необходимо вставить значения в столбец, вы можете использовать команду SET IDENITY_INSERT в своей партии.

Для вставки, где вы хотите использовать свойство идентичности, исключить столбец идентификаторов из списка столбцов в вставном высказывании:

INSERT INTO [dbo].[MyTable] (MyData) VALUES (@MyData) 

Если вы хотите, чтобы вставить строки, где вы обеспечиваете значение для в столбце идентификации, используйте следующее:

SET IDENTITY_INSERT MyTable ON 

INSERT INTO [dbo].[MyTable] (DisplayOrder, MyData) 
VALUES (@DisplayOrder, @MyData) 

SET IDENTITY_INSERT MyTable OFF 

Вы должны иметь возможность ОБНОВИТЬ столбец без каких-либо других шагов.

Вы также можете ознакомиться с командой DBCC CHECKIDENT. Эта команда установит ваше следующее значение идентификации. Если вы вставляете строки, где следующее значение идентификации может оказаться неприемлемым, вы можете использовать команду для установки нового значения.

DECLARE @DisplayOrder INT 

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1 

DBCC CHECKIDENT (MyTable, RESEED, @DisplayOrder) 
+0

(BTW есть опечатка в SET IDENITY_INSERT) – asmo

+0

Приятно помочь. Я исправлю опечатку для будущих читателей. Удачи. – bobs

+0

Я только заметил, что IDENTITY_INSERT, похоже, не разрешает инструкции UPDATE для столбцов идентификаторов (он допускает только инструкции INSERT). Мне нужно другое решение. – asmo

0

Вы хорошо работаете (с небольшой модификацией) и просты. Я бы обернул его в транзакцию, как сказал @David Knell. Это приведет к коду, как:

CREATE PROCEDURE [dbo].[AddItem] 

AS 

DECLARE @DisplayOrder INT 

BEGIN TRANSACTION 

SET @DisplayOrder = (SELECT MAX(DisplayOrder) FROM [dbo].[MyTable]) + 1 

INSERT INTO [dbo].[MyTable] (DisplayOrder) VALUES (@DisplayOrder) 

COMMIT TRANSACTION 

Обертывание селектов & INSERT в транзакции гарантирует, что ваши DisplayOrder значения не будут дублироваться AddItem. Если вы делаете много одновременного добавления (много в секунду), могут возникнуть разногласия на MyTable, но для случайных вставок это не будет проблемой.

+0

Обертка 'SELECT' и' INSERT' в транзакции не гарантирует этого вообще. –

+0

Вы правы, я думаю, что этот ответ на самом деле имеет гораздо более чистое решение: http://stackoverflow.com/questions/193257/in-ms-sql-server-is-there-a-way-to-atomically-increment- a-column-being-used-as/193456 # 193456 –

+0

Да. Версия этого была в книге, на которую ссылается и в моем ответе. Он обрабатывает распределение кусков чисел намного проще, чем решение, которое я выбрал. Дело в том, что это решение состоит в том, что если номер последовательности выделяется из одной и той же транзакции, он блокирует любые параллельные вставки до завершения первой транзакции, что может быть или не быть нежелательным. Решение таблицы идентификаторов не блокируется. Изменить: взглянув на него снова, это совсем не то же самое. Я отвечу на альтернативный вариант. –

0

Вот решение, которое я держал:

CREATE PROCEDURE [dbo].[AddItem] 

AS 

DECLARE @DisplayOrder INT 

BEGIN TRANSACTION 

SET @DisplayOrder = (SELECT ISNULL(MAX(DisplayOrder), 0) FROM [dbo].[MyTable]) + 1 

INSERT INTO [dbo].[MyTable] (DisplayOrder) VALUES (@DisplayOrder) 

COMMIT TRANSACTION 
+0

Это может привести к тому, что дублирующиеся DisplayOrders будут введены, если у вас есть параллельные исполнения 'AddItem' –

+0

Но операторы SELECT и INSERT заключены в транзакцию. Разве это не должно исправлять дубликаты? – asmo

+0

Я думаю, что это была идея, но она не будет работать без дополнительных указаний на блокировку при любом уровне изоляции. Поскольку два параллельных выбора не будут блокировать друг друга и оба будут читать одно и то же значение. Тогда вставки будут успешными, за исключением сериализуемого уровня, когда я думаю, что вы получите тупик. –