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 树操作符类的类型(以确定范围类型值的顺序)。通常,子类型的默认 B 树操作符类用于确定排序;要使用非默认操作符类,请使用 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
会用完整有效的类型定义替换外壳条目,之后就可以正常使用新类型。
如果类型支持修饰符(即附加到类型声明的可选约束,例如 char(5)
或 numeric(30,2)
),则需要可选的 type_modifier_input_function
和 type_modifier_output_function
。 PostgreSQL 允许用户定义的类型将一个或多个简单常量或标识符作为修饰符。但是,此信息必须能够被打包成单个非负整数值,以便存储在系统目录中。type_modifier_input_function
以 cstring
数组的形式传递声明的修饰符。它必须检查值的有效性(如果值错误则抛出错误),如果它们正确,则返回一个非负的 integer
值,该值将存储为列的 “typmod”。如果类型没有 type_modifier_input_function
,则类型修饰符将被拒绝。type_modifier_output_function
将内部整数 typmod 值转换回用户显示所需的正确格式。它必须返回一个 cstring
值,该值是要附加到类型名称的精确字符串;例如,numeric
的函数可能会返回 (30,2)
。允许省略 type_modifier_output_function
,在这种情况下,默认的显示格式只是用括号括起来的存储的 typmod 整数值。
可选的 analyze_function
对数据类型列执行类型特定的统计信息收集。默认情况下,如果该类型有默认的 b-tree 操作符类,则 ANALYZE
将尝试使用该类型的 “等于” 和 “小于” 操作符来收集统计信息。对于非标量类型,此行为可能不合适,因此可以通过指定自定义分析函数来覆盖它。分析函数必须声明为接受 internal
类型的单个参数,并返回 boolean
结果。分析函数的详细 API 出现在 src/include/commands/vacuum.h
中。
可选的 subscript_function
允许在 SQL 命令中对数据类型进行下标。指定此函数不会使该类型被视为 “真正” 的数组类型;例如,它不会成为 ARRAY[]
构造的结果类型的候选项。但是,如果对该类型的值进行下标操作是从中提取数据的自然表示法,则可以编写一个 subscript_function
来定义其含义。下标函数必须声明为接受 internal
类型的单个参数,并返回一个 internal
结果,该结果是指向实现下标的 struct 方法(函数)的指针。下标函数的详细 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 字节的整数开头,该整数给出该类型值的总长度。(请注意,长度字段通常是经过编码的,如 第 65.2 节 中所述;直接访问它是不明智的。)
可选标志 PASSEDBYVALUE
指示此数据类型的值是通过值传递而不是通过引用传递。通过值传递的类型必须是固定长度的,并且它们的内部表示不能大于 Datum
类型的大小(某些机器上为 4 个字节,其他机器上为 8 个字节)。
alignment
参数指定数据类型所需的存储对齐方式。允许的值等效于 1、2、4 或 8 字节边界上的对齐方式。请注意,可变长度类型必须至少具有 4 的对齐方式,因为它们必然包含一个 int4
作为其第一个组件。
storage
参数允许选择可变长度数据类型的存储策略。(固定长度类型只允许使用 plain
。)plain
指定该类型的数据将始终内联存储且不压缩。extended
指定系统将首先尝试压缩长数据值,如果该值仍然太长,则将其移出主表行。external
允许将值移出主表,但系统不会尝试压缩它。main
允许压缩,但不鼓励将值移出主表。(如果无法以其他方式使行适合,则具有此存储策略的数据项仍然可能被移出主表,但它们将优先于 extended
和 external
项保留在主表中。)
除了 plain
之外的所有 storage
值都意味着数据类型的函数可以处理已经 toasted 的值,如 第 65.2 节 和 第 36.13.1 节 所述。给定的其他特定值仅确定可 toasting 数据类型列的默认 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
函数时,除非 SUBSCRIPT
处理函数需要查询 typelem
以确定返回什么,否则无需指定 ELEMENT
。请注意,指定 ELEMENT
会导致系统假定新类型包含元素类型或在某种程度上物理依赖于元素类型;因此,例如,如果存在任何依赖类型的列,则不允许更改元素类型的属性。
name
要创建的类型名称(可选,包含模式限定)。
attribute_name
复合类型的属性(列)名称。
data_type
要成为复合类型列的现有数据类型的名称。
collation
要与复合类型的列或范围类型关联的现有排序规则的名称。
label
表示枚举类型的一个值的文本标签的字符串文字。
subtype
范围类型将表示的范围的元素类型的名称。
subtype_operator_class
子类型的 b 树运算符类的名称。
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
新类型将具有相同表示形式的现有数据类型的名称。除非在此 CREATE TYPE
命令的其他地方明确指定覆盖,否则 internallength
、passedbyvalue
、alignment
和 storage
的值将从该类型复制。
category
此类型的类别代码(单个 ASCII 字符)。默认值为 “用户定义的类型” 的 'U'
。其他标准类别代码可以在 表 51.65 中找到。您还可以选择其他 ASCII 字符来创建自定义类别。
preferred
如果此类型是其类型类别中的首选类型,则为 true,否则为 false。默认值为 false。在现有类型类别中创建新的首选类型时要非常小心,因为这可能会导致行为发生令人惊讶的变化。
default
数据类型的默认值。如果省略,则默认为 null。
element
正在创建的类型是一个数组;这指定了数组元素的类型。
delimiter
用于此类型数组中值之间的分隔符字符。
collatable
如果此类型的操作可以使用排序规则信息,则为 True。默认值为 false。
由于对数据类型的使用没有任何限制,因此创建基本类型或范围类型等同于授予对类型定义中提到的函数的公共执行权限。对于在类型定义中有用的函数类型来说,这通常不是问题。但是,在设计类型时,如果需要在将其转换为外部形式或从外部形式转换时使用“秘密”信息,您可能需要三思而后行。
在 PostgreSQL 8.3 版本之前,生成的数组类型的名称始终是元素类型的名称,前面加上一个下划线字符 (_
)。 (因此,类型名称的长度限制为比其他名称少一个字符。)虽然通常仍然是这种情况,但如果名称长度达到最大值或与以下划线开头的用户类型名称发生冲突,则数组类型名称可能会有所不同。因此,不建议编写依赖于此约定的代码。相反,应使用 pg_type
.typarray
来查找与给定类型关联的数组类型。
建议避免使用以下划线开头的类型和表名称。虽然服务器会更改生成的数组类型名称以避免与用户给定的名称发生冲突,但仍然存在混淆的风险,特别是对于可能假设以下划线开头的类型名称始终表示数组的旧客户端软件而言。
在 PostgreSQL 8.2 版本之前,shell 类型创建语法 CREATE TYPE
不存在。创建新的基本类型的方法是首先创建其输入函数。在这种方法中,PostgreSQL 首先将新数据类型的名称视为输入函数的返回类型。在这种情况下,隐式创建 shell 类型,然后可以在其余 I/O 函数的定义中引用它。此方法仍然有效,但不建议使用,并且在将来的某些版本中可能会被禁止。此外,为了避免由于函数定义中的简单拼写错误而意外地用 shell 类型使目录混乱,仅当输入函数是用 C 编写时,才会通过这种方式创建 shell 类型。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 扩展。在SQL标准中,CREATE TYPE
语句还定义了 PostgreSQL 中未实现的其他形式。
创建具有零个属性的复合类型的功能是 PostgreSQL 特有的与标准的偏差(类似于 CREATE TABLE
中的相同情况)。
如果您在文档中发现任何不正确、与您特定功能的使用体验不符或需要进一步澄清的地方,请使用此表单报告文档问题。