Возможно ли реализовать ручное увеличение с помощью простого SQL INSERT? - PullRequest
6 голосов
/ 27 апреля 2009

У меня есть первичный ключ, который я не хочу автоматически увеличивать (по разным причинам), поэтому я ищу способ просто увеличить это поле, когда я ВСТАВЛЯЮ. Просто я имею в виду без хранимых процедур и без триггеров, поэтому просто последовательность команд 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

Однако при выполнении команда выдает ошибку, говоря, что

"Подзапросы не разрешены в этом контекст. только скалярные выражения разрешено "

Итак, как я могу это сделать / что я делаю не так?


РЕДАКТИРОВАТЬ: Поскольку это было указано в качестве рассмотрения, гарантируется, что таблица для вставки будет иметь как минимум 1 строку.

Ответы [ 11 ]

10 голосов
/ 27 апреля 2009

Вы понимаете, что у вас будут столкновения, верно?

вам нужно сделать что-то подобное, и это может вызвать взаимные блокировки, поэтому будьте уверены, что вы пытаетесь достичь здесь

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

Вы увидите кучу этих

Сервер: сообщение 2627, уровень 14, состояние 1, строка 7 Нарушение ограничения PRIMARY KEY 'PK__Table1__3213E83F2962141D'. Невозможно вставить дубликат ключа в объект 'dbo.Table1'. Заявление было прекращено.

2 голосов
/ 27 апреля 2009

Попробуйте вместо этого:

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

Я бы не советовал делать это по ряду причин (производительность, безопасность транзакций и т. Д.)

1 голос
/ 03 апреля 2012

У нас похожая ситуация, когда нам нужно было увеличивать и не могло быть пробелов в числах. (Если вы используете значение идентификатора и откат транзакции, этот номер не будет вставлен, и у вас будут пробелы, поскольку значение идентификатора не будет откатано.)

Мы создали отдельную таблицу для последнего использованного номера и добавили в ноль 0.

Наша вставка занимает несколько шагов.

- увеличить число Обновите dbo.NumberTable номер набора = число + 1

- узнать, что такое увеличенное число выберите @ номер = число из dbo.NumberTable

- использовать номер вставить в dbo.MyTable, используя @ number

коммит или откат

Это приводит к тому, что одновременные транзакции обрабатываются в одной строке, так как каждая параллельная транзакция будет ожидать, поскольку NumberTable заблокирован. Как только ожидающая транзакция получает блокировку, она увеличивает текущее значение и блокирует его от других. Это текущее значение является последним использованным числом, и если транзакция откатывается, обновление NumberTable также откатывается, поэтому пропусков нет.

Надеюсь, это поможет.

Другой способ вызвать выполнение отдельного файла - использовать блокировку приложения SQL. Мы использовали этот подход для более длительных процессов, таких как синхронизация данных между системами, поэтому одновременно может выполняться только один процесс синхронизации.

1 голос
/ 13 марта 2012

Я не знаю, ищет ли кто-нибудь ответ, но вот решение, которое, кажется, работает:

-- 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 голос
/ 27 апреля 2009

Это может быть из-за отсутствия записей, поэтому подзапрос возвращает NULL ... try

INSERT INTO tblTest(RecordID, Text) 
VALUES ((SELECT ISNULL(MAX(RecordID), 0) + 1 FROM tblTest), 'asdf')
0 голосов
/ 02 октября 2018

Как насчет создания отдельной таблицы для поддержки счетчика? Он имеет лучшую производительность, чем MAX(id), так как это будет O (1). MAX(id) в лучшем случае O (lgn) в зависимости от реализации.

А затем, когда вам нужно вставить, просто заблокируйте таблицу счетчиков для чтения счетчика и увеличьте счетчик. Затем вы можете снять блокировку и вставить в таблицу с увеличенным значением счетчика.

0 голосов
/ 03 октября 2015

Есть критика этого? У меня работает.

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
0 голосов
/ 27 апреля 2009

Кажется очень странным делать подобные вещи без столбца IDENTITY (автоинкремент), что заставляет меня подвергать сомнению саму архитектуру. Я имею в виду, серьезно, это идеальная ситуация для столбца IDENTITY. Это может помочь нам ответить на ваш вопрос, если вы объясните причину этого решения. =) * * Тысяча одна

Сказав это, некоторые варианты:

  • с использованием триггера INSTEAD OF для этой цели. Итак, вы бы сделали INSERT (оператор INSERT не должен был бы передавать ID). Код триггера будет обрабатывать вставку соответствующего идентификатора. Вам нужно было бы использовать синтаксис WITH (UPDLOCK, HOLDLOCK), используемый другим ответчиком, чтобы удерживать блокировку на время триггера (который неявно заключен в транзакцию) и повышать тип блокировки с «разделяемого» до «обновления» "замок (IIRC).
  • Вы можете использовать идею, описанную выше, но иметь таблицу, цель которой - сохранить последнее, максимальное значение, вставленное в таблицу. Таким образом, после настройки таблицы вам больше не придется каждый раз выполнять SELECT MAX (ID). Вы бы просто увеличили значение в таблице. Это безопасно при условии, что вы используете соответствующую блокировку (как обсуждалось). Опять же, это позволяет избежать повторного сканирования таблицы каждый раз, когда вы вставляете.
  • использовать GUID вместо идентификаторов. Гораздо проще объединять таблицы между базами данных, поскольку идентификаторы GUID всегда будут уникальными (тогда как записи в базах данных будут иметь конфликтующие целочисленные идентификаторы). Чтобы избежать разбиения страницы, можно использовать последовательные идентификаторы GUID. Это полезно только в том случае, если вам может потребоваться объединить базу данных.
  • Использование хранимого процесса вместо триггерного подхода (поскольку по какой-то причине следует избегать триггеров). У вас все еще будет проблема с блокировкой (и проблемы с производительностью, которые могут возникнуть). Но sprocs предпочтительнее динамического SQL (в контексте приложений) и часто гораздо более производительны.

Извините за бессвязные. Надеюсь, это поможет.

0 голосов
/ 27 апреля 2009
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')

0 голосов
/ 27 апреля 2009

Это должно работать:

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;
...