Apakah aman untuk bergantung pada urutan klausa OUTPUT INSERT?

19

Diberikan tabel ini:

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

Dalam dua skenario yang sedikit berbeda, saya ingin menyisipkan baris dan mengembalikan nilai dari kolom identitas.

skenario 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)
;

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

Pertanyaan

Dapatkah saya mengandalkan nilai identitas yang dikembalikan dari dbo.Targetsisipan tabel untuk dikembalikan sesuai urutan yang ada pada tabel 1) VALUESklausa dan 2) #Target, sehingga saya dapat mengkorelasikannya dengan posisi mereka di rowset output kembali ke input asli?

Sebagai referensi

Berikut adalah beberapa kode C # yang dipangkas yang menunjukkan apa yang terjadi dalam aplikasi (skenario 1, segera akan dikonversi untuk digunakan 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();
}
ErikE
sumber

Jawaban:

22

Dapatkah saya mengandalkan nilai identitas yang dikembalikan dari dbo.Tabel tabel target yang akan dikembalikan sesuai urutan yang ada dalam 1) NILAI klausa dan 2) #Target tabel, sehingga saya bisa menghubungkannya dengan posisi mereka di output rowset kembali ke input asli?

Tidak, Anda tidak dapat mengandalkan apa pun untuk dijamin tanpa jaminan yang terdokumentasi aktual. Dokumentasi secara eksplisit menyatakan tidak ada jaminan seperti itu.

SQL Server tidak menjamin urutan baris diproses dan dikembalikan oleh pernyataan DML menggunakan klausa OUTPUT. Terserah pada aplikasi untuk memasukkan klausa WHERE yang sesuai yang dapat menjamin semantik yang diinginkan, atau memahami bahwa ketika beberapa baris memenuhi syarat untuk operasi DML, tidak ada pesanan yang dijamin.

Ini akan bergantung pada banyak asumsi tidak berdokumen

  1. Urutan baris yang dihasilkan dari pemindaian konstan berada dalam urutan yang sama dengan klausa nilai (Saya belum pernah melihatnya berbeda tetapi AFAIK ini tidak dijamin).
  2. Urutan baris yang dimasukkan akan sama dengan urutan hasil dari pemindaian konstan (jelas tidak selalu demikian).
  3. Jika menggunakan rencana eksekusi "lebar" (per indeks), nilai-nilai dari klausa output akan ditarik dari operator pembaruan indeks berkerumun dan bukan dari indeks sekunder mana pun.
  4. Bahwa pesanan dijamin akan dipertahankan sesudahnya - misalnya ketika kemasan baris untuk transmisi melalui jaringan .
  5. Bahwa bahkan jika pesanan tampak dapat diprediksi sekarang, perubahan implementasi pada fitur seperti penyisipan paralel tidak akan mengubah urutan di masa mendatang (saat ini jika klausa OUTPUT ditentukan dalam pernyataan INSERT ... SELECT untuk mengembalikan hasil kepada klien, maka paket paralel adalah dinonaktifkan secara umum, termasuk INSERT )

Contoh titik dua gagal (dengan asumsi PK berkerumun (Color, Action)) dapat dilihat jika Anda menambahkan 600 baris ke VALUESklausa. Kemudian paket memiliki operator sortir sebelum memasukkan sehingga kehilangan pesanan awal Anda dalam VALUESklausa.

Ada cara yang terdokumentasi untuk mencapai tujuan Anda dan ini adalah menambahkan penomoran ke sumber dan menggunakan MERGEalih-alihINSERT

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; 

masukkan deskripsi gambar di sini

@seekor kuda tanpa nama

Apakah penggabungan ini benar-benar diperlukan? Tidak bisakah kamu melakukan insert into ... select ... from (values (..)) t (...) order by sourceid?

Ya kamu bisa. Memesan jaminan dalam SQL Server ... menyatakan itu

MASUKKAN kueri yang menggunakan SELECT dengan ORDER BY untuk mengisi baris menjamin bagaimana nilai identitas dikomputasi tetapi bukan urutan penempatan baris

Jadi bisa digunakan

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

masukkan deskripsi gambar di sini

Ini akan menjamin bahwa nilai-nilai identitas diberikan dalam urutan t.SourceIdtetapi bukan bahwa itu adalah output dalam urutan tertentu atau bahwa nilai-nilai kolom identitas yang diberikan tidak memiliki kesenjangan (misalnya jika upaya penyisipan dilakukan bersamaan).

Martin Smith
sumber
2
Bagian terakhir ini tentang potensi kesenjangan dan output yang tidak berada dalam urutan tertentu membuat hal-hal sedikit lebih menarik untuk mencoba menghubungkan kembali ke input. Saya kira pesanan oleh dalam aplikasi akan melakukan pekerjaan, tetapi tampaknya lebih aman dan lebih jelas untuk hanya menggunakan MERGE.
ErikE
Gunakan OUTPUT ... INTO [#temp]sintaks, lalu SELECT ... FROM [#temp] ORDER BYuntuk menjamin urutan output.
Max Vernon
TL; versi DR: Dengan SQL Server, dan saya percaya implementasi SQL secara umum, kecuali ada klausa ORDER BY, pesanan tidak dijamin.
nateirvin
Kembali. gap: Apakah melampirkan InsertPernyataan dalam Transactionmencegah kesenjangan?
Tom