Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Wie implementiert man eine bedingte gespeicherte Upsert-Prozedur?

Ich habe das folgende Skript zusammengestellt, um diesen Trick zu beweisen, den ich in den vergangenen Jahren verwendet habe. Wenn Sie es verwenden, müssen Sie es an Ihre Zwecke anpassen. Kommentare folgen:

/*
CREATE TABLE Item
 (
   Title      varchar(255)  not null
  ,Teaser     varchar(255)  not null
  ,ContentId  varchar(30)  not null
  ,RowLocked  bit  not null
)


UPDATE item
 set RowLocked = 1
 where ContentId = 'Test01'

*/


DECLARE
  @Check varchar(30)
 ,@pContentID varchar(30)
 ,@pTitle varchar(255)
 ,@pTeaser varchar(255)

set @pContentID = 'Test01'
set @pTitle     = 'TestingTitle'
set @pTeaser    = 'TestingTeasier'

set @check = null

UPDATE dbo.Item
 set
   @Check = ContentId
  ,Title  = @pTitle
  ,Teaser = @pTeaser
 where ContentID = @pContentID
  and RowLocked = 0

print isnull(@check, '<check is null>')

IF @Check is null
    INSERT dbo.Item (ContentID, Title, Teaser, RowLocked)
     values (@pContentID, @pTitle, @pTeaser, 0)

select * from Item

Der Trick dabei ist, dass Sie Werte in lokalen Variablen innerhalb einer Update-Anweisung setzen können. Oben wird der „Flag“-Wert nur gesetzt, wenn die Aktualisierung funktioniert (d. h. die Aktualisierungskriterien erfüllt sind); Andernfalls wird es nicht geändert (hier bei null belassen), Sie können dies überprüfen und entsprechend verarbeiten.

Bezüglich der Transaktion und ihrer Serialisierbarkeit würde ich gerne mehr darüber erfahren, was in der Transaktion gekapselt werden muss, bevor ich vorschlage, wie weiter vorzugehen ist.

-- Ergänzungen, Fortsetzung des zweiten Kommentars unten -----------

Die Ideen von Mr. Saffron sind eine gründliche und solide Art und Weise, diese Routine zu implementieren, da Ihre Primärschlüssel außerhalb definiert und an die Datenbank übergeben werden (d.h. Sie verwenden keine Identitätsspalten – gut von mir, sie werden oft überbeansprucht).

Ich habe einige weitere Tests durchgeführt (eine Primärschlüsseleinschränkung für die Spalte ContentId hinzugefügt, UPDATE und INSERT in eine Transaktion eingeschlossen, den serialisierbaren Hinweis zum Update hinzugefügt) und ja, das sollte alles tun, was Sie wollen. Die fehlgeschlagene Aktualisierung belegt diesen Teil des Index mit einer Bereichssperre, wodurch alle gleichzeitigen Versuche, diesen neuen Wert in die Spalte einzufügen, blockiert werden. Wenn N Anfragen gleichzeitig gesendet werden, erstellt natürlich die "erste" die Zeile, und sie wird sofort von der zweiten, dritten usw. aktualisiert - es sei denn, Sie setzen das "Schloss" irgendwo entlang der Zeile. Guter Trick!

(Beachten Sie, dass Sie ohne den Index in der Schlüsselspalte die gesamte Tabelle sperren würden. Außerdem kann die Bereichssperre die Zeilen auf "beiden Seiten" des neuen Werts sperren - oder vielleicht nicht, ich habe es nicht getan Testen Sie das. Sollte keine Rolle spielen, da die Dauer der Operation [?] im einstelligen Millisekundenbereich liegen sollte.)