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
的相同情况)。
如果您在文档中看到任何不正确、与您对特定功能的经验不符或需要进一步澄清的内容,请使用此表单报告文档问题。