PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Postgres UPDATE com ORDER BY, como fazer?


Até onde eu sei, não há como fazer isso diretamente através do UPDATE declaração; a única maneira de garantir a ordem de bloqueio é adquirir bloqueios explicitamente com um SELECT ... ORDER BY ID FOR UPDATE , por exemplo.:
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
)

Isso tem a desvantagem de repetir o ID pesquisa de índice em Balances tabela. Em seu exemplo simples, você pode evitar essa sobrecarga buscando o endereço da linha física (representado pelo ctid coluna do sistema ) durante a consulta de bloqueio e usando isso para conduzir o 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
))

(Tenha cuidado ao usar ctid s, pois os valores são transitórios. Estamos seguros aqui, pois os bloqueios bloquearão qualquer alteração.)

Infelizmente, o planejador utilizará apenas o ctid em um conjunto restrito de casos (você pode saber se está funcionando procurando por um nó "Tid Scan" no EXPLAIN resultado). Para lidar com consultas mais complicadas em um único UPDATE declaração, por exemplo se seu novo saldo estava sendo retornado por some_function() junto com o ID, você precisará retornar à pesquisa baseada em 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

Se a sobrecarga de desempenho for um problema, você precisará recorrer ao uso de um cursor, que seria algo assim:
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
$$