関連する質問があります ですが、これは私のパズルの別の部分です。
更新された行から列の古い値を取得したい-トリガーを使用せずに(ストアドプロシージャ、またはその他の追加の非SQL /クエリエンティティ)。
私が持っているクエリは次のようなものです:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
WHERE trans_nbr IN (
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(trans_nbr) > 1
LIMIT our_limit_to_have_single_process_grab
)
RETURNING row_id;
サブクエリの最後で「FOR UPDATE ON my_table」を実行できた場合、それはすばらしいことです(そして、他の質問/問題を修正します)。しかし、これは機能しません。これと「GROUP BY」を含めることはできません(trans_nbrのCOUNTを把握するために必要です)。次に、これらのtrans_nbrを取得して、最初にクエリを実行し、(間もなく)以前のprocessing_by値を取得します。
私は次のようにしてみました:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
FROM my_table old_my_table
JOIN (
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(trans_nbr) > 1
LIMIT our_limit_to_have_single_process_grab
) sub_my_table
ON old_my_table.trans_nbr = sub_my_table.trans_nbr
WHERE my_table.trans_nbr = sub_my_table.trans_nbr
AND my_table.processing_by = old_my_table.processing_by
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by
しかし、それはうまくいきません。 old_my_table
は結合の外には表示されません。 RETURNING
句はそれを知らない。
私はこれまでに行ったすべての試みの数を失っています。私は文字通り何時間もこれを研究してきました。
私のサブクエリの行をロックするための防弾方法を見つけることができた場合-そしてそれらの行のみで、サブクエリが発生した場合-回避しようとしている同時実行の問題はすべて消えます...
UPDATE:[WIPES Egg OFF FACE]さて、上記の非ジェネリックコードに「誤動作」と書いたタイプミスがありました。 ";それはそうです...Erwin Brandstetterのおかげで、それを述べたので、私はそれをやり直しました(夜の睡眠後、目をリフレッシュし、 bfastのバナナ)。この種の解決策を見つけるのにmeとても長く/難しいので、おそらく私の恥ずかしさはそれだけの価値がありますか?少なくともこれはSO後世のために今...です...:>
私が今持っているもの(それは機能します)は次のとおりです:
UPDATE my_table
SET processing_by = our_id_info -- unique to this worker
FROM my_table AS old_my_table
WHERE trans_nbr IN (
SELECT trans_nbr
FROM my_table
GROUP BY trans_nbr
HAVING COUNT(*) > 1
LIMIT our_limit_to_have_single_process_grab
)
AND my_table.row_id = old_my_table.row_id
RETURNING my_table.row_id, my_table.processing_by, old_my_table.processing_by AS old_processing_by
COUNT(*)は、コメントのFlimzyからの提案に基づいています私の他の(上にリンクされた)質問に。 (私は必要以上に具体的だった。[この場合]。)
私の別の質問を参照してください 同時実行性を正しく実装するため、さらには非ブロッキングバージョンについても。このクエリは、更新から古い値と新しい値を取得する方法を示すだけであり、不良/間違った同時実行ビットは無視します。
オプションの
RETURNING
句を使用すると、UPDATE
は実際に更新された各行に基づいて値を計算して返します。テーブルの列、またはFROM
で言及されている他のテーブルの列、あるいはその両方を使用する式はすべて計算できます。テーブルの列の新しい(更新後)値が使用されます。RETURNING
リストの構文は、SELECT
の出力リストの構文と同じです。
鉱山を強調します。 RETURNING
句の古い行にアクセスする方法はありません。これは、トリガーで、またはSELECT
の前に別のUPDATE
beforeを使用して行うことができます。
ただし、FROM
句でテーブルの別のインスタンスに参加すると、達成しようとしていることは完全に正常に機能します。
UPDATE tbl x
SET tbl_id = 23
, name = 'New Guy'
FROM tbl y -- using the FROM clause
WHERE x.tbl_id = y.tbl_id -- must be UNIQUE NOT NULL
AND x.tbl_id = 3
RETURNING y.tbl_id AS old_id, y.name AS old_name
, x.tbl_id , x.name;
戻り値:
old_id | old_name | tbl_id | name
--------+----------+--------+---------
3 | Old Guy | 23 | New Guy
私はこれをPostgreSQLのバージョン8.4から9.6でテストしました。
INSERT
の場合は異なります。
同時書き込み操作で競合状態を回避する方法はいくつかあります。シンプルで遅くて確実な(しかし高価な)メソッドは、SERIALIZABLE
分離レベルでトランザクションを実行することです。
BEGIN ISOLATION LEVEL SERIALIZABLE;
UPDATE ..;
COMMIT;
しかし、それはおそらくやり過ぎです。また、シリアル化の失敗が発生した場合は、操作を繰り返す準備をする必要があります。
よりシンプルで高速(そして同時書き込み負荷と同じくらい信頼できる)は、更新されるone行の明示的なロックです。
UPDATE tbl x
SET tbl_id = 24
, name = 'New Gal'
FROM (SELECT tbl_id, name FROM tbl WHERE tbl_id = 4 FOR UPDATE) y
WHERE x.tbl_id = y.tbl_id
RETURNING y.tbl_id AS old_id, y.name AS old_name, x.tbl_id, x.name;
この関連質問の下の詳細な説明、例、リンク:
SELECT
サブクエリを使用できます。
例: pdate ユーザーのメールRETURNING
古い値。
RETURNING
サブクエリ
UPDATE users SET email = '[email protected]' WHERE id = 1
RETURNING (SELECT email FROM users WHERE id = 1);
WITH u AS (
SELECT email FROM users WHERE id = 1
)
UPDATE users SET email = '[email protected]' WHERE id = 1
RETURNING (SELECT email FROM u);
これは間違いなく私のローカルデータベースで数回動作しましたが、SELECT
のWITH
がUPDATE
の前に一貫して実行されることが保証されているかどうかはわかりません。 -WITHのステートメントは、互いに、およびメインクエリと同時に実行されます。
@ MattDiPasqualeによって提案 としてのCTEバリアントも機能するはずです。
CTEの快適な手段があれば、より明確になりますが、
WITH sel AS (
SELECT tbl_id, name FROM tbl WHERE tbl_id = 3 -- assuming unique tbl_id
)
, upd AS (
UPDATE tbl SET name = 'New Guy' WHERE tbl_id = 3
RETURNING tbl_id, name
)
SELECT s.tbl_id AS old_id, s.name As old_name
, u.tbl_id, u.name
FROM sel s, upd u;
テストなしで私はこれがうまくいくと主張します:SELECT
とUPDATE
はデータベースの同じスナップショットを参照してください。 SELECT
は古い値を返すようにバインドされています(たとえUPDATE
を使用してCTEの後にCTEを配置した場合でも)。一方、UPDATE
は定義により新しい値を返します。ボイラ。
しかし、それは私の最初の答えよりも遅くなります。
このジレンマに直面したとき、テーブルにジャンク列を追加し、レコードを更新するときに古い値をジャンク列(次に返す)にコピーします。これはテーブルを少し膨らませますが、結合の必要性を回避します。