Sqlserver
 sql >> Teknologi Basis Data >  >> RDS >> Sqlserver

Tidak dapat menggunakan UPDATE dengan klausa OUTPUT saat pemicu ada di atas meja

Peringatan Visibilitas :Jangan jawaban yang lain. Ini akan memberikan nilai yang salah. Baca terus mengapa itu salah.

Mengingat kludge yang dibutuhkan untuk membuat UPDATE dengan OUTPUT bekerja di SQL Server 2008 R2, saya mengubah kueri saya dari:

UPDATE BatchReports  
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid

ke:

SELECT BatchFileXml, ResponseFileXml, ProcessedDate FROM BatchReports
WHERE BatchReports.BatchReportGUID = @someGuid

UPDATE BatchReports
SET IsProcessed = 1
WHERE BatchReports.BatchReportGUID = @someGuid

Pada dasarnya saya berhenti menggunakan OUTPUT . Ini tidak seburuk Entity Framework itu sendiri menggunakan peretasan yang sama!

Semoga 2012 2014 2016 2018 2019 Tahun 2020 akan memiliki implementasi yang lebih baik.

Pembaruan:menggunakan OUTPUT berbahaya

Masalah yang kami mulai dengan mencoba menggunakan OUTPUT klausa untuk mengambil "setelah" nilai dalam tabel:

UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
WHERE BatchReports.BatchReportGUID = @someGuid

Itu kemudian mencapai batasan yang sudah diketahui ("tidak akan diperbaiki" bug) di SQL Server:

Tabel target 'BatchReports' dari pernyataan DML tidak dapat mengaktifkan pemicu apa pun jika pernyataan tersebut berisi klausa OUTPUT tanpa klausa INTO

Upaya Solusi #1

Jadi kami mencoba sesuatu di mana kami akan menggunakan TABLE perantara variabel untuk menampung OUTPUT hasil:

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion timestamp, 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

Kecuali itu gagal karena Anda tidak diizinkan memasukkan timestamp ke dalam tabel (bahkan variabel tabel sementara).

Upaya Solusi #2

Kami diam-diam mengetahui bahwa timestamp sebenarnya adalah bilangan bulat tidak bertanda 64-bit (alias 8 byte). Kita dapat mengubah definisi tabel sementara untuk menggunakan binary(8) daripada timestamp :

DECLARE @t TABLE (
   LastModifiedDate datetime,
   RowVersion binary(8), 
   BatchReportID int
)
  
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.LastModifiedDate, inserted.RowVersion, inserted.BatchReportID
INTO @t
WHERE BatchReports.BatchReportGUID = @someGuid

SELECT * FROM @t

Dan itu berhasil, kecuali nilainya salah .

Stempel waktu RowVersion yang kami kembalikan bukanlah nilai stempel waktu seperti yang ada setelah UPDATE selesai:

  • stempel waktu yang dikembalikan :0x0000000001B71692
  • stempel waktu sebenarnya :0x0000000001B71693

Itu karena nilai OUTPUT ke dalam tabel kami tidak nilai-nilai seperti yang ada di akhir pernyataan UPDATE:

  • PERBARUI pernyataan dimulai
    • memodifikasi baris
      • stempel waktu diperbarui (mis. 2 → 3)
    • OUTPUT mengambil stempel waktu baru (yaitu 3)
    • pemicu berjalan
      • memodifikasi baris lagi
        • stempel waktu diperbarui (mis. 3 → 4)
  • PERBARUI pernyataan selesai
  • OUTPUT mengembalikan 3 (nilai yang salah)

Artinya:

  • Kami tidak mendapatkan stempel waktu seperti yang ada di akhir pernyataan UPDATE (4 )
  • Sebagai gantinya, kami mendapatkan stempel waktu seperti yang ada di tengah tak tentu dari pernyataan UPDATE (3 )
  • Kami tidak mendapatkan stempel waktu yang benar

Hal yang sama berlaku untuk apa pun pemicu yang mengubah apa saja nilai dalam baris. OUTPUT tidak akan OUTPUT nilai pada akhir UPDATE.

Ini berarti Anda tidak dapat mempercayai OUTPUT untuk mengembalikan nilai yang benar.

Realitas menyakitkan ini didokumentasikan dalam BOL:

Kolom yang dikembalikan dari OUTPUT mencerminkan data apa adanya setelah pernyataan INSERT, UPDATE, atau DELETE selesai tetapi sebelum pemicu dieksekusi.

Bagaimana Entity Framework menyelesaikannya?

.NET Entity Framework menggunakan versi baris untuk Konkurensi Optimis. EF bergantung pada mengetahui nilai timestamp sebagaimana adanya setelah mereka mengeluarkan UPDATE.

Karena Anda tidak dapat menggunakan OUTPUT untuk data penting apa pun, Kerangka Entitas Microsoft menggunakan solusi yang sama seperti yang saya lakukan:

Solusi #3 - Final - Jangan gunakan klausa OUTPUT

Untuk mengambil setelah nilai, masalah Kerangka Entitas:

UPDATE [dbo].[BatchReports]
SET [IsProcessed] = @0
WHERE (([BatchReportGUID] = @1) AND ([RowVersion] = @2))

SELECT [RowVersion], [LastModifiedDate]
FROM [dbo].[BatchReports]
WHERE @@ROWCOUNT > 0 AND [BatchReportGUID] = @1

Jangan gunakan OUTPUT .

Ya, ia mengalami kondisi balapan, tetapi itulah yang terbaik yang dapat dilakukan SQL Server.

Bagaimana dengan INSERT

Lakukan apa yang Entity Framework lakukan:

SET NOCOUNT ON;

DECLARE @generated_keys table([CustomerID] int)

INSERT Customers (FirstName, LastName)
OUTPUT inserted.[CustomerID] INTO @generated_keys
VALUES ('Steve', 'Brown')

SELECT t.[CustomerID], t.[CustomerGuid], t.[RowVersion], t.[CreatedDate]
FROM @generated_keys AS g
   INNER JOIN Customers AS t
   ON g.[CustomerGUID] = t.[CustomerGUID]
WHERE @@ROWCOUNT > 0

Sekali lagi, mereka menggunakan SELECT pernyataan untuk membaca baris, daripada menaruh kepercayaan pada klausa OUTPUT.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Klausa WHERE vs ON saat menggunakan GABUNG

  2. Cara Mengganti Nama Database di SQL Server - Tutorial SQL Server / TSQL Bagian 26

  3. Fungsi format tanggal SQL Server

  4. Hapus Data melalui Fungsi Bernilai Tabel di SQL Server

  5. Bagaimana cara membuat batasan unik yang juga memungkinkan nol?