CREATE TYPE — 定義一個新資料型別
CREATE TYPEname
AS ( [attribute_name
data_type
[ COLLATEcollation
] [, ... ] ] ) CREATE TYPEname
AS ENUM ( [ 'label
' [, ... ] ] ) CREATE TYPEname
AS RANGE ( SUBTYPE =subtype
[ , SUBTYPE_OPCLASS =subtype_operator_class
] [ , COLLATION =collation
] [ , CANONICAL =canonical_function
] [ , SUBTYPE_DIFF =subtype_diff_function
] [ , MULTIRANGE_TYPE_NAME =multirange_type_name
] ) CREATE TYPEname
( INPUT =input_function
, OUTPUT =output_function
[ , RECEIVE =receive_function
] [ , SEND =send_function
] [ , TYPMOD_IN =type_modifier_input_function
] [ , TYPMOD_OUT =type_modifier_output_function
] [ , ANALYZE =analyze_function
] [ , SUBSCRIPT =subscript_function
] [ , INTERNALLENGTH = {internallength
| VARIABLE } ] [ , PASSEDBYVALUE ] [ , ALIGNMENT =alignment
] [ , STORAGE =storage
] [ , LIKE =like_type
] [ , CATEGORY =category
] [ , PREFERRED =preferred
] [ , DEFAULT =default
] [ , ELEMENT =element
] [ , DELIMITER =delimiter
] [ , COLLATABLE =collatable
] ) CREATE TYPEname
CREATE TYPE
將一個新資料型別註冊到當前資料庫中使用。定義該型別的使用者將成為其所有者。
如果給出了模式名稱,則型別將在指定的模式中建立。否則,它將在當前模式中建立。型別名稱在同一模式中必須與任何現有型別或域的名稱不同。(因為表有相關聯的資料型別,所以型別名稱在同一模式中也必須與任何現有表的名稱不同。)
如上面語法摘要所示,CREATE TYPE
有五種形式。它們分別建立複合型別、列舉型別、範圍型別、基本型別 或 殼型別。前四種將在下面依次討論。殼型別只是一個佔位符,用於稍後定義型別;它透過發出不帶除型別名稱之外的任何引數的CREATE TYPE
命令來建立。在建立範圍型別和基本型別時(如這些部分所述),需要殼型別作為前向引用。
第一種形式的 CREATE TYPE
建立一個複合型別。複合型別由屬性名稱和資料型別列表指定。如果屬性的資料型別是可排序的,也可以指定其排序規則。複合型別本質上與表的行型別相同,但是使用 CREATE TYPE
避免了在只需要定義一個型別而不需要建立實際表時建立表的需要。獨立複合型別很有用,例如,作為函式的引數或返回型別。
要能夠建立複合型別,您必須對所有屬性型別擁有 USAGE
許可權。
第二種形式的 CREATE TYPE
建立一個列舉(enum)型別,如第 8.7 節中所述。列舉型別接受一個由帶引號的標籤組成的列表,每個標籤的長度必須小於 NAMEDATALEN
位元組(在標準的 PostgreSQL 構建中為 64 位元組)。(可以建立一個不帶任何標籤的列舉型別,但這樣的型別在使用 ALTER TYPE
新增至少一個標籤之前不能用於儲存值。)
第三種形式的 CREATE TYPE
建立一個新範圍型別,如第 8.17 節中所述。
範圍型別的 subtype
必須是具有關聯 b-tree 運算子類的任何型別(用於確定範圍型別值的排序)。通常使用子型別的預設 b-tree 運算子類來確定排序;要使用非預設運算子類,請使用 subtype_opclass
指定其名稱。如果子型別是可排序的,並且您想在範圍排序中使用非預設排序規則,請使用 collation
選項指定所需的排序規則。
可選的canonical
函式必須接受一個引數,其型別為正在定義的範圍型別,並返回相同型別的值。這用於在適當時將範圍值轉換為規範形式。有關更多資訊,請參見第 8.17.8 節。建立 canonical
函式有點棘手,因為它必須在聲明範圍型別之前定義。為此,您必須首先建立一個殼型別,這是一個沒有除名稱和所有者之外任何屬性的佔位符型別。這可以透過發出命令 CREATE TYPE
來完成,不帶其他引數。然後可以使用殼型別作為引數和返回值來宣告函式,最後可以使用相同的名稱聲明範圍型別。這會自動將殼型別條目替換為有效的範圍型別。name
可選的subtype_diff
函式必須接受兩個subtype
型別的引數,並返回一個double precision
值,表示兩個給定值之間的差異。雖然這是可選的,但提供它允許對範圍型別列的 GiST 索引具有更高的效率。有關更多資訊,請參見第 8.17.8 節。
可選的multirange_type_name
引數指定了相應的多範圍型別的名稱。如果未指定,則名稱自動選擇如下。如果範圍型別名稱包含子字串 range
,則透過將 range
子字串替換為 multirange
來形成多範圍型別名稱。否則,將透過將 _multirange
字尾附加到範圍型別名稱來形成多範圍型別名稱。
第四種形式的 CREATE TYPE
建立一個新基本型別(標量型別)。要建立新基本型別,您必須是超級使用者。(此限制是由於錯誤的型別定義可能會混淆甚至崩潰伺服器。)
引數可以按任何順序出現,不一定是上面所示的順序,並且大多數是可選的。您必須(使用 CREATE FUNCTION
)註冊兩個或更多函式,然後才能定義型別。必須提供支援函式input_function
和 output_function
,而函式receive_function
、send_function
、type_modifier_input_function
、type_modifier_output_function
、analyze_function
和 subscript_function
是可選的。通常,這些函式必須用 C 或其他低階語言編寫。
該input_function
將型別的外部文字表示轉換為型別運算子和函式使用的內部表示。output_function
執行反向轉換。輸入函式可以宣告為接受一個 cstring
型別的引數,或者接受三個 cstring
、oid
、integer
型別的引數。第一個引數是 C 字串形式的輸入文字,第二個引數是型別本身的 OID(陣列型別除外,它們而是接收其元素型別的 OID),第三個引數是目標列的 typmod
(如果已知,否則傳遞 -1)。輸入函式必須返回資料型別本身的值。通常,輸入函式應宣告為 STRICT;如果不是,則在讀取 NULL 輸入值時,它將以 NULL 的第一個引數被呼叫。在這種情況下,函式仍必須返回 NULL,除非它引發錯誤。(此情況主要用於支援域輸入函式,這些函式可能需要拒絕 NULL 輸入。)輸出函式必須宣告為接受新資料型別的一個引數。輸出函式必須返回 cstring
型別。輸出函式不會為 NULL 值呼叫。
可選的receive_function
將型別的外部二進位制表示轉換為內部表示。如果未提供此函式,則該型別不能進行二進位制輸入。二進位制表示應選擇易於轉換為內部形式,同時具有可移植性。(例如,標準整數資料型別使用網路位元組順序作為外部二進位制表示,而內部表示是機器的本機位元組順序。)接收函式應執行充分的檢查,以確保值有效。接收函式可以宣告為接受一個 internal
型別的引數,或者接受三個 internal
、oid
、integer
型別的引數。第一個引數是指向 StringInfo
緩衝區(包含接收到的位元組串)的指標;可選引數與文字輸入函式相同。接收函式必須返回資料型別本身的值。通常,接收函式應宣告為 STRICT;如果不是,則在讀取 NULL 輸入值時,它將以 NULL 的第一個引數被呼叫。在這種情況下,函式仍必須返回 NULL,除非它引發錯誤。(此情況主要用於支援域接收函式,這些函式可能需要拒絕 NULL 輸入。)類似地,可選的send_function
將內部表示轉換為外部二進位制表示。如果未提供此函式,則該型別不能進行二進位制輸出。傳送函式必須宣告為接受新資料型別的一個引數。傳送函式必須返回 bytea
型別。傳送函式不會為 NULL 值呼叫。
此時您可能會想,輸入和輸出函式如何能宣告它們具有新型別的結果或引數,因為它們必須在新型別建立之前建立。答案是,型別應首先定義為殼型別,這是一個佔位符型別,它除了名稱和所有者之外沒有其他屬性。這可以透過發出命令 CREATE TYPE
來實現,不帶任何其他引數。然後可以引用 C I/O 函式來定義殼型別。最後,帶有完整定義的 name
CREATE TYPE
會將殼條目替換為完整、有效的型別定義,之後新型別就可以正常使用了。
可選的type_modifier_input_function
和 type_modifier_output_function
在型別支援修飾符時是必需的,即附加到型別宣告的可選約束,例如 char(5)
或 numeric(30,2)
。PostgreSQL 允許使用者定義型別接受一個或多個簡單的常量或識別符號作為修飾符。但是,此資訊必須能夠打包為單個非負整數值,以便儲存在系統目錄中。type_modifier_input_function
接收以 cstring
陣列形式宣告的修飾符。它必須檢查值的有效性(如果錯誤則丟擲錯誤),如果正確,則返回一個非負整數值,該值將作為列 “typmod” 儲存。如果型別沒有 type_modifier_input_function
,則將拒絕型別修飾符。type_modifier_output_function
將內部整數 typmod 值轉換回使用者顯示器的正確形式。它必須返回一個 cstring
值,該值是應附加到型別名稱的確切字串;例如 numeric
的函式可能會返回 (30,2)
。允許省略 type_modifier_output_function
,在這種情況下,預設顯示格式只是用括號括起來的儲存的 typmod 整數值。
可選的analyze_function
為資料型別的列執行型別特定的統計資訊收集。預設情況下,ANALYZE
將嘗試使用型別的 “equals” 和 “less-than” 運算子收集統計資訊,前提是該型別存在預設的 b-tree 運算子類。對於非標量型別,此行為可能不合適,因此可以透過指定自定義分析函式來覆蓋它。分析函式必須宣告為接受 internal
型別的一個引數,並返回一個 boolean
結果。分析函式的詳細 API 出現在 src/include/commands/vacuum.h
中。
可選的subscript_function
允許資料型別在 SQL 命令中被下標訪問。指定此函式不會使該型別被視為“真正的”陣列型別;例如,它不是 ARRAY[]
構造的結果型別的候選。但是,如果下標訪問該型別的值是提取其資料的自然表示法,則可以編寫 subscript_function
來定義其含義。下標函式必須宣告為接受 internal
型別的一個引數,並返回一個 internal
結果,該結果是指向實現下標的函式結構的指標。下標函式的詳細 API 出現在 src/include/nodes/subscripting.h
中。閱讀src/backend/utils/adt/arraysubs.c
中的陣列實現,或contrib/hstore/hstore_subs.c
中的更簡單的程式碼也可能很有用。附加資訊出現在下面的陣列型別中。
雖然新型別內部表示的細節僅由 I/O 函式和其他您建立的用於處理該型別的函式知道,但有幾個內部表示的屬性必須宣告給 PostgreSQL。其中最重要的是internallength
。基本資料型別可以是固定長度的,在這種情況下internallength
是一個正整數,或者可變長度的,透過將internallength
設定為VARIABLE
來指示。(內部來說,這表示為將 typlen
設定為 -1。)所有可變長度型別的內部表示都必須以 4 位元組整數開頭,該整數給出該型別值的總長度。(請注意,長度欄位通常是編碼的,如第 66.2 節中所述;直接訪問它是不明智的。)
可選標誌 PASSEDBYVALUE
表示該資料型別的值按值傳遞,而不是按引用傳遞。按值傳遞的型別必須是固定長度的,並且其內部表示不能大於 Datum
型別的大小(在某些機器上為 4 位元組,在其他機器上為 8 位元組)。
該alignment
引數指定資料型別的儲存對齊要求。允許的值相當於 1、2、4 或 8 位元組邊界的對齊。請注意,可變長度型別必須具有至少 4 的對齊,因為它們必然包含 int4
作為其第一個元件。
該storage
引數允許為可變長度資料型別選擇儲存策略。(固定長度型別只允許 plain
。)plain
指定該型別的資料將始終內聯儲存,不壓縮。extended
指定系統將首先嚐試壓縮長資料值,並且如果值仍然太長,它會將值移出主錶行。external
允許將值移出主表,但系統不會嘗試壓縮它。main
允許壓縮,但會阻止將值移出主表。(具有此儲存策略的資料項如果無法使行適合,仍可能被移出主表,但它們將優先於 extended
和 external
項保留在主表中。)
除 plain
外的所有 storage
值都暗示該資料型別的函式可以處理已TOAST 處理過的值,如第 66.2 節和第 36.13.1 節中所述。具體的其他值僅決定 TOAST 可處理資料型別的預設 TOAST 儲存策略;使用者可以使用 ALTER TABLE SET STORAGE
為單個列選擇其他策略。
該like_type
引數提供了一種替代方法來指定資料型別的基本表示屬性:從現有型別複製它們。 internallength
、passedbyvalue
、alignment
和 storage
的值將從指定型別複製。(雖然通常不希望這樣做,但可以在指定 LIKE
子句的同時覆蓋其中一些值。)在新的低階實現“依附”於現有型別的情況下,使用這種方式指定表示尤其有用。
category
和 preferred
引數可用於幫助控制在歧義情況下將應用哪個隱式轉換。每種資料型別都屬於一個由單個 ASCII 字元命名的類別,並且每種型別在其類別中是 “首選” 的或不是。“解析器” 將優先轉換為首選型別(但僅限於同一類別內的其他型別),當此規則有助於解析過載函式或運算子時。有關更多詳細資訊,請參見第 10 章。對於沒有與任何其他型別進行隱式轉換的型別,將其設定保留為預設值就足夠了。但是,對於一組具有隱式轉換的相關型別,通常將它們全部標記為屬於一個類別,並選擇一兩個“最通用”的型別作為該類別內的首選。category
引數在將使用者定義型別新增到現有內建類別(如數字或字串型別)時尤其有用。但是,也可以建立全新的使用者定義型別類別。選擇除大寫字母以外的任何 ASCII 字元來命名此類類別。
如果使用者希望資料型別的列預設為除 null 值以外的其他值,則可以指定預設值。使用 DEFAULT
關鍵字指定預設值。(此類預設值可以被附加到特定列的顯式 DEFAULT
子句覆蓋。)
要指示型別是固定長度陣列型別,請使用 ELEMENT
關鍵字指定陣列元素的型別。例如,要定義一個 4 位元組整數(int4
)的陣列,請指定 ELEMENT = int4
。有關更多詳細資訊,請參見下面的陣列型別。
要指定陣列值在陣列的外部表示中使用的分隔符,delimiter
可以設定為特定字元。預設分隔符是逗號(,
)。請注意,分隔符與陣列元素型別相關聯,而不是與陣列型別本身。
如果可選布林引數collatable
為 true,則型別的列定義和表示式可以透過使用 COLLATE
子句攜帶排序資訊。實際使用排序資訊取決於操作該型別的函式的實現;僅僅透過標記型別為可排序,這不會自動發生。
每當建立使用者定義型別時,PostgreSQL 都會自動建立一個關聯的陣列型別,其名稱由元素型別的名稱組成,前面加上下劃線,並在必要時截斷以使其長度小於 NAMEDATALEN
位元組。(如果生成的名稱與現有型別名稱衝突,則重複此過程,直到找到不衝突的名稱。)這個隱式建立的陣列型別是可變長度的,並使用內建的輸入和輸出函式 array_in
和 array_out
。此外,系統使用此型別來處理使用者定義型別上的 ARRAY[]
等構造。陣列型別會跟蹤其元素型別的所有者或模式的任何更改,並且如果元素型別被刪除,它也會被刪除。
如果你問為什麼有一個 ELEMENT
選項,如果系統會自動建立正確的陣列型別。使用 ELEMENT
的主要情況是,當你正在建立一個固定長度的型別,而這個型別內部是一個數量相同的元素的陣列,並且你希望允許透過下標直接訪問這些元素,除了你打算為整個型別提供的操作之外。例如,型別 point
只表示為兩個浮點數,可以透過 point[0]
和 point[1]
訪問。請注意,此功能僅適用於內部形式完全是相同固定長度欄位序列的固定長度型別。出於歷史原因(即,這顯然是錯誤的,但為時已晚無法更改),固定長度陣列型別的下標從零開始,而不是像可變長度陣列那樣從一開始。
指定 SUBSCRIPT
選項允許資料型別被下標訪問,即使系統不認為它是陣列型別。固定長度陣列的這種行為實際上是透過 SUBSCRIPT
處理函式 raw_array_subscript_handler
實現的,如果你為固定長度型別指定 ELEMENT
而不指定 SUBSCRIPT
,則會自動使用它。
指定自定義 SUBSCRIPT
函式時,沒有必要指定 ELEMENT
,除非 SUBSCRIPT
處理函式需要諮詢 typelem
來了解它應該返回什麼。請注意,指定 ELEMENT
會導致系統假定新型別包含,或在某種程度上在物理上依賴於元素型別;因此,例如,如果存在依賴型別的任何列,則不允許更改元素型別的屬性。
name
要建立的型別的名稱(可選模式限定)。
attribute_name
複合型別的屬性(列)的名稱。
data_type
將成為複合型別一列的現有資料型別的名稱。
collation
複合型別的列或範圍型別的屬性的現有排序規則的名稱。
label
表示與列舉型別的一個值關聯的文字標籤的字串字面量。
subtype
範圍型別將表示範圍的元素型別的名稱。
subtype_operator_class
子型別的 b-tree 運算子類的名稱。
canonical_function
範圍型別的規範化函式的名稱。
subtype_diff_function
子型別的差異函式的名稱。
multirange_type_name
相應的多範圍型別的名稱。
input_function
將資料從型別的外部文字形式轉換為其內部形式的函式的名稱。
output_function
將資料從型別的內部形式轉換為其外部文字形式的函式的名稱。
receive_function
將資料從型別的外部二進位制形式轉換為其內部形式的函式的名稱。
send_function
將資料從型別的內部形式轉換為其外部二進位制形式的函式的名稱。
type_modifier_input_function
將型別的修飾符陣列轉換為內部形式的函式的名稱。
type_modifier_output_function
將型別的修飾符的內部形式轉換為外部文字形式的函式的名稱。
analyze_function
為資料型別執行統計分析的函式的名稱。
subscript_function
定義下標訪問資料型別值的含義的函式的名稱。
internallength
一個數字常量,指定新型別內部表示的長度(以位元組為單位)。預設假設它是可變長度的。
alignment
資料型別的儲存對齊要求。如果指定,它必須是 char
、int2
、int4
或 double
;預設值為 int4
。
storage
資料型別的儲存策略。如果指定,必須是 plain
、external
、extended
或 main
;預設值為 plain
。
like_type
現有資料型別的名稱,新型別將與其具有相同的表示形式。 internallength
、passedbyvalue
、alignment
和 storage
的值將從該型別複製,除非在 CREATE TYPE
命令的其他地方明確指定覆蓋。
category
此型別的類別程式碼(單個 ASCII 字元)。預設值為 'U'
,表示“使用者定義型別”。其他標準類別程式碼可以在表 52.65 中找到。您也可以選擇其他 ASCII 字元來建立自定義類別。
preferred
如果此型別是其型別類別中的首選型別,則為 true,否則為 false。預設為 false。在現有型別類別中建立新首選型別時要非常小心,因為這可能會導致行為發生意外更改。
default
資料型別的預設值。如果省略,則預設為 null。
element
正在建立的型別是一個數組;這指定了陣列元素的型別。
delimiter
由該型別組成的陣列值之間使用的分隔符字元。
collatable
如果此型別操作可以使用排序資訊,則為 true。預設為 false。
由於一旦建立了資料型別,對其使用就沒有限制,因此建立基本型別或範圍型別相當於授予了在型別定義中引用的函式公共的執行許可權。對於型別定義中有用的函式來說,這通常不成問題。但是,您可能需要三思而後行,然後再設計一個需要“秘密”資訊才能從外部形式轉換或轉換為外部形式的型別。
在 PostgreSQL 8.3 版本之前,生成的陣列型別的名稱總是正好是元素型別名稱前面加上一個下劃線字元(_
)。(因此,型別名稱的長度受限於比其他名稱少一個字元。)雖然通常仍然是這樣,但如果出現最大長度名稱或與以使用者型別名稱開頭(如下劃線)的使用者型別名稱衝突,陣列型別名稱可能與此不同。因此,依賴此約定編寫程式碼已棄用。而是使用 pg_type
.typarray
來定位與給定型別關聯的陣列型別。
建議避免使用以 [下劃線] 開頭的型別和表名。雖然伺服器會更改生成的陣列型別名稱以避免與使用者給定的名稱衝突,但仍然存在混淆的風險,特別是對於可能假定以 [下劃線] 開頭的型別名稱始終代表陣列的舊客戶端軟體。
在 PostgreSQL 8.2 版本之前,殼型別建立語法 CREATE TYPE
不存在。建立新基本型別的方法是先建立其輸入函式。在此方法中,PostgreSQL 首先將新資料型別的名稱視為輸入函式的返回型別。在這種情況下,殼型別會被隱式建立,然後可以在其餘 I/O 函式的定義中引用它。此方法仍然有效,但已棄用,並可能在將來的某個版本中被禁止。此外,為了避免由於函式定義中的簡單拼寫錯誤而意外地充斥目錄中的殼型別,只有當輸入函式是用 C 編寫的時,才會以這種方式建立殼型別。name
在 PostgreSQL 16 及更高版本中,最好為基本型別的輸入函式返回“軟”錯誤,使用新的 errsave()
/ereturn()
機制,而不是像以前版本那樣丟擲 ereport()
異常。有關更多資訊,請參見 src/backend/utils/fmgr/README
。
此示例建立了一個複合型別並在函式定義中使用它
CREATE TYPE compfoo AS (f1 int, f2 text); CREATE FUNCTION getfoo() RETURNS SETOF compfoo AS $$ SELECT fooid, fooname FROM foo $$ LANGUAGE SQL;
此示例建立了一個列舉型別並在表定義中使用它
CREATE TYPE bug_status AS ENUM ('new', 'open', 'closed'); CREATE TABLE bug ( id serial, description text, status bug_status );
此示例建立了一個範圍型別
CREATE TYPE float8_range AS RANGE (subtype = float8, subtype_diff = float8mi);
此示例建立了基本資料型別 box
,然後在一個表定義中使用該型別
CREATE TYPE box; CREATE FUNCTION my_box_in_function(cstring) RETURNS box AS ... ; CREATE FUNCTION my_box_out_function(box) RETURNS cstring AS ... ; CREATE TYPE box ( INTERNALLENGTH = 16, INPUT = my_box_in_function, OUTPUT = my_box_out_function ); CREATE TABLE myboxes ( id integer, description box );
如果 box
的內部結構是四個 float4
元素的陣列,我們也可以使用
CREATE TYPE box ( INTERNALLENGTH = 16, INPUT = my_box_in_function, OUTPUT = my_box_out_function, ELEMENT = float4 );
這將允許透過下標訪問 box 值的分量編號。否則,該型別行為與之前相同。
此示例建立了一個大型物件型別並在表定義中使用它
CREATE TYPE bigobj ( INPUT = lo_filein, OUTPUT = lo_fileout, INTERNALLENGTH = VARIABLE ); CREATE TABLE big_objs ( id integer, obj bigobj );
更多示例,包括合適的輸入和輸出函式,請參見第 36.13 節。
第一種形式的 CREATE TYPE
命令,它建立複合型別,符合SQL標準。其他形式是 PostgreSQL 的擴充套件。標準中的 CREATE TYPE
語句SQL還定義了其他未在 PostgreSQL 中實現的形式。
建立具有零個屬性的複合型別的能力是 PostgreSQL 對標準的特定偏離(類似於 CREATE TABLE
的相同情況)。
如果您在文件中看到任何不正確、與您對特定功能的經驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。