Sqlserver
 sql >> база данни >  >> RDS >> Sqlserver

Не може да се използва UPDATE с клауза OUTPUT, когато има тригер в таблицата

Предупреждение за видимост :Не отговаряйте на другия. Ще даде неправилни стойности. Прочетете защо е грешно.

Като се има предвид kludge, необходим за извършване на UPDATE с OUTPUT работи в SQL Server 2008 R2, промених заявката си от:

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

до:

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

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

По принцип спрях да използвам OUTPUT . Това не е толкова лошо, колкото самата Entity Framework използва същия този хак!

Надяваме се 2012 2014 2016 2018 2019 2020 г. ще има по-добро изпълнение.

Актуализация:използването на OUTPUT е вредно

Проблемът, с който започнахме, беше да се опитаме да използваме OUTPUT клауза за извличане на "след" стойности в таблица:

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

Това след това достига ограничението за добре познаване („няма да се поправи“ грешка) в SQL Server:

Целевата таблица „BatchReports“ на DML израза не може да има никакви активирани задействания, ако изразът съдържа клауза OUTPUT без клауза INTO

Опит за заобикаляне №1

Така че опитваме нещо, където ще използваме междинна TABLE променлива за задържане на OUTPUT резултати:

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

Само дето това е неуспешно, защото не ви е позволено да вмъкнете timestamp в таблицата (дори и временна променлива на таблица).

Опит за заобикаляне #2

Тайно знаем, че timestamp всъщност е 64-битово (известно още като 8 байта) цяло число без знак. Можем да променим нашата временна дефиниция на таблица, за да използваме binary(8) вместо 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

И това работи,освен че стойностите са грешни .

Знакът за време RowVersion ние връщаме не е стойността на времевата марка, както е съществувала след завършване на АКТУАЛИЗИРАНЕТО:

  • Върнат времеви печат :0x0000000001B71692
  • действително времеви печат :0x0000000001B71693

Това е така, защото стойностите OUTPUT в нашата таблица сане стойностите, каквито бяха в края на оператора UPDATE:

  • Започва оператор UPDATE
    • променя ред
      • марката за време е актуализирана (напр. 2 → 3)
    • OUTPUT извлича ново времеви печат (т.е. 3)
    • спусъкът се изпълнява
      • променя отново ред
        • клеймото за време е актуализирано (напр. 3 → 4)
  • Изявлението UPDATE е завършено
  • OUTPUT връща 3 (грешна стойност)

Това означава:

  • Не получаваме клеймото за време, както съществува в края на оператора UPDATE (4 )
  • Вместо това получаваме клеймото за време, както беше в неопределената среда на оператора UPDATE (3 )
  • Не получаваме правилната времева марка

Същото важи и завсякото тригер, който променя всяко стойност в реда. OUTPUT няма да ИЗВЕДЕ стойността към края на АКТУАЛИЗИРАНЕТО.

Това означава, че не можете да имате доверие на OUTPUT да върне правилни стойности.

Тази болезнена реалност е документирана в BOL:

Колоните, върнати от OUTPUT, отразяват данните, каквито са, след като операторът INSERT, UPDATE или DELETE е завършил, но преди да бъдат изпълнени тригерите.

Как го реши Entity Framework?

.NET Entity Framework използва версия на редове за оптимистичен паралелност. EF зависи от познаването на стойността на timestamp както съществува, след като издадат АКТУАЛИЗИРАНЕ.

Тъй като не можете да използвате OUTPUT за всякакви важни данни Entity Framework на Microsoft използва същото решение, което правя аз:

Заобиколно решение №3 – Окончателно – Не използвайте клауза OUTPUT

За да извлечете след стойности, проблеми с Entity Framework:

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

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

Не използвайте OUTPUT .

Да, той страда от състезание, но това е най-доброто, което SQL Server може да направи.

Какво ще кажете за INSERT

Правете това, което прави Entity Framework:

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

Отново те използват SELECT изявление за четене на реда, вместо да се доверява на клаузата 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. VBA код за добавяне на свързана таблица с първичен ключ

  2. Каква е разликата между използването на кръстосано свързване и поставянето на запетая между двете таблици?

  3. Възможно ли е да се използва пълнотекстово търсене (FTS) с LINQ?

  4. Как да потърся Xml стойности и атрибути от таблица в SQL Server?

  5. 2 начина да проверите дали достъпът до данни е разрешен в SQL Server (примери за T-SQL)