views:

898

answers:

7

I'd like to update a set of rows based on a simple criteria and get the list of PKs that were changed. I thought I could just do something like this but am worried about possible concurrency problems:

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

If that is wrapped in a transaction are there any concurrency issues that can occur? Or is there a better way to do this?

+7  A: 

It'd be easier to do your UPDATE first and then run 'SELECT ID FROM INSERTED'.

Take a look at SQL Tips for more info and examples.

Kevin Fairchild
+1 - this is a nice feature
Optimal Solutions
+21  A: 

Consider looking at the OUTPUT clause capability of UPDATE.

http://msdn.microsoft.com/en-us/library/ms177564(SQL.90).aspx

Mark Canlas
maybe somebody with edit abilities can change this answer so the link is actually a link? good answer.
Sailing Judo
Woah. I had no idea you could do that! That greatly simplifies a number of tedious things I find myself doing. Many thanks indeed - off ot read-up on it some more and look for the 'catch'. :-)
robsoft
A: 

if it's inside the transaction, the database locking system will take care of concurrency issues. of course, if you use one (the mssql default is that it uses lock, so it states if you don't override that)

zappan
A: 

Edit: my bad, you wanted the select to show results after the update, not update from a select.

Have you tried a sub-select?

SQL> update mytable set mydate = sysdate where mydate in (select mydate from mytable where mydate is null);

+2  A: 

One way to handle this is to do it in a transaction, and make your SELECT query take an update lock on the rows selected until the transaction completes.

BEGIN TRAN

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

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

COMMIT TRAN

This eliminates the possibility that a concurrent client updates the rows selected in the moment between your SELECT and your UPDATE.

When you commit the transaction, the update locks will be released.

Another way to handle this is to declare a cursor for your SELECT with the FOR UPDATE option. Then UPDATE WHERE CURRENT OF CURSOR. The following is not tested, but should give you the basic idea:

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 AlertDate = @UpdateTime
  WHERE CURRENT OF cur1;

  FETCH NEXT FROM cur1;

END
Bill Karwin
+1 for UPDLOCK, its a correct solution to this problem and in short transactions will not lead to deadlocks
Sam Saffron
Will this result in all rows getting the same datetime value (as you would in a single SQL call)? If not, you should get the time before the loop into a variable and just set AltertDate to the variable. If it's an issue, please edit this in.
IronGoofy
+2  A: 

Perhaps something more like this?

declare @UpdateTime datetime

set @UpdateTime = getutcdate()

update Table1 set AlertDate = @UpdateTime where AlertDate is null

select ID from Table1 where AlertDate = @UpdateTime
Gordon Bell
This has the nice advantage that, even if you don't do this within a transaction, it will be *very* difficult that you'd get results some other process altered.
Joe Pineda
+1  A: 

in SQL 2008 a new TSQL statement "MERGE" is introduced which performs insert, update, or delete operations on a target table based on the results of a join with a source table. You can synchronize two tables by inserting, updating, or deleting rows in one table based on differences found in the other table.

http://blogs.msdn.com/ajaiman/archive/2008/06/25/tsql-merge-statement-sql-2008.aspx http://msdn.microsoft.com/en-us/library/bb510625.aspx