PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Postgres UPDATE avec ORDER BY, comment faire ?

Autant que je sache, il n'y a aucun moyen d'accomplir cela directement via le UPDATE déclaration; la seule façon de garantir l'ordre des verrous est d'acquérir explicitement des verrous avec un SELECT ... ORDER BY ID FOR UPDATE , par exemple :

UPDATE Balances
SET Balance = 0
WHERE ID IN (
  SELECT ID FROM Balances
  WHERE ID IN (SELECT ID FROM some_function())
  ORDER BY ID
  FOR UPDATE
)

Cela a l'inconvénient de répéter l'ID recherche d'index sur les Balances table. Dans votre exemple simple, vous pouvez éviter cette surcharge en récupérant l'adresse physique de la ligne (représentée par le ctid colonne système ) pendant la requête de verrouillage, et l'utiliser pour piloter la UPDATE :

UPDATE Balances
SET Balance = 0
WHERE ctid = ANY(ARRAY(
  SELECT ctid FROM Balances
  WHERE ID IN (SELECT ID FROM some_function())
  ORDER BY ID
  FOR UPDATE
))

(Soyez prudent lorsque vous utilisez ctid s, car les valeurs sont transitoires. Nous sommes en sécurité ici, car les verrous bloqueront tout changement.)

Malheureusement, le planificateur n'utilisera que le ctid dans un ensemble restreint de cas (vous pouvez savoir si cela fonctionne en recherchant un nœud "Tid Scan" dans le EXPLAIN production). Pour gérer des requêtes plus complexes dans une seule UPDATE déclaration, par ex. si votre nouveau solde était retourné par some_function() à côté de l'ID, vous devrez revenir à la recherche basée sur l'ID :

UPDATE Balances
SET Balance = Locks.NewBalance
FROM (
  SELECT Balances.ID, some_function.NewBalance
  FROM Balances
  JOIN some_function() ON some_function.ID = Balances.ID
  ORDER BY Balances.ID
  FOR UPDATE
) Locks
WHERE Balances.ID = Locks.ID

Si la surcharge de performances est un problème, vous devrez recourir à l'utilisation d'un curseur, qui ressemblera à ceci :

DO $$
DECLARE
  c CURSOR FOR
    SELECT Balances.ID, some_function.NewBalance
    FROM Balances
    JOIN some_function() ON some_function.ID = Balances.ID
    ORDER BY Balances.ID
    FOR UPDATE;
BEGIN
  FOR row IN c LOOP
    UPDATE Balances
    SET Balance = row.NewBalance
    WHERE CURRENT OF c;
  END LOOP;
END
$$