sql >> Database teknologi >  >> RDS >> Sqlserver

Kan ikke bruge UPDATE med OUTPUT-klausul, når en trigger er på bordet

Synlighedsadvarsel :Lad være med at svare den anden. Det vil give forkerte værdier. Læs videre for, hvorfor det er forkert.

Givet den kludge, der er nødvendig for at lave UPDATE med OUTPUT arbejde i SQL Server 2008 R2, ændrede jeg min forespørgsel fra:

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

til:

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

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

Grundlæggende holdt jeg op med at bruge OUTPUT . Dette er ikke så slemt som selve Entity Framework bruger det samme hack!

Forhåbentlig 2012 2014 2016 2018 2019 2020 vil have en bedre implementering.

Opdatering:Brug af OUTPUT er skadeligt

Det problem, vi startede med, var at prøve at bruge OUTPUT klausul for at hente "efter" værdier i en tabel:

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

Det rammer så den velkendte begrænsning ("vil ikke rette" bug) i SQL Server:

Måltabellen 'BatchReports' i DML-sætningen kan ikke have nogen aktiverede triggere, hvis sætningen indeholder en OUTPUT-sætning uden INTO-sætning

Løsningsforsøg #1

Så vi prøver noget, hvor vi vil bruge en mellemliggende TABLE variabel for at holde OUTPUT resultater:

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

Bortset fra at det mislykkes, fordi du ikke har tilladelse til at indsætte et timestamp ind i tabellen (selv en midlertidig tabelvariabel).

Løsningsforsøg #2

Vi ved i al hemmelighed, at et timestamp er faktisk et 64-bit (alias 8 byte) heltal uden fortegn. Vi kan ændre vores midlertidige tabeldefinition til at bruge binary(8) i stedet for 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

Og det virker, bortset fra at værdien er forkert .

Tidsstemplet RowVersion vi returnerer er ikke værdien af ​​tidsstemplet, som det eksisterede efter OPDATERING fuldført:

  • returneret tidsstempel :0x0000000001B71692
  • faktisk tidsstempel :0x0000000001B71693

Det er fordi værdierne OUTPUT i vores tabel er ikke værdierne, som de var i slutningen af ​​UPDATE-sætningen:

  • UPDATE-erklæring starter
    • ændrer række
      • tidsstemplet er opdateret (f.eks. 2 → 3)
    • OUTPUT henter nyt tidsstempel (dvs. 3)
    • trigger kører
      • ændrer række igen
        • tidsstemplet er opdateret (f.eks. 3 → 4)
  • OPDATERING er fuldført
  • OUTPUT returnerer 3 (den forkerte værdi)

Det betyder:

  • Vi får ikke tidsstemplet, da det findes i slutningen af ​​UPDATE-sætningen (4 )
  • I stedet får vi tidsstemplet, som det var i den ubestemte midte af UPDATE-sætningen (3 )
  • Vi får ikke det korrekte tidsstempel

Det samme gælder enhver trigger, der ændrer enhver værdi i rækken. OUTPUT vil ikke OUTPUT værdien fra slutningen af ​​OPDATERING.

Dette betyder, at du aldrig kan stole på, at OUTPUT returnerer nogen korrekte værdier.

Denne smertefulde virkelighed er dokumenteret i BOL:

Kolonner, der returneres fra OUTPUT, afspejler dataene, som de er, efter INSERT-, UPDATE- eller DELETE-sætningen er fuldført, men før triggere udføres.

Hvordan løste Entity Framework det?

.NET Entity Framework bruger rækkeversion til Optimistisk samtidighed. EF afhænger af at kende værdien af ​​timestamp som det eksisterer efter de har udstedt en OPDATERING.

Da du ikke kan bruge OUTPUT for alle vigtige data bruger Microsofts Entity Framework den samme løsning, som jeg gør:

Løsning #3 - Endelig - Brug ikke OUTPUT-sætning

For at hente efter værdier, Entity Framework-spørgsmål:

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

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

Brug ikke OUTPUT .

Ja, den lider af en racetilstand, men det er det bedste SQL Server kan gøre.

Hvad med INSERTs

Gør hvad Entity Framework gør:

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

Igen bruger de en SELECT sætning for at læse rækken, i stedet for at stole på OUTPUT-sætningen.



  1. Python psycopg2 indsættes ikke i postgresql-tabellen

  2. Få århundredet fra en dato i PostgreSQL

  3. Linux - PHP 7.0 og MSSQL (Microsoft SQL)

  4. Fejlfinding:For mange omdirigeringer