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
CLASS
命令为当前数据库注册一个新的数据类型。定义类型的用户成为其拥有者。
如果给出了模式名称,则在指定的模式中创建类型。否则,在当前模式中创建。类型名称必须不同于相同模式中的任何现有类型或域的名称。(由于表已关联了数据类型,类型名称也必须不同于相同模式中任何现有表的名称。)
CREATE TYPE
有五种形式,如上面语法概要中所示。它们分别创建复合类型、枚举类型、范围类型、基本类型或shell 类型。前四个类型分别在下面讨论。shell 类型只是稍后定义类型的占位符;它在除了类型名称外没有其他参数的情况下发出 CREATE TYPE
而创建。在创建范围类型和基本类型时,需要 shell 类型作为前向引用,如在那些部分中所述。
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
,则多重范围类型名称通过用multirange
替换范围类型名称中的range
子字符串形成。否则,多重范围类型名称通过向范围类型名称追加_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
执行反向转换。可以声明 input 函数采用一个类型为 cstring
的参数,也可以声明它采用三个类型为 cstring
、oid
、integer
的参数。第一个参数是作为 C 字符串的输入文本,第二个参数是类型自身的 OID(数组类型除外,它们是接收元素类型的 OID),第三个参数是目标列的 typmod
,如果已知(如果未知,将传递 -1)。input 函数必须返回数据类型本身的值。通常,input 函数应声明为 STRICT;如果不是,那么在读取 NULL 输入值时将使用一个 NULL 第一个参数调用它。在这种情况下,函数仍必须返回 NULL,除非引发错误。(此情况主要用于支持域 input 函数,它可能需要拒绝 NULL 输入。)必须声明 output 函数采用新数据类型的一个参数。output 函数必须返回类型 cstring
。不为 NULL 值调用 output 函数。
可选的 receive_function
将此类型的外部二进制表示转换成内部表述形式。如果未提供此函数,则此类型将无法参与二进制输入。应当对二进制表述形式进行选择以便便宜地转换成内部格式,而且同时保持合理的便携性。(例如,标准整数数据类型使用网络字节顺序作为外部二进制表述形式,而内部表述形式按该机器的原生字节顺序。)接收函数应当执行充分的检查以确保该值有效。可以将接收函数声明为获取类型 internal
的一个参数,或声明为获取类型 internal
、oid
、integer
的三个参数。第一个参数是指向一个保存已接收字节字符串的 StringInfo
缓冲区的指针;可选参数与此文本输入函数的参数相同。接收函数必须返回数据类型本身的一个值。接收函数通常应声明为严格的;如果不是,当读取一个 NULL 输入值时,它将用一个 NULL 第一个参数加以调用。此种情况下,该函数仍必须返回 NULL,除非它引起了错误。(此用例主要用于支持域接收函数,域接收函数可能需要拒绝 NULL 输入。)类似地,可选的 send_function
将转换自内部表述形式成外部二进制表述形式。如果未提供此函数,则此类型将无法参与二进制输出。必须将发送函数声明为获取新数据类型的一个参数。发送函数必须返回类型 bytea
。不会为 NULL 值调用发送函数。
现在你应该在想,如何在输入和输出函数被创建为具有新类型的结果或参数时声明这些函数,因为它们必须在该新类型创建之前创建。答案是应首先将该类型定义为 shell 类型,这是一个没有属性的占位符类型,除了一个名称和一个所有者以外。这通过发出 CREATE TYPE
命令来完成,该命令不带其他参数。然后可以定义引用 shell 类型的 C I/O 函数。最后,带有完全定义的 name
CREATE TYPE
将 shell 条目替换为一个完全有效的类型定义,随后可以正常地使用该新类型。
可选的 type_modifier_input_function
和 type_modifier_output_function
是在类型支持修饰符时必需的,即附加到类型声明中的可选约束,如 char(5)
或 numeric(30,2)
。 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
执行数据类型列的特定于类型的统计信息收集。默认情况下,ANALYZE
将尝试使用类型的 “equals” 和 “less-than” 运算符收集统计信息,如果类型有默认的 b-tree 运算符类。对于非标量类型,此行为可能不合适,因此可以通过指定自定义分析函数来覆盖它。必须将分析函数声明为采用单个类型 internal
的参数,并返回 boolean
结果。分析函数的详细 API 载于 src/include/commands/vacuum.h
中。
可选的 subscript_function
允许在 SQL 命令中对数据类型进行指标化。指定此函数不会导致将类型视为一个 “true” 数组类型;例如,它不会成为 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 字节整数开头,该整数给出了该类型此值的总长度。(请注意,长度字段通常会进行编码,如 第 65.2 节 所述;不应该直接对其进行访问。)
可选标记 PASSEDBYVALUE
表示按值传递此数据类型的值,而不是按引用传递。按值传递的类型必须是固定长度的,并且这些类型的内部表示不能大于 Datum
类型的尺寸(在某些机器上是 4 字节,在其他机器上是 8 字节)。
alignment
参数指定数据类型所需的存储对齐。允许的值等于在 1、2、4 或 8 字节边界上的对齐。请注意,变长类型必须对齐至少 4,因为这些类型必然包含 int4
作为其第一个分量。
storage
参数允许选择可变长度数据类型存储策略。(对于固定长度类型,仅允许使用 plain
)。plain
指定该类型的数据将始终以行内方式存储,并且不进行压缩。extended
指定系统将首先尝试压缩长数据值,并且如果值仍然太长,则将该值移出主表行。external
允许将该值移出主表,但系统不会尝试对其进行压缩。main
允许压缩,但不建议将该值移出主表。(如果无法通过其他方式使行适合,仍然可能会将采用此存储策略的数据项移出主表,但它们将被优先保留在主表中,而不是 extended
和 external
项。)
除 plain
之外的所有 storage
值均暗示数据类型函数可以处理 吐司过(如 第 65.2 节 和 第 36.13.1 节 所述)的值。给定的其他特定值仅仅确定可吐司数据类型列的默认 TOAST 存储策略;用户可以使用 ALTER TABLE SET STORAGE
为各个列选择其他策略。
like_type
参数提供了一种替代方法来指定数据类型的基本表示属性:从某些现有类型中复制它们。将从指定的类型中复制 internallength
、passedbyvalue
、alignment
和 storage
的值。(有可能(虽然通常不建议)通过与 LIKE
子句一起指定这些值来覆盖其中一些值。)采用这种方式指定表示在新的数据类型低级实现以某种方式“搭载”现有类型时特别有用。
可以使用category
和 preferred
参数来帮助控制在歧义情形下将应用哪种隐式转换。每种数据类型属于由单个 ASCII 字符命名的类别,并且每种类型在该类别中要么是“首选”,要么不是。在解析器将此规则应用于解决重载函数或操作符时有所帮助时,它会优先转换为首选类型(但仅从同一类别中的其他类型)。有关更多详细信息,请参阅第 10 章。对于没有到或来自任何其他类型的隐式转换的类型而言,将这些设置保留为默认值就足够了。但是,对于具有隐式转换的一组相关类型,通常将它们全部标记为属于某个类别,并选择一或两个“最通用”类型作为该类别中的首选类型,这样做通常会有帮助。当向现有的内置类别(例如数字或字符串类型)添加用户定义类型时,category
参数特别有用。但是,也可以创建新的完全由用户定义的类型类别。选择除大写字母以外的任何 ASCII 字符来命名此类类别。
如果用户希望数据类型列的默认值不是空值,则可以指定默认值。通过DEFAULT
关键字指定默认值。(此类默认值可以被附加到特定列的显式DEFAULT
子句替代。)
要表明类型是固定长度数组类型,请使用ELEMENT
关键字指定数组元素的类型。例如,要定义 4 字节整数 (int4
) 的数组,请指定ELEMENT = int4
。有关更多详细信息,请参阅下文的数组类型。
若要指定在此类型的数组的外部表示形式的值之间使用的定界符,可将delimiter
设置为特定字符。默认定界符是逗号 (,
)。请注意,定界符与数组元素类型相关联,而不是与数组类型本身相关联。
如果可选布尔参数collatable
为真,则该类型的列定义和表达式可以通过使用COLLATE
子句来承载校对信息。由操作该类型的函数的实现实际使用校对信息;这不会仅仅通过将该类型标记为 collatable 而自动发生。
每当创建一个用户定义的类型时,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
将成为复合类型列的现有数据类型的名称。
排序规则
将与复合类型列或范围类型相关联的现有排序规则的名称。
标签
表示与枚举类型的某个值相关的文本标签的字符串文字。
子类型
范围类型将表示其范围的元素类型的名称。
子类型运算符类
子类型的 b 树运算符类的名称。
规范函数
范围类型规范化函数的名称。
子类型差异函数
子类型的差异函数的名称。
多范围类型名称
相应多范围类型的名称。
输入函数
将数据从类型的外部文本形式转换为内部形式的函数的名称。
输出函数
将数据从类型的内部形式转换为外部文本形式的函数的名称。
接收函数
将数据从类型的外部二进制形式转换为内部形式的函数的名称。
发送函数
将数据从类型的内部形式转换为外部二进制形式的函数的名称。
类型修改器输入函数
将类型修改器(数组)转换为内部形式的函数的名称。
类型修改器输出函数
将类型的修改器的内部形式转换为外部文本形式的函数的名称。
分析函数
对数据类型执行统计分析的函数的名称。
下标函数
定义对数据类型的某个值进行下标编制的函数的名称。
internallength
指定新类型的内部表示长度(以字节为单位)的数字常量。默认假设为可变长度。
对齐方式
数据类型的存储对齐要求。如果指定,则必须为 char
、int2
、int4
或 double
;默认值为 int4
。
存储
数据类型的存储策略。如果指定,则必须为 plain
、external
、extended
或 main
;默认值为 plain
。
同类类型
新类型将与之具有相同表示形式的现有数据类型的名称。internallength
、passedbyvalue
、alignment
和 storage
的值将从该类型复制,除非在此 CREATE TYPE
命令中的其他位置通过显式指定覆盖这些值。
类别
此类型的类别代码(单个 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 版之前,不存在外壳类型创建语法 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 的扩展。在SQL标准中 CREATE TYPE
语句还定义了其他形式,但这些形式在 PostgreSQL 中未实现。
创建具有零属性的复合类型的能力是 PostgreSQL 对该标准做出的特定于 PostgreSQL 的偏差(类似于 CREATE TABLE
中的相同情况)。