UPDATE — 更新資料表的列
[ WITH [ RECURSIVE ]with_query
[, ...] ] UPDATE [ ONLY ]table_name
[ * ] [ [ AS ]alias
] SET {column_name
= {expression
| DEFAULT } | (column_name
[, ...] ) = [ ROW ] ( {expression
| DEFAULT } [, ...] ) | (column_name
[, ...] ) = (sub-SELECT
) } [, ...] [ FROMfrom_item
[, ...] ] [ WHEREcondition
| WHERE CURRENT OFcursor_name
] [ RETURNING { * |output_expression
[ [ AS ]output_name
] } [, ...] ]
UPDATE
變更符合條件的所有列中指定欄位的值。 只需要在 SET
子句中提及要修改的欄位; 未明確修改的欄位將保留其先前的值。
有兩種方法可以使用資料庫中其他資料表包含的資訊來修改資料表: 使用子查詢,或在 FROM
子句中指定其他資料表。 哪種技術更合適取決於具體情況。
可選的 RETURNING
子句會使 UPDATE
計算並傳回基於每個實際更新的列的值。 可以計算任何使用資料表欄位和/或 FROM
中提及的其他資料表欄位的表達式。 使用資料表欄位的新(更新後)值。 RETURNING
清單的語法與 SELECT
的輸出清單的語法相同。
您必須具有資料表的 UPDATE
權限,或至少具有要更新的欄位的權限。 您還必須具有在 expressions
或 condition
中讀取其值的任何欄位的 SELECT
權限。
with_query
WITH
子句允許您指定一個或多個子查詢,這些子查詢可以在 UPDATE
查詢中按名稱引用。 有關詳細信息,請參閱第 7.8 節和SELECT。
table_name
要更新的資料表的名稱(可選擇使用結構描述限定)。 如果在資料表名稱之前指定 ONLY
,則僅在指定的資料表中更新符合條件的列。 如果未指定 ONLY
,則也會在從指定的資料表繼承的任何資料表中更新符合條件的列。 (可選)可以在資料表名稱後指定 *
以明確表示包含子代資料表。
alias
目標資料表的替代名稱。 提供別名時,它會完全隱藏資料表的實際名稱。 例如,給定 UPDATE foo AS f
,UPDATE
語句的其餘部分必須將此資料表稱為 f
,而不是 foo
。
column_name
由 table_name
命名的資料表中的欄位名稱。 如果需要,欄位名稱可以用子欄位名稱或陣列下標限定。 請勿在目標欄位的規格中包含資料表的名稱 — 例如,UPDATE table_name SET table_name.col = 1
無效。
expression
要賦予欄位的表達式。 該表達式可以使用資料表中此欄位和其他欄位的舊值。
DEFAULT
將欄位設定為其預設值(如果未為其分配任何特定的預設表達式,則為 NULL)。 識別欄位將被設定為由關聯的序列產生的新值。 對於產生的欄位,允許指定此項,但僅指定從其產生表達式計算欄位的正常行為。
sub-SELECT
SELECT
子查詢,它產生的輸出欄位數與其前面帶括號的欄位清單中列出的欄位數一樣多。 執行時,子查詢不得產生超過一列。 如果它產生一列,則其欄位值將分配給目標欄位; 如果它不產生任何列,則 NULL 值將分配給目標欄位。 子查詢可以引用正在更新的資料表當前列的舊值。
from_item
一個資料表表達式,允許其他資料表的欄位出現在 WHERE
條件和更新表達式中。 這使用與 FROM
語句的 SELECT
子句相同的語法; 例如,可以指定資料表名稱的別名。 除非您打算進行自連接(在這種情況下,它必須以別名出現在 from_item
中),否則不要重複將目標資料表作為 from_item
。
condition
一個傳回 boolean
類型值的表達式。 只有此表達式傳回 true
的列才會被更新。
cursor_name
要在 WHERE CURRENT OF
條件中使用的游標名稱。要更新的列是最近從此游標提取的列。游標必須是對 UPDATE
的目標表格進行的非分組查詢。請注意,WHERE CURRENT OF
不能與布林條件一起指定。有關使用游標與 WHERE CURRENT OF
的更多資訊,請參閱 DECLARE。
output_expression
一個表達式,將在每次更新列後由 UPDATE
命令計算並返回。該表達式可以使用由 table_name
命名的表格或在 FROM
中列出的表格的任何欄位名稱。寫入 *
以返回所有欄位。
output_name
用於返回欄位的名稱。
成功完成時,UPDATE
命令會返回一個如下格式的命令標籤
UPDATE count
count
是更新的列數,包括已匹配但值未更改的列。請注意,當更新被 BEFORE UPDATE
觸發器抑制時,此數字可能小於符合 condition
的列數。如果 count
為 0,則表示查詢未更新任何列(這不被視為錯誤)。
如果 UPDATE
命令包含 RETURNING
子句,則結果將類似於包含 RETURNING
列表中定義的欄位和值的 SELECT
語句,這些欄位和值是根據命令更新的列計算得出的。
當存在 FROM
子句時,本質上發生的情況是目標表格與 from_item
列表中提到的表格進行聯接,並且聯接的每個輸出列代表目標表格的更新操作。使用 FROM
時,應確保聯接最多為每個要修改的列產生一個輸出列。換句話說,目標列不應與來自其他表格的多個列聯接。如果這樣做,則只會使用其中一個聯接列來更新目標列,但是將使用哪個聯接列並不容易預測。
由於這種不確定性,僅在子查詢中引用其他表格更安全,儘管通常比使用聯接更難讀懂且速度更慢。
在分割表格的情況下,更新列可能會導致它不再滿足包含分割區的分割約束。在這種情況下,如果分割樹中存在另一個分割區,此列滿足其分割約束,則該列將被移動到該分割區。如果沒有這樣的分割區,則會發生錯誤。在幕後,列移動實際上是 DELETE
和 INSERT
操作。
正在移動的列上同時進行 UPDATE
或 DELETE
操作可能會導致序列化失敗錯誤。假設會話 1 正在分割鍵上執行 UPDATE
,同時,會話 2 對於此列是可見的,它對該列執行 UPDATE
或 DELETE
操作。在這種情況下,會話 2 的 UPDATE
或 DELETE
將檢測到列移動並引發序列化失敗錯誤(總是返回 SQLSTATE 代碼 '40001')。如果發生這種情況,應用程式可能希望重試事務。在表格未分割或沒有列移動的通常情況下,會話 2 將識別新更新的列,並在此新列版本上執行 UPDATE
/DELETE
。
請注意,雖然列可以從本機分割區移動到外部表格分割區(前提是外部資料包裝器支援元組路由),但它們不能從外部表格分割區移動到另一個分割區。
如果發現外鍵直接引用源分割區的祖先,但該祖先與 UPDATE
查詢中提到的祖先不同,則將列從一個分割區移動到另一個分割區的嘗試將會失敗。
將表格 films
的 kind
欄位中的單字 Drama
更改為 Dramatic
UPDATE films SET kind = 'Dramatic' WHERE kind = 'Drama';
調整 weather
表格中一行中的溫度輸入,並將降水量重設為其預設值
UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT WHERE city = 'San Francisco' AND date = '2003-07-03';
執行相同的操作並返回更新的條目
UPDATE weather SET temp_lo = temp_lo+1, temp_hi = temp_lo+15, prcp = DEFAULT WHERE city = 'San Francisco' AND date = '2003-07-03' RETURNING temp_lo, temp_hi, prcp;
使用替代的欄位列表語法來執行相同的更新
UPDATE weather SET (temp_lo, temp_hi, prcp) = (temp_lo+1, temp_lo+15, DEFAULT) WHERE city = 'San Francisco' AND date = '2003-07-03';
使用 FROM
子句語法,增加管理 Acme Corporation 帳戶的銷售人員的銷售計數
UPDATE employees SET sales_count = sales_count + 1 FROM accounts WHERE accounts.name = 'Acme Corporation' AND employees.id = accounts.sales_person;
使用 WHERE
子句中的子查詢執行相同的操作
UPDATE employees SET sales_count = sales_count + 1 WHERE id = (SELECT sales_person FROM accounts WHERE name = 'Acme Corporation');
更新帳戶表格中的聯絡人姓名,以符合目前指定的銷售人員
UPDATE accounts SET (contact_first_name, contact_last_name) = (SELECT first_name, last_name FROM employees WHERE employees.id = accounts.sales_person);
類似的結果可以使用聯接來完成
UPDATE accounts SET contact_first_name = first_name, contact_last_name = last_name FROM employees WHERE employees.id = accounts.sales_person;
但是,如果 employees
.id
不是唯一鍵,則第二個查詢可能會產生意外的結果,而如果有多個 id
匹配,則第一個查詢保證會引發錯誤。此外,如果特定 accounts
.sales_person
條目沒有匹配項,則第一個查詢會將相應的姓名欄位設定為 NULL,而第二個查詢根本不會更新該列。
更新摘要表格中的統計資料,以符合目前的資料
UPDATE summary s SET (sum_x, sum_y, avg_x, avg_y) = (SELECT sum(x), sum(y), avg(x), avg(y) FROM data d WHERE d.group_id = s.group_id);
嘗試插入新的庫存項目以及庫存數量。如果該項目已存在,則改為更新現有項目的庫存數量。若要執行此操作而不導致整個事務失敗,請使用儲存點
BEGIN; -- other operations SAVEPOINT sp1; INSERT INTO wines VALUES('Chateau Lafite 2003', '24'); -- Assume the above fails because of a unique key violation, -- so now we issue these commands: ROLLBACK TO sp1; UPDATE wines SET stock = stock + 24 WHERE winename = 'Chateau Lafite 2003'; -- continue with other operations, and eventually COMMIT;
變更游標 c_films
目前所指行的 films
表格的 kind
欄位
UPDATE films SET kind = 'Dramatic' WHERE CURRENT OF c_films;
影響多個列的更新可能會對系統效能產生負面影響,例如表格膨脹、複寫延遲增加和鎖定爭用增加。在這種情況下,以較小的批次執行操作可能是有意義的,可能在批次之間對表格執行 VACUUM
操作。雖然 UPDATE
沒有 LIMIT
子句,但可以透過使用 Common Table Expression 和自聯接來獲得類似的效果。使用標準 PostgreSQL 表格存取方法,對系統欄位 ctid 進行自聯接非常有效率
WITH exceeded_max_retries AS ( SELECT w.ctid FROM work_item AS w WHERE w.status = 'active' AND w.num_retries > 10 ORDER BY w.retry_timestamp FOR UPDATE LIMIT 5000 ) UPDATE work_item SET status = 'failed' FROM exceeded_max_retries AS emr WHERE work_item.ctid = emr.ctid;
此指令需要重複執行,直到沒有需要更新的列為止。使用 ORDER BY
子句可以讓指令優先更新特定的列;如果其他的更新操作使用相同的排序,它也可以避免死鎖。如果擔心鎖定競爭,可以將 SKIP LOCKED
加入到CTE中,以防止多個指令更新同一列。然而,那樣的話,最終需要一個沒有 SKIP LOCKED
或 LIMIT
的 UPDATE
指令,以確保沒有遺漏任何符合條件的列。
此指令符合SQL標準,除了 FROM
和 RETURNING
子句是 PostgreSQL 擴充功能,以及使用 WITH
和 UPDATE
的能力也是。
某些其他的資料庫系統提供了 FROM
選項,在該選項中,目標表格應該再次列在 FROM
裡面。這不是 PostgreSQL 解釋 FROM
的方式。移植使用此擴充功能的應用程式時要小心。
根據標準,目標欄位名稱的括號子列表的來源值可以是任何產生正確欄位數量的列值表達式。PostgreSQL 只允許來源值為列建構子或子 SELECT
。在列建構子的情況下,單個欄位的更新值可以指定為 DEFAULT
,但在子 SELECT
內部則不行。
如果您在文件中發現任何不正確、與您特定功能的經驗不符或需要進一步澄清的地方,請使用此表格報告文件問題。