Чи безпечно покладатися на порядок дії INSERT's OUTPUT?


19

Враховуючи цю таблицю:

CREATE TABLE dbo.Target (
   TargetId int identity(1, 1) NOT NULL,
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL, -- of course this should be normalized
   Code int NOT NULL,
   CONSTRAINT PK_Target PRIMARY KEY CLUSTERED (TargetId)
);

У двох дещо різних сценаріях я хочу вставити рядки та повернути значення з стовпця ідентичності.

Сценарій 1

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
   (VALUES
      ('Blue', 'New', 1234),
      ('Blue', 'Cancel', 4567),
      ('Red', 'New', 5678)
   ) t (Color, Action, Code)
;

Сценарій 2

CREATE TABLE #Target (
   Color varchar(20) NOT NULL,
   Action varchar(10) NOT NULL,
   Code int NOT NULL,
   PRIMARY KEY CLUSTERED (Color, Action)
);

-- Bulk insert to the table the same three rows as above by any means

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM #Target
;

Питання

Чи можу я покластися на повернені значення ідентичності із dbo.Targetвставки таблиці, яку потрібно повернути у тому порядку, який вони існували в VALUESпункті 1) та 2) #Target, щоб я міг співвіднести їх за своїм положенням у наборі наборів рядків назад до початкового вводу?

Для довідки

Ось окремий C # код, який демонструє те, що відбувається в додатку (сценарій 1, який незабаром слід перетворити на використання SqlBulkCopy):

public IReadOnlyCollection<Target> InsertTargets(IEnumerable<Target> targets) {
   var targetList = targets.ToList();
   const string insertSql = @"
      INSERT dbo.Target (
         CoreItemId,
         TargetDateTimeUtc,
         TargetTypeId,
      )
      OUTPUT
         Inserted.TargetId
      SELECT
         input.CoreItemId,
         input.TargetDateTimeUtc,
         input.TargetTypeId,
      FROM
         (VALUES
            {0}
         ) input (
            CoreItemId,
            TargetDateTimeUtc,
            TargetTypeId
         );";
   var results = Connection.Query<DbTargetInsertResult>(
      string.Format(
         insertSql,
         string.Join(
            ", ",
            targetList
               .Select(target => $@"({target.CoreItemId
                  }, '{target.TargetDateTimeUtc:yyyy-MM-ddTHH:mm:ss.fff
                  }', {(byte) target.TargetType
                  })";
               )
         )
      )
      .ToList();
   return targetList
      .Zip( // The correlation that relies on the order of the two inputs being the same
         results,
         (inputTarget, insertResult) => new Target(
            insertResult.TargetId, // with the new TargetId to replace null.
            inputTarget.TargetDateTimeUtc,
            inputTarget.CoreItemId,
            inputTarget.TargetType
         )
      )
      .ToList()
      .AsReadOnly();
}

Відповіді:


22

Чи можу я покластися на повернені значення ідентичності з dbo.Target table вставити для повернення у тому порядку, який вони існували у пункті 1) VALUES та 2) # Target table, щоб я міг співвіднести їх за їх позицією у вихідному наборі рядків назад до вихідного вводу?

Ні, ви не можете розраховувати на те, що можна гарантувати без фактичної документально підтвердженої гарантії. У документації прямо зазначено, що такої гарантії немає.

SQL Server не гарантує порядок, в якому рядки обробляються та повертаються операторами DML за допомогою пункту OUTPUT. Додаток повинен включати відповідний пункт WHERE, який може гарантувати бажану семантику, або розуміти, що коли для операції DML може бути кваліфіковано кілька рядків, немає гарантованого порядку.

Це спиралося б на безліч незадокументованих припущень

  1. Порядок виведення рядків з постійного сканування в тому ж порядку, що і значення значень (я ніколи не бачив, щоб вони відрізнялися, але AFAIK це не гарантується).
  2. Порядок вставки рядків буде таким самим, як і порядок, який вони виводяться при постійному скануванні (безумовно, це не завжди так).
  3. Якщо за допомогою плану виконання "широкого" (на індекс) значення з пункту виведення буде виведено з оператора оновлення кластерного оновлення, а не з будь-яких вторинних індексів.
  4. Таке замовлення гарантовано збережеться і надалі - наприклад, під час упаковки рядків для передачі по мережі .
  5. Навіть якщо замовлення видається передбачуваним, тепер зміни в застосуванні таких функцій, як паралельна вставка, не змінюватимуть порядок у майбутньому (в даний час, якщо пункт OUTPUT вказаний в операторі INSERT… SELECT для повернення результату клієнтові, то паралельні плани є загалом відключено, включаючи INSERT )

Приклад пункту другого відмови (якщо припустити кластеризований ПК з (Color, Action)) можна побачити, якщо додати додаток до 600 рядків VALUES. Тоді в плані є оператор сортування перед вставкою, що втрачає ваш початковий порядок у VALUESпункті.

Існує задокументований спосіб досягнення вашої мети, і це додати нумерацію до джерела та використовувати MERGEзамість цьогоINSERT

MERGE dbo.Target
USING (VALUES (1, 'Blue', 'New', 1234),
              (2, 'Blue', 'Cancel', 4567),
              (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ON 1 = 0
WHEN NOT MATCHED THEN
  INSERT (Color,
          Action,
          Code)
  VALUES (Color,
          Action,
          Code)
OUTPUT t.SourceId,
       inserted.TargetId; 

введіть тут опис зображення

@a_horse_with_no_name

Чи дійсно необхідне злиття? Чи не могли ви просто зробити insert into ... select ... from (values (..)) t (...) order by sourceid?

Так, ти міг. Замовлення гарантій у SQL Server… стверджує, що

INSERT-запити, які використовують SELECT з ORDER BY для заповнення рядків, гарантує, як обчислюються значення ідентичності, але не порядок введення рядків

Таким чином, ви можете використовувати

INSERT dbo.Target (Color, Action, Code)
OUTPUT inserted.TargetId
SELECT t.Color, t.Action, t.Code
FROM
(VALUES (1, 'Blue', 'New', 1234),
        (2, 'Blue', 'Cancel', 4567),
        (3, 'Red', 'New', 5678) ) t (SourceId, Color, Action, Code)
ORDER BY t.SourceId

введіть тут опис зображення

Це гарантувало б, що значення ідентичності присвоюються в порядку, t.SourceIdале не те, що вони виводяться в будь-якому конкретному порядку або що присвоєні значення стовпців ідентичності не мають прогалин (наприклад, при спробі паралельної вставки).


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

Використовуйте OUTPUT ... INTO [#temp]синтаксис, SELECT ... FROM [#temp] ORDER BYщоб гарантувати вихідний порядок.
Макс Вернон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.