Вставте оновлення, що зберігається, на SQL Server


104

Я написав збережений додаток, який буде робити оновлення, якщо запис існує, інакше він зробить вставку. Це виглядає приблизно так:

update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)

Моя логіка написання цього способу полягає в тому, що оновлення буде виконувати неявний вибір, використовуючи пункт де, і якщо це повертає 0, то вставка відбудеться.

Альтернативою зробити це таким чином було б зробити вибір, а потім виходячи з кількості повернутих рядків або зробити оновлення, або вставити. Це я вважав неефективним, тому що якщо ви робите оновлення, це призведе до вибору 2 вибору (перший явний вибір вибору, а другий неявний у місці оновлення). Якби процесор робив вставку, різниці в ефективності не було б.

Чи звучить тут моя логіка? Це, як би ви поєднали вставку та оновлення у збережений додаток?

Відповіді:


61

Ваше припущення правильне, це оптимальний спосіб зробити це, і це називається вгору / злиття .

Важливість UPSERT - від sqlservercentral.com :

Для кожного оновлення у згаданому вище випадку ми видаляємо з таблиці ще одне додаткове зчитування, якщо ми використовуємо UPSERT замість EXISTS. На жаль для Insert, і методи UPSERT, і IF EXISTS використовують однакову кількість читань на таблиці. Тому перевірку на існування слід проводити лише тоді, коли є дуже вагома причина для виправдання додаткового вводу-виводу. Оптимізований спосіб зробити це - переконатися, що у БД є мало можливостей для читання.

Найкраща стратегія - спроба оновлення. Якщо оновлення не впливає на жодні рядки, тоді вставляйте. У більшості випадків рядок уже буде існувати, і буде потрібно лише одне введення / виведення.

Редагувати : Будь ласка, ознайомтеся з цією відповіддю та пов’язаною публікацією блогу, щоб дізнатися про проблеми з цією схемою та як зробити її безпечною.


1
Що ж, це хоча б відповіло на одне питання, я думаю. І я не додав код, тому що код у питанні вже здався мені правильним. Хоча я би вклав це в транзакцію, я не враховував рівень ізоляції для оновлення. Дякуємо, що вказали на це у своїй відповіді!
binOr

54

Будь ласка, прочитайте публікацію в моєму блозі, щоб отримати хороший, безпечний зразок, який ви можете використовувати. Є багато міркувань, і прийнята відповідь на це питання далеко не безпечна.

Для швидкої відповіді спробуйте наступну схему. Це буде добре працювати на SQL 2000 і вище. SQL 2005 дає вам помилку, що відкриває інші параметри, а SQL 2008 дає команду MERGE.

begin tran
   update t with (serializable)
   set hitCount = hitCount + 1
   where pk = @id
   if @@rowcount = 0
   begin
      insert t (pk, hitCount)
      values (@id,1)
   end
commit tran

1
У своєму дописі на блозі ви закінчите, використовуючи підказку WITH (оновлення, серіалізація) під час перевірки існування. Однак при читанні MSDN йдеться: "UPDLOCK - Вказує, що блокування оновлень потрібно робити та утримувати до завершення транзакції." Чи означає це, що серіалізаційний натяк є зайвим, оскільки блокування оновлень все одно буде проведено до кінця транзакції, чи я щось неправильно зрозумів?
Dan Def

10

Для використання з SQL Server 2000/2005 оригінальний код повинен бути укладений в транзакцію, щоб переконатися, що дані залишаються послідовними у паралельному сценарії.

BEGIN TRANSACTION Upsert
update myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
insert into myTable (Col1, Col2) values (@col1, @col2)
COMMIT TRANSACTION Upsert

Це спричинить додаткові витрати на продуктивність, але забезпечить цілісність даних.

Додайте, як уже було запропоновано, MERGE слід використовувати там, де це можливо.



6

Вам не тільки потрібно запустити його в операції, він також потребує високого рівня ізоляції. Я фактично рівень ізоляції за замовчуванням - Read Commited, і цей код потребує Serializable.

SET transaction isolation level SERIALIZABLE
BEGIN TRANSACTION Upsert
UPDATE myTable set Col1=@col1, Col2=@col2 where ID=@ID
if @@rowcount = 0
  begin
    INSERT into myTable (ID, Col1, Col2) values (@ID @col1, @col2)
  end
COMMIT TRANSACTION Upsert

Можливо, додавання також перевірки помилки @@ та відкату може бути хорошою ідеєю.


@Munish Goyal Тому що в базі даних паралельно виконуються кілька команд і пріоритетів. Тоді інший потік може вставити рядок відразу після запуску оновлення та перед запуском вставки.
Tomas Tintera

5

Якщо ви не здійснюєте злиття в SQL 2008, ви повинні змінити його на:

якщо @@ rowcount = 0 і @@ помилка = 0

в іншому випадку, якщо оновлення не вдалося з якихось причин, воно спробує і потім вставити, тому що кількість рядків у невдалому виписці дорівнює 0


3

Великий шанувальник UPSERT, дійсно скорочує код для управління. Ось ще один спосіб зробити це: Один з вхідних параметрів - ID, якщо ідентифікатор NULL або 0, ви знаєте, що це ВСТАВКА, інакше це оновлення. Припускає, що програма знає, чи є ідентифікатор, тому не буде працювати у будь-яких ситуаціях, але розріже виконання в два рази, якщо це зробити.


2

Змінена посада Діми Маленко:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE 

BEGIN TRANSACTION UPSERT 

UPDATE MYTABLE 
SET    COL1 = @col1, 
       COL2 = @col2 
WHERE  ID = @ID 

IF @@rowcount = 0 
  BEGIN 
      INSERT INTO MYTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

IF @@Error > 0 
  BEGIN 
      INSERT INTO MYERRORTABLE 
                  (ID, 
                   COL1, 
                   COL2) 
      VALUES      (@ID, 
                   @col1, 
                   @col2) 
  END 

COMMIT TRANSACTION UPSERT 

Ви можете зафіксувати помилку та надіслати запис до невдалої таблиці вставки.
Мені потрібно було це зробити, оскільки ми приймаємо будь-які дані, що надсилаються через WSDL, і, якщо можливо, виправляючи їх всередині.


1

Ваша логіка здається здоровою, але ви, можливо, захочете додати якийсь код, щоб запобігти вставленню, якщо ви перейшли до певного первинного ключа.

В іншому випадку, якщо ви завжди робите вставку, якщо оновлення не вплинуло на записи, що відбувається, коли хтось видаляє запис перед запуском "UPSERT"? Тепер запис, який ви намагалися оновити, не існує, тому він створить запис замість цього. Мабуть, це не поведінка, яку ви шукали.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.