PostgreSQL中主鍵的重複行
假設我有一個名為 的表
people
,其中id
是主鍵:+-----------+---------+---------+ | id | fname | lname | | (integer) | (text) | (text) | +===========+=========+=========+ | 1 | Daniel | Edwards | | 2 | Fred | Holt | | 3 | Henry | Smith | +-----------+---------+---------+
我正在嘗試編寫一個足夠健壯的行重複查詢,以考慮對錶的架構更改。每當我向表中添加一列時,我都不想返回並修改重複查詢。
我知道我可以做到這一點,這將重複記錄 id 2 並為重複的記錄提供一個新的 id:
INSERT INTO people (fname, lname) SELECT fname, lname FROM people WHERE id = 2;
但是,如果我添加一
age
列,我需要修改查詢以考慮年齡列。顯然我不能執行以下操作,因為它還會複製主鍵,從而導致
duplicate key value violates unique constraint
– 而且,我不希望它們共享相同的 id:INSERT INTO people SELECT * FROM people WHERE id = 2
話雖如此,解決這一挑戰的合理方法是什麼?我寧願遠離儲存過程,但我不是 100% 反對它們,我想……
簡單的
hstore
如果您安裝了附加模組
hstore
(下面連結中的說明),則有一種非常簡單的方法可以在不了解其他列的情況下替換單個欄位的值:基本範例:使用複制行
id = 2
但替換2
為3
:INSERT INTO people SELECT (p #= hstore('id', '3')).* FROM people p WHERE id = 2;
細節:
假設(因為它沒有在問題中定義)這
people.id
是一個**serial
**帶有附加序列的列,您將需要序列中的下一個值。我們可以用 來確定序列名稱pg_get_serial_sequence()
。細節:或者,如果序列名稱不會改變,您可以硬編碼序列名稱。
我們會有這樣的查詢:
INSERT INTO people SELECT (p #= hstore('id', nextval(pg_get_serial_sequence('people', 'id'))::text)).* FROM people p WHERE id = 2;
哪個有效,但 Postgres 查詢計劃器存在一個弱點:表達式是針對行中的每一列單獨評估的,浪費了序列號和性能。為避免這種情況,請將表達式移動到子查詢中並僅將行分解一次:
INSERT INTO people SELECT (p1).* FROM ( SELECT p #= hstore('id', nextval(pg_get_serial_sequence('people', 'id'))::text) AS p1 FROM people p WHERE id = 2 ) sub;
一次(或幾行)可能最快。
json/jsonb
如果您沒有
hstore
安裝並且無法安裝其他模組,您可以使用json_populate_record()
or執行類似的技巧jsonb_populate_record()
,但該功能未記錄並且可能不可靠。**更新:**自 Postgres 13 起也記錄了該功能。請參閱:臨時臨時表
另一個簡單的解決方案是使用像這樣的臨時臨時:
BEGIN; CREATE TEMP TABLE people_tmp ON COMMIT DROP AS SELECT * FROM people WHERE id = 2; UPDATE people_tmp SET id = nextval(pg_get_serial_sequence('people', 'id')); INSERT INTO people TABLE people_tmp; COMMIT;
我添加
ON COMMIT DROP
了在事務結束時自動刪除表。因此,我還將操作包裝到它自己的事務中。兩者都不是絕對必要的。這提供了廣泛的附加選項 - 您可以在插入之前對行執行任何操作,但由於創建和刪除臨時表的成本,它會有點慢。
此解決方案同時適用於單行或任意數量的行。每行自動從序列中獲取一個新的預設值。
動態 SQL
一次處理多行,動態 SQL 將是最快的。連接系統表
pg_attribute
或資訊模式中的列,並在DO
語句中動態執行或編寫函式以供重複使用:CREATE OR REPLACE FUNCTION f_row_copy(_tbl regclass, _id int, OUT row_ct int) LANGUAGE plpgsql AS $func$ BEGIN EXECUTE ( SELECT format('INSERT INTO %1$s(%2$s) SELECT %2$s FROM %1$s WHERE id = $1', _tbl, string_agg(quote_ident(attname), ', ')) FROM pg_attribute WHERE attrelid = _tbl AND NOT attisdropped -- no dropped (dead) columns AND attnum > 0 -- no system columns AND attname <> 'id' -- exclude id column ) USING _id; GET DIAGNOSTICS row_ct = ROW_COUNT; -- directly assign OUT parameter END $func$;
稱呼:
SELECT f_row_copy('people', 9);
適用於任何具有名為 的整數列的表
id
。您也可以輕鬆地使列名動態化…也許不是您的首選,因為您想這樣做
stay away from stored procedures
,但話又說回來,無論如何,它不是“儲存過程” …有關的:
先進的解決方案
列是一種
serial
特殊情況。如果您想用各自的預設值填充更多或所有列,它會變得更加複雜。考慮這個相關的答案: