61 Stimmen

Gibt es eine Möglichkeit, Zeilen gleichzeitig zu SELECT und UPDATE zu machen?

Ich möchte eine Reihe von Zeilen auf der Grundlage eines einfachen Kriteriums aktualisieren und die Liste der PKs erhalten, die geändert wurden. Ich dachte, ich könnte einfach so etwas tun, aber bin besorgt über mögliche Gleichzeitigkeitsprobleme:

SELECT Id FROM Table1 WHERE AlertDate IS NULL;
UPDATE Table1 SET AlertDate = getutcdate() WHERE AlertDate IS NULL;

Wenn das in einer Transaktion verpackt ist, gibt es irgendwelche Gleichzeitigkeitsprobleme, die auftreten können? Oder gibt es eine bessere Möglichkeit, dies zu tun?

1 Stimmen

Ich denke, das ist bisher die beste Lösung stackoverflow.com/questions/22066397/

91voto

Mark Canlas Punkte 9187

Erwägen Sie einen Blick auf die OUTPUT-Klausel :

USE AdventureWorks2012;  
GO  

DECLARE @MyTableVar table(  
    EmpID int NOT NULL,  
    OldVacationHours int,  
    NewVacationHours int,  
    ModifiedDate datetime);  

UPDATE TOP (10) HumanResources.Employee  
SET VacationHours = VacationHours * 1.25,  
    ModifiedDate = GETDATE()   
OUTPUT inserted.BusinessEntityID,  
       deleted.VacationHours,  
       inserted.VacationHours,  
       inserted.ModifiedDate  
INTO @MyTableVar;  

--Display the result set of the table variable.  
SELECT EmpID, OldVacationHours, NewVacationHours, ModifiedDate  
FROM @MyTableVar;  
GO  
--Display the result set of the table.  
SELECT TOP (10) BusinessEntityID, VacationHours, ModifiedDate  
FROM HumanResources.Employee;  
GO

23voto

K. R. Punkte 1102

Viele Jahre später...

Die akzeptierte Antwort der Verwendung der OUTPUT-Klausel ist gut. Ich musste die eigentliche Syntax ausgraben, also hier ist sie:

DECLARE @UpdatedIDs table (ID int)
UPDATE 
    Table1 
SET 
    AlertDate = getutcdate() 
OUTPUT
    inserted.Id
INTO
    @UpdatedIDs
WHERE 
    AlertDate IS NULL;

HINZUFÜGEN SEP 14, 2015:

"Kann ich eine skalare Variable anstelle einer Tabellenvariablen verwenden?", könnte man fragen... Tut mir leid, aber das können Sie nicht. Sie müssen SELECT @SomeID = ID from @UpdatedIDs wenn Sie eine einzelne ID benötigen.

20voto

Bill Karwin Punkte 493880

Eine Möglichkeit, damit umzugehen, ist, dies in einer Transaktion zu tun und Ihre SELECT-Abfrage mit einer Aktualisierungssperre für die ausgewählten Zeilen zu versehen, bis die Transaktion abgeschlossen ist.

BEGIN TRAN

SELECT Id FROM Table1 WITH (UPDLOCK)
WHERE AlertDate IS NULL;

UPDATE Table1 SET AlertDate = getutcdate() 
WHERE AlertDate IS NULL;

COMMIT TRAN 

Dadurch wird die Möglichkeit ausgeschlossen, dass ein konkurrierender Client die ausgewählten Zeilen in dem Moment zwischen Ihrem SELECT und Ihrem UPDATE aktualisiert.

Wenn Sie die Transaktion bestätigen, werden die Aktualisierungssperren freigegeben.

Eine andere Möglichkeit ist, einen Cursor für Ihr SELECT mit der Option FOR UPDATE zu deklarieren. Dann UPDATE WHERE CURRENT OF CURSOR. Das Folgende ist nicht getestet, sollte Ihnen aber die Grundidee vermitteln:

DECLARE cur1 CURSOR FOR
  SELECT AlertDate FROM Table1 
  WHERE AlertDate IS NULL
  FOR UPDATE;

DECLARE @UpdateTime DATETIME

SET @UpdateTime = GETUTCDATE()

OPEN cur1;

FETCH NEXT FROM cur1;

WHILE @@FETCH_STATUS = 0
BEGIN

  UPDATE Table1 
  SET AlertDate = @UpdateTime  --set value
  WHERE CURRENT OF cur1;

  FETCH NEXT FROM cur1;

END

5 Stimmen

+1 für UPDLOCK, es ist eine korrekte Lösung für dieses Problem und in kurzen Transaktionen wird nicht zu Deadlocks führen

0 Stimmen

Wird dies dazu führen, dass alle Zeilen denselben Datumswert erhalten (wie bei einem einzigen SQL-Aufruf)? Wenn nicht, sollten Sie die Zeit vor der Schleife in einer Variablen speichern und AltertDate einfach auf die Variable setzen. Wenn das ein Problem ist, bearbeiten Sie dies bitte.

11voto

Kevin Fairchild Punkte 10701

Es wäre einfacher, zuerst Ihr UPDATE durchzuführen und dann SELECT ID FROM INSERTED" auszuführen.

Werfen Sie einen Blick auf SQL-Tipps für weitere Informationen und Beispiele.

0 Stimmen

Das ist eine geniale Idee für einfache Abfragen!

0 Stimmen

Dies würde in einem Trigger funktionieren, aber würde es in einer gespeicherten Prozedur oder einem anderen Ausführungskontext funktionieren?

0 Stimmen

@aheho, derselbe grundlegende Ansatz kann innerhalb einer proc verwendet werden, indem Sie eine Ausgabeklausel für Ihre Aktualisierungsanweisung verwenden.

6voto

Gordon Bell Punkte 12689

Vielleicht eher so etwas?

declare @UpdateTime datetime

set @UpdateTime = getutcdate()

update Table1 set AlertDate = @UpdateTime where AlertDate is null

select ID from Table1 where AlertDate = @UpdateTime

2 Stimmen

Dies hat den Vorteil, dass selbst wenn Sie dies nicht innerhalb einer Transaktion tun, wird es sehr Es ist schwierig, Ergebnisse zu erzielen, die durch einen anderen Prozess verändert wurden.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X