Вставить, если не существует, но вернуть идентичность в любом случае - PullRequest
18 голосов
/ 07 ноября 2011

У меня есть 3 таблицы: аудиоформаты, видеоформаты и fileInfo. У меня есть транзакция такая, что когда я вставляю в таблицу fileInfo, эта вставка включает в себя FK из audioFormats и videoFormats. Вставка в последние таблицы происходит, если аудиоформат или видеоформат еще не находятся в этих таблицах, а затем сгенерированное (или существующее) значение ID вставляется в fileInfo.

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

Я могу вставить значение, если оно еще не существует:

INSERT INTO audioformats (audioformat)
VALUES(@format)
WHERE NOT EXISTS (SELECT 1 FROM audioformats WHERE audioformat = @format)

Я могу получить вставленный идентификатор из вставки:

INSERT INTO audioFormats (audioFormat)
VALUES ('Test')
SET @audioFormatId = SCOPE_IDENTITY()

SCOPE_IDENTITY не даст мне значение идентификатора, если не было вставки. Я могу выполнить скалярный запрос для получения идентификатора после возможной вставки, но мне кажется, что я должен быть в состоянии сделать все это с помощью одного SELECT и INSERT.

Ответы [ 5 ]

13 голосов
/ 07 ноября 2011

Вы можете использовать оператор IF, чтобы сделать это

IF NOT EXISTS(SELECT TOP 1 1 FROM audioformats WHERE audioformat = @format)
BEGIN
  INSERT INTO audioFormats (audioFormat)
  VALUES ('Test')
  SET @audioFormatId = SCOPE_IDENTITY()
END
ELSE
BEGIN
  SELECT @audioFormatID = ID FROM audioformats WHERE audioformat = @format
END

, или вы можете сделать это так:

INSERT INTO audioformats (audioformat)
  SELECT @format
  FROM audioFormats
  WHERE NOT EXISTS (SELECT TOP 1 1 FROM audioformats WHERE audioformat = @format)

SELECT @audioFormatID = ID FROM audioformats WHERE audioformat = @format
4 голосов
/ 08 ноября 2011

Я пометил ответ как правильный, потому что он был наиболее похож на мой окончательный SQL, но он не соответствовал первоначальному требованию использования не более одного запроса и одной вставки.Следующее делает, и это то, что я в конечном итоге использовал:

-- One select
SELECT @audioFormatId = id
FROM audioformats
WHERE audioformat = @audioFormat;

-- Optionally one insert
IF @audioFormatId IS NULL AND @audioFormat IS NOT NULL
BEGIN
    INSERT INTO audioFormats (audioFormat)
    VALUES (@audioFormat)
    SET @audioFormatId = SCOPE_IDENTITY()
END

Ответ Хогана будет , вероятно, быстрее, когда большинство вызовов запроса действительно выполняют вставку, потому что если IF NOT EXISTSзапрос выполняется быстрее, чем тот, который действительно возвращает строки.

Мой будет , вероятно, быстрее, в тех случаях, когда значение уже существует большую часть времени, потому что тогда ровно один запрос (и если IF NOT EXISTSзапрос) будет сделано.Я подозреваю, что нулевая проверка тривиальна.

3 голосов
/ 07 ноября 2011

Я думаю, что вы можете использовать MERGE для этого.

Это дает вам решение без транзакций.Некоторая яркая искра могла бы улучшить это, используя предложение output для получения целевого идентификатора, но ниже будет работать просто отлично.

Я добавил ссылку на тестер запросов StackOverflow ниже.

-- Merge example
-- Get ID of existing row or insert new row

-- Initialise unit test data
declare @AudioFormatId int;
declare @AudioFormat nvarchar(50)
declare @tblAudioFormats TABLE (AudioFormatId int identity, AudioFormat nvarchar(50)   );
insert into @tblAudioFormats(AudioFormat) values ('MP3'), ('WAV');

-- set query criteria
set @AudioFormat = 'MP3' -- query below returns 1 - updating MP3
--set @AudioFormat = 'WAV' -- query below returns 2 - updating WAV
--set @AudioFormat = 'MIDI' -- query below returns 3 - inserting MIDI

-- Insert or update AudioFormat and return id of target audio format.
merge
  @tblAudioFormats as Target
using
  (select @AudioFormat as AudioFormat) as source(AudioFormat)
on 
  (source.AudioFormat = target.AudioFormat)
when matched then
  update set @AudioFormatID = target.AudioFormatId
when not matched then
  insert(AudioFormat) values (source.AudioFormat);

if @AudioFormatId is null set @AudioFormatId = scope_identity()

-- return ID of target audio format
select @AudioFormatId as TargetAudioFormatId

Выполните этот запрос здесь: Запрос ссылки StackOverflow для образца

2 голосов
/ 07 ноября 2011

Вам нужно будет гарантировать, что ваш столбец audioformat уникален с помощью ограничения UNIQUE , и поместите свой код в try / catch .

Попробуйте вставить. Если это не удалось, то перехватите, запросите новую запись и верните существующий идентификатор.

Вы могли бы сначала попробовать сделать запрос. Но если кто-то вставит между началом вашей партии и когда вы вставите, БД все равно сгенерирует исключение.

2 голосов
/ 07 ноября 2011

Если таблица аудиоформатов имеет автоинкремент IDENTITY(1,1) PK, вы можете получить только что вставленный идентификатор, просто выбрав:

SELECT MAX(ID) FROM audioFormats 

РЕДАКТИРОВАТЬ:

Как уже упоминалось в комментарииэтот подход применим, когда в таблицу вставляется только один запрос.

В противном случае вы можете взглянуть на функцию IDENT_CURRENT ('table_name') .

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...