下面這篇博客很詳細
PostgreSQL 動態表復制(CREATE TABLE AS&CREATE TABLE LIKE)
轉自:http://www.linuxidc.com/Linux/2016-09/135540.htm
前言
項目中有表復制的需求,而且是動態復制,即在存儲過程里根據參數數組的值循環復制n張結構(約束、索引等)等一致的一組表,PostgreSQL提供了兩種語法來進行表復制,分別是:
- CREATE TABLE AS
- CREATE TABLE LIKE
下面就通過一個例子來看看究竟哪一種更好或者說更符合我們的需求。
CREATE TABLE AS
首先看看CREATE TABLE AS的用法,在這之前結合一個具體的例子看看,我們需要復制的是這樣一張表:
如上圖所示,在PowerDesigner的物理模型(pdm)中我們可以看到這張表定義了主鍵和一個外鍵,再看看它的ddl語句:
drop table t_key_event_file_student; /*==============================================================*/ /* Table: t_key_event_file_student */ /*==============================================================*/ create table t_key_event_file_student ( id SERIAL not null, key_event_score_student_id INT4 not null, file_name varchar(100) not null, file_path varchar(100) not null, constraint PK_T_KEY_EVENT_FILE_STUDENT primary key (id) ); comment on table t_key_event_file_student is '關鍵事件業務表(附件)'; comment on column t_key_event_file_student.id is '主鍵'; comment on column t_key_event_file_student.key_event_score_student_id is '關鍵事件錄入ID'; comment on column t_key_event_file_student.file_name is '附件文件名稱'; comment on column t_key_event_file_student.file_path is '附件文件路徑'; alter table t_key_event_file_student add constraint FK_T_KEY_EV_REF16_T_KEY_EV foreign key (key_event_score_student_id) references t_key_event_score_student (id) on delete restrict on update restrict;
如上所示,首先理一下這張表都包含了什么東西,我們復制表的同時應帶上什么東西。
首先,id定義成了SERIAL類型,那就意味着建表的同時會為我們自動創建一個序列,那么這個序列在表復制的時候是肯定不能copy的,因為那樣的話將意味着原表和復制的表公用一個序列,明顯不合理。其次是約束,我們可以看到上面的DDL語句中出現了三種約束,分別是:主鍵(Primary Key)約束、外鍵(Foreign Key)約束以及非空(Not Null)約束,很顯然,表復制的同時這三種約束都應存在,中間的語句還有若干條comment(注釋),理論上注釋內容在表復制的同時也應該存在,所以簡單總結一下我們做表復制的取舍:
- 所有約束、索引和注釋在復制時都應被拷貝。
- 序列不應拷貝,應當為每一張復制的表單獨創建一個新的序列。
搞清楚這些問題后接下來看看PostgreSQL的相關支持能為我們實現什么,首先看一下CREATE TABLE AS,官方是這樣描述的:
如上圖所示,CREATE TABLE AS主要做兩件事情,分別是建表(CREATE DATA)和填充數據(FILL DATA),下面我們就通過CREATE TABLE AS復制一張表試試看。本篇blog的示例都會用t_key_event_file_student這張表,首先給這張表插入3條數據:
接下來運行CREATE TABLE AS來復制該表:
create table t_key_event_file_student_100 as select * from t_key_event_file_student;
創建成功后看看它的DDL語句:
再看一下這張表的數據:
如上圖,首先第一張圖可以看到拷貝后的表結構,那我們再回頭看看原始表的表結構好做對比:
如上圖,這樣一比較發現差距還挺大的,CREATE TABLE AS復制出來的表,所有約束、注釋和序列都沒有被拷貝,但數據成功拷貝了,就如同官方文檔中的描述,顯而易見,這與我們的預期相差甚遠,所以就不做過多考慮了,接下來看看第二種復制方式——CREATE TABLE LIKE。
CREATE TABLE LIKE
如題,LIKE不同於CREATE TABLE AS 語句,它是標准CREATE TABLE語句的一個參數項,在官方文檔中可以看到:
后面還有對like_options的參數值枚舉:
如上圖,用法很簡單,即INCLUDING后面6個值或者EXCLUDING后面6個值,例如:INCLUDING DEFAULTS INCLUDING CONSTRAINTS INCLUDING COMMENTS
,這就是一種配置方式。直觀起見我們依舊通過舉例說明,下面通過CREATE TABLE LIKE來完成復制:
create table t_key_event_file_student_101 (like t_key_event_file_student);
復制成功后再看一下表結構的DDL語句和數據:
如上圖,同CREATE TABLE AS不同的是這次復制成功拷貝了所有NOT-NULL約束,並且沒有拷貝表數據,這也漸漸接近了我們的需求,並且驗證了一點,就是CREATE TABLE LIKE並不會復制任何數據,而CREATE TABLE AS則會復制數據。回顧一下我們的需求:
- 所有約束、索引和注釋在復制時都應被拷貝。
- 序列不應拷貝,應當為每一張復制的表單獨創建一個新的序列。
接下來就要通過LIKE選項的INCLUDING關鍵字來實現了后續需求了,官方文檔中對於CREATE TABLE的like_options有幾小段詳細的解釋:
LIKE source_table [ like_option … ]
The LIKE clause specifies a table from which the new table automatically copies all column names, their data types, and their not-null constraints.
如上所示,當使用LIKE子句做表復制時,默認會自動拷貝所有字段、字段類型以及它們的NOT-NULL約束,這也就解釋了剛才為什么會成功復制NOT-NULL約束。
Default expressions for the copied column definitions will only be copied if INCLUDING DEFAULTS is specified. The default behavior is to exclude default expressions, resulting in the copied columns in the new table having null defaults.
如上所示,當指定了INCLUDING DEFAULTS時默認的列定義均會被拷貝,這么說的話由於原始表的主鍵是SERIAL類型,創建后id會綁定序列,那么序列是否也會被拷貝呢?測試一下:
create table t_key_event_file_student_102 (like t_key_event_file_student INCLUDING DEFAULTS);
接下來看一下DDL語句:
沒錯,與官方的說法一致,由於序列是指定在了列定義(column definitions )上,所以當使用了INCLUDING DEFAULTS時它自然會被復制,但這與我們的需求不符,因為我們的需求是每張被復制的表都應創建一個其專屬的唯一序列,所以結論就是不能用INCLUDING DEFAULTS,繼續往下看:
Not-null constraints are always copied to the new table. CHECK constraints will be copied only if INCLUDING CONSTRAINTS is specified. Indexes, PRIMARY KEY, and UNIQUE constraints on the original table will be created on the new table only if the INCLUDING INDEXES clause is specified. No distinction is made between column constraints and table constraints.
如上所示,NOT-NULL約束always copied to the new table,這一點在上面也提過了,它總會被復制。CHECK約束只有在指定了INCLUDING CONSTRAINTS時才會被拷貝,這很好理解,由於我們的原始表並沒有CHECK約束,所以暫不考慮。如果希望索引、主鍵約束和唯一約束被復制的話,那么需要指定INCLUDING INDEXES,顯然這是我們需要的,因為我們的原始表指定了主鍵約束,還有最后一段:
Comments for the copied columns, constraints, and indexes will only be copied if INCLUDING COMMENTS is specified. The default behavior is to exclude comments, resulting in the copied columns and constraints in the new table having no comments.
如果希望復制注釋,那么需要指定INCLUDING COMMENTS,很明顯,這也是我們需要的。至此我們已經可以篩選出我們需要的東西了,下面通過標���看一下:
including constraints:沒有CHECK約束,所以不考慮- including indexes :需要主鍵約束
- including comments:需要注釋
including defaults:不需要復制序列,所以不要
結論是我們的LIKE選項為:INCLUDING INDEXES INCLUDING COMMENTS,所以這次就能復制一個“最貼近我們需求”的表了:
create table t_key_event_file_student_103 (like t_key_event_file_student INCLUDING INDEXES INCLUDING COMMENTS);
依舊看一下DDL語句:
如上圖,可以看到這次復制的有NOT-NULL約束、主鍵約束以及注釋,這樣就完成了我們的表復制,可剛才為什么說創建的是最貼近我們需求的表呢?因為到此為止對比需求發現我們可能還少了點東西,原始表中有外鍵約束,那么該如何復制呢?答案是無法復制,PostgreSQL官方並不提供外鍵約束的復制,所以只能自己通過alter語句去添加外鍵約束了,同樣序列也是,通過語句手動創建即可,最后就看一下通過PostgreSQL的自定義函數完成動態表復制的全過程。
自定義函數實現動態復制
如題,需求是傳入一個字符串數組,根據數組的大小(n)來動態復制n張表,接下來直接看一下完整的自定義函數代碼:
CREATE OR REPLACE FUNCTION "public"."f_inittables1"(arr _text) RETURNS "pg_catalog"."void" AS $BODY$ DECLARE scount INTEGER; rownum integer := 1; currsnum text; strSQL text; BEGIN scount:=array_length(arr,1); while rownum <= scount LOOP currsnum:=arr[rownum]; RAISE NOTICE '這里是%', currsnum; -- 開始復制 ----建表 strSQL := 'CREATE TABLE t_self_evaluation'||'_'||currsnum||' (like t_self_evaluation including constraints including indexes including comments);'; EXECUTE strSQL; ----添加外鍵約束 strSQL :='alter table t_self_evaluation'||'_'||currsnum||' add constraint FK_T_SELF_E_REF12_T_EVALUA_'||currsnum||' foreign key (scheme_id) references t_evaluation_scheme (id) on delete restrict on update restrict;'; EXECUTE strSQL; ----指定序列 strSQL :='create sequence t_self_evaluation_'||currsnum||'_id_seq increment by 1 minvalue 1 maxvalue 9223372036854775807 start with 1 owned by t_self_evaluation_'||currsnum||'.id'; EXECUTE strSQL; rownum := rownum + 1; end LOOP; END; $BODY$ LANGUAGE 'plpgsql' VOLATILE COST 100 ; ALTER FUNCTION "public"."f_inittables1"(arr _text) OWNER TO "postgres";
如上所示,遍歷參數數組,根據數組的值拼接構造表名,同時構造外鍵名和序列名,在循環的n次中通過EXECUTE關鍵字執行建表語句實現動態建表,下面調用一下試試,傳入一個5個字符串的數組:
select f_inittables1('{"021","270","271","070","150"}');
運行結束后可以看到控制台成功打印了RAISE NOTICE信息:
最后再看一下復制的表:
如上圖,可以看到已經完全滿足我們的需求了,至此我們的動態表復制就算全部結束了。
總結
簡單記錄一下PostgreSQL中實現動態表復制的全過程,希望對遇到同樣問題的朋友有所幫助,The End。