sql >> データベース >  >> RDS >> PostgreSQL

ORDERBYを使用したPostgresUPDATE、その方法は?

    私の知る限り、UPDATEを介してこれを直接達成する方法はありません。 声明;ロックの順序を保証する唯一の方法は、SELECT ... ORDER BY ID FOR UPDATEを使用してロックを明示的に取得することです。 例:

    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
    )
    

    これには、IDを繰り返すという欠点があります。 Balancesでのインデックスルックアップ テーブル。簡単な例では、物理行アドレス( ctid システム列 )ロッククエリ中に、それを使用して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
    ))
    

    ctidを使用するときは注意してください s、値は一時的なものであるため。ロックによって変更がブロックされるため、ここでは安全です。)

    残念ながら、プランナーはctidのみを使用します 限られたケースの場合(EXPLAINで「TidScan」ノードを探すことで機能しているかどうかを確認できます 出力)。単一のUPDATE内でより複雑なクエリを処理するため ステートメント、例:新しい残高がsome_function()によって返されていた場合 IDと一緒に、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
    

    パフォーマンスのオーバーヘッドが問題になる場合は、カーソルを使用する必要があります。カーソルは次のようになります。

    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
    $$
    


    1. Oracleの日付にAD/BCインジケーターを追加する方法

    2. 選択クエリのMySQLIFELSEIF

    3. MySQL-大きなデータベースからいくつかの特定のテーブルを削除する

    4. PHP+HTMLを使用してハイパーリンクでデータベース値を渡す方法