web-dev-qa-db-fra.com

Existe-t-il un moyen de sélectionner et de mettre à jour des lignes en même temps?

Je voudrais mettre à jour un ensemble de lignes sur la base d'un critère simple et obtenir la liste des PK qui ont été modifiés. Je pensais que je pouvais simplement faire quelque chose comme ça, mais je m'inquiète des éventuels problèmes de concurrence:

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

Si cela est inclus dans une transaction, des problèmes de concurrence peuvent-ils survenir? Ou existe-t-il une meilleure façon de procéder?

41
DavGarcia

Pensez à regarder la clause OUTPUT :

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 
72
Mark Canlas

Une façon de gérer cela est de le faire dans une transaction et de faire en sorte que votre requête SELECT prenne un verrou de mise à jour sur les lignes sélectionnées jusqu'à ce que la transaction se termine.

BEGIN TRAN

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

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

COMMIT TRAN 

Cela élimine la possibilité qu'un client simultané mette à jour les lignes sélectionnées entre votre SELECT et votre UPDATE.

Lorsque vous validez la transaction, les verrous de mise à jour seront libérés.

Une autre façon de gérer cela est de déclarer un curseur pour votre SELECT avec l'option FOR UPDATE. Ensuite, METTEZ À JOUR O C COURANT LE CURSEUR. Ce qui suit n'est pas testé, mais devrait vous donner l'idée de base:

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
13
Bill Karwin

Plusieurs années plus tard...

La réponse acceptée de l'utilisation de la clause OUTPUT est bonne. J'ai dû creuser la syntaxe réelle, alors voici:

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

AJOUTÉ SEP 14, 2015:

"Puis-je utiliser une variable scalaire au lieu d'une variable de table?" on peut demander ... Désolé, mais non, vous ne pouvez pas. Tu devras SELECT @SomeID = ID from @UpdatedIDs si vous avez besoin d'un seul identifiant.

11
K. R.

Il serait plus facile de faire votre mise à jour en premier, puis d'exécuter "SELECT ID FROM INSERTED".

Jetez un œil à SQL Tips pour plus d'informations et d'exemples.

9
Kevin Fairchild

Peut-être quelque chose de plus comme ça?

declare @UpdateTime datetime

set @UpdateTime = getutcdate()

update Table1 set AlertDate = @UpdateTime where AlertDate is null

select ID from Table1 where AlertDate = @UpdateTime
4
Gordon Bell

J'ai fait face au même problème; Je dois mettre à jour le montant du crédit et obtenir une modification de l'heure, ainsi que les détails du crédit de DB. C'est fondamentalement

Effectuer de manière synchrone/atomique (MISE À JOUR puis OBTENIR) dans MYSQL

J'ai essayé de nombreuses options et en ai trouvé une qui a résolu mon problème.

1) OPTION_1 CHOISIR POUR LA MISE À JOUR

Cela maintient le verrouillage jusqu'à la mise à jour (SYNC de GET à UPDATE), mais j'ai besoin d'un verrouillage après la mise à jour jusqu'à GET.

2) OPTION_2 Procédure stockée

La procédure stockée ne s'exécutera pas de manière synchrone comme redis lua, nous avons donc également besoin d'un code de synchronisation pour effectuer cela.

3) OPTION_3 Transaction

J'ai utilisé JPA entityManager comme ci-dessous, pensant qu'avant la validation, personne ne peut mettre à jour, et avant la validation, j'obtiendrai l'objet mis à jour avec l'heure modifiée (à partir de la base de données). Mais je n'ai pas eu le dernier objet. N'engagez que j'ai la dernière.

    try {
        entityManager.getTransaction().begin();
        //entityManager.persist(object);
        int upsert = entityManager.createNativeQuery(
        "update com.bill.Credit c set c.balance = c.balance - ?1
          where c.accountId = ?2 and c.balance >= ?1").executeUpdate(); 
             //c.balance >= ? for limit check
        Credit newCredit = entityManager.find(Credit.class, "id");
        entityManager.refresh(newCredit); //SHOULD GET LATEST BUT NOT
        entityManager.getTransaction().commit();
    } finally {     
        entityManager.unwrap(Session.class).close();
    } 

4) OPTION_4 LOCK a résolu le problème, donc avant la mise à jour, j'ai acquis le verrou; puis après GET j'ai libéré le verrou.

private Object getLock(final EntityManager entityManager, final String Id){

    entityManager.getTransaction().begin();
    Object obj_acquire = entityManager.createNativeQuery("SELECT GET_LOCK('" + Id + "', 10)").getSingleResult();
    entityManager.getTransaction().commit();
    return obj_acquire;
}


private Object releaseLock(final EntityManager entityManager, final String Id){

    entityManager.getTransaction().begin();
    Object obj_release = entityManager.createNativeQuery("SELECT RELEASE_LOCK('" + Id + "')").getSingleResult();
    entityManager.getTransaction().commit();
    return obj_release;
}
1
Kanagavelu Sugumar

s'il se trouve à l'intérieur de la transaction, le système de verrouillage de la base de données s'occupera des problèmes de concurrence. bien sûr, si vous en utilisez un (la valeur par défaut de mssql est qu'il utilise un verrou, il indique donc si vous ne remplacez pas cela)

0
zappan

dans SQL 2008, une nouvelle instruction TSQL "MERGE" est introduite, qui effectue des opérations d'insertion, de mise à jour ou de suppression sur une table cible en fonction des résultats d'une jointure avec une table source. Vous pouvez synchroniser deux tables en insérant, en mettant à jour ou en supprimant des lignes dans une table en fonction des différences trouvées dans l'autre table.

http://blogs.msdn.com/ajaiman/archive/2008/06/25/tsql-merge-statement-sql-2008.aspxhttp://msdn.Microsoft. com/fr-fr/library/bb510625.aspx

0
ashishjaiman

Edit: mon mauvais, vous vouliez que la sélection affiche les résultats après la mise à jour, pas la mise à jour à partir d'une sélection.

Avez-vous essayé une sous-sélection?

update mytable set mydate = sysdate 
where mydate in (select mydate from mytable where mydate is null);
0
telesphore4