↑↓ 选择↵ 打开⌫ 切换范围完整搜索

PG.CENTER 连接 PostgreSQL 文档、百科与生态知识。由 Pigsty 维护。

支持中的版本: 当前版本 (18) / 17 / 16 / 15 / 14
开发中的版本: 19 / 20devel
已结束支持的版本: 13 / 12 / 11 / 10 / 9.6 / 9.5 / 9.4 / 9.3 / 9.2 / 9.1 / 9.0 / 8.4 / 8.3 / 8.2 / 8.1 / 8.0 / 7.4 / 7.3 / 7.2 / 7.1 / 7.0 / 6.5 / 6.4
历史版本。 PostgreSQL 12 已结束支持。 2024-11-21. 请参阅 当前版本手册.

CREATE TYPE

CREATE TYPE — 定义一种新数据类型

大纲

CREATE TYPE name AS
    ( [ attribute_name data_type [ COLLATE collation ] [, ... ] ] )

CREATE TYPE name AS ENUM
    ( [ 'label' [, ... ] ] )

CREATE TYPE name AS RANGE (
    SUBTYPE = subtype
    [ , SUBTYPE_OPCLASS = subtype_operator_class ]
    [ , COLLATION = collation ]
    [ , CANONICAL = canonical_function ]
    [ , SUBTYPE_DIFF = subtype_diff_function ]
)

CREATE TYPE name (
    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 ]
    [ , INTERNALLENGTH = { internallength | VARIABLE } ]
    [ , PASSEDBYVALUE ]
    [ , ALIGNMENT = alignment ]
    [ , STORAGE = storage ]
    [ , LIKE = like_type ]
    [ , CATEGORY = category ]
    [ , PREFERRED = preferred ]
    [ , DEFAULT = default ]
    [ , ELEMENT = element ]
    [ , DELIMITER = delimiter ]
    [ , COLLATABLE = collatable ]
)

CREATE TYPE name

描述

CREATE TYPE 在当前数据库中注册一种新的数据类型。定义该类型的用户将成为其拥有者。

如果给出了模式名,则该类型会在指定模式中创建;否则会在当前模式中创建。类型名必须不同于同一模式中任何现有类型或域的名称。(由于表也具有关联的数据类型,类型名还必须不同于同一模式中任何现有表的名称。)

如上面的语法概要所示,CREATE TYPE 有五种形式。它们分别用于创建复合类型、枚举类型、范围类型、基础类型或 shell 类型。下文将依次讨论前四种。shell 类型只是稍后定义某种类型时使用的占位符;它通过执行除类型名外不带任何参数的 CREATE TYPE 来创建。正如相应小节所述,创建范围类型和基础类型时,需要用 shell 类型作为前向引用。

复合类型

第一种形式的 CREATE TYPE 创建复合类型。复合类型由属性名和数据类型列表指定。如果某个属性的数据类型支持排序规则,还可以指定该属性的排序规则。复合类型本质上与表的行类型相同,但如果目的只是定义一种类型,使用 CREATE TYPE 就不必实际创建表。例如,独立的复合类型可用作函数的参数类型或返回类型。

要创建复合类型,必须对所有属性类型都拥有 USAGE 权限。

枚举类型

如第 8.7 节中所述,第二种形式的 CREATE TYPE 创建枚举(enum)类型。枚举类型接受一个带引号标签的列表,其中每个标签的长度都必须小于 NAMEDATALEN 字节(在标准 PostgreSQL 构建中为 64 字节)。(也可以创建零标签的枚举类型,但在使用 ALTER TYPE 至少添加一个标签之前,这种类型不能用来保存值。)

范围类型

如第 8.17 节中所述,第三种形式的 CREATE TYPE 创建范围类型。

范围类型的 subtype 可以是任何带有关联 B-树操作符类的类型(该操作符类用于确定范围类型值的顺序)。通常使用子类型默认的 B-树操作符类来确定顺序;若要使用非默认操作符类,可用 subtype_opclass 指定其名称。如果子类型支持排序规则,而你希望在范围排序中使用非默认排序规则,可用 collation 选项指定所需排序规则。

可选的 canonical 函数必须接受一个正在定义的范围类型值作为参数,并返回同一类型的值。在适用时,它用于将范围值转换为规范形式。更多信息见第 8.17.8 节。创建 canonical 函数有些棘手,因为它必须在声明范围类型之前定义。为此,必须先创建一种 shell 类型,它除了名称和拥有者外没有任何属性,只是一个占位符类型。这可通过执行不带任何附加参数的命令 CREATE TYPE name 完成。然后就可以把该 shell 类型用作参数类型和结果类型来声明该函数,最后再用同一名称声明范围类型。这样会自动用有效的范围类型替换 shell 类型条目。

可选的 subtype_diff 函数必须接受两个 subtype 类型的值作为参数,并返回一个表示这两个给定值之差的 double precision 值。虽然这是可选的,但提供该函数可显著提高该范围类型列上 GiST 索引的效率。详见第 8.17.8 节。

基础类型

第四种形式的 CREATE TYPE 创建一种新的基础类型(标量类型)。要创建新的基础类型,你必须是超级用户。(这样限制是因为错误的类型定义可能使服务器陷入混乱,甚至导致其崩溃。)

这些参数可以按任意顺序出现,不必局限于上面展示的顺序,而且大多数都是可选的。在定义该类型之前,必须先注册两个或更多函数(使用 CREATE FUNCTION)。支持函数 input_function 和 output_function 是必需的;receive_function、send_function、type_modifier_input_function、type_modifier_output_function、analyze_function 则是可选的。通常这些函数必须用 C 或其他低级语言编写。

input_function 将类型的外部文本表示转换为该类型的操作符和函数所使用的内部表示。output_function 执行相反的转换。输入函数可以声明为接受一个 cstring 参数,或者接受三个参数,类型分别为 cstring、oid、integer。第一个参数是以 C 字符串表示的输入文本,第二个参数是该类型自身的 OID(数组类型例外,此时传入的是其元素类型的 OID),第三个参数是在已知情况下目标列的 typmod(未知则传入 -1)。输入函数必须返回该数据类型本身的值。通常输入函数应声明为 STRICT;否则,在读取 NULL 输入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在这种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝 NULL 输入的域输入函数。)输出函数必须声明为接受一个新数据类型参数,并且必须返回 cstring 类型。对于 NULL 值不会调用输出函数。

可选的 receive_function 把类型的外部二进制表示转换为内部表示。如果未提供此函数,该类型就不能参与二进制输入。外部二进制表示应选择为既能低成本转换为内部形式,又具有合理可移植性。(例如,标准整数数据类型把网络字节序用作外部二进制表示,而内部表示则使用机器的本地字节序。)receive_function 应进行充分检查以确保值有效。它可以声明为接受一个 internal 参数,或者接受三个参数,类型分别为 internal、oid、integer。第一个参数是指向保存已接收字节串的 StringInfo 缓冲区的指针;其余可选参数与文本输入函数相同。接收函数必须返回该数据类型本身的值。通常,接收函数应声明为 STRICT;否则,在读取 NULL 输入值时会以 NULL 作为第一个参数调用它。除非函数抛出错误,否则在这种情况下仍必须返回 NULL。(这种情况主要是为了支持可能需要拒绝 NULL 输入的域接收函数。)类似地,可选的 send_function 把内部表示转换为外部二进制表示。如果未提供此函数,该类型就不能参与二进制输出。发送函数必须声明为接受一个新数据类型参数,并且必须返回 bytea 类型。对于 NULL 值不会调用发送函数。

读到这里,你可能会问:既然新类型本身还没创建,输入和输出函数怎么能声明为返回或接受这个新类型呢?答案是,应先把该类型定义为一种 shell 类型,它除了名称和拥有者外没有任何属性,只是一个占位符类型。这可通过执行不带任何附加参数的命令 CREATE TYPE name 完成。然后就可以定义引用该 shell 类型的 C I/O 函数。最后,再用带完整定义的 CREATE TYPE 替换该 shell 条目,生成一个完整且有效的类型定义,此后新类型就能正常使用。

如果该类型支持修饰符,也就是附加在类型声明上的可选约束,例如 char(5) 或 numeric(30,2),就需要可选的 type_modifier_input_function 和 type_modifier_output_function。PostgreSQL 允许用户定义类型接受一个或多个简单常量或标识符作为修饰符。不过,这些信息必须能够打包成单个非负整数值,以便存储在系统目录中。声明的修饰符会以 cstring 数组的形式传递给 type_modifier_input_function。它必须检查这些值是否有效(若无效则抛出错误),若有效则返回一个非负 integer 值,该值将作为列的“typmod”存储。如果该类型没有 type_modifier_input_function,就会拒绝类型修饰符。type_modifier_output_function 则把内部整数 typmod 值转换回适合用户显示的正确形式。它必须返回一个 cstring 值,即精确追加到类型名后的字符串;例如,numeric 的该函数可能返回 (30,2)。允许省略 type_modifier_output_function;在这种情况下,默认显示格式只是把存储的 typmod 整数值放在圆括号中。

可选的 analyze_function 为该数据类型的列执行类型专用的统计信息收集。默认情况下,如果该类型有默认的 B-树操作符类,ANALYZE 将尝试使用该类型的“等于”和“小于”操作符收集统计信息。对于非标量类型,这种行为很可能不合适,因此可以通过指定自定义分析函数来覆盖。分析函数必须声明为接受一个 internal 参数并返回 boolean 结果。分析函数的详细 API 见 src/include/commands/vacuum.h。

虽然新类型内部表示的细节只有 I/O 函数以及你为该类型编写的其他函数才知道,但仍有若干内部表示属性必须向 PostgreSQL 声明。其中最重要的是 internallength。基础类型可以是定长的,此时 internallength 为正整数;也可以是变长的,此时将 internallength 设为 VARIABLE。(在内部,这通过把 typlen 设为 -1 表示。)所有变长类型的内部表示都必须以一个 4 字节整数开头,用来给出该类型该值的总长度。(注意,如第 69.2 节中所述,长度字段通常是经过编码的;直接访问它并不明智。)

可选标志 PASSEDBYVALUE 表示该数据类型的值按值传递,而不是按引用传递。按值传递的类型必须是定长的,且其内部表示不能大于 Datum 类型的大小(某些机器上为 4 字节,另一些为 8 字节)。

alignment 参数指定该数据类型所需的存储对齐方式。允许的值分别对应按 1、2、4 或 8 字节边界对齐。注意,变长类型的对齐至少必须为 4,因为它们的第一个组成部分必然是一个 int4。

storage 参数允许为变长数据类型选择存储策略。(定长类型只允许 plain。)plain 表示该类型数据始终行内存储且不压缩。extended 表示系统会先尝试压缩较长的数据值,如果仍然过长,就把该值移出主表行。external 允许把值移出主表,但系统不会尝试压缩它。main 允许压缩,但不鼓励把值移出主表。(采用这种存储策略的数据项在没有其他办法让一行适配时仍可能被移出主表,但与 extended 和 external 数据项相比,它们会被优先保留在主表中。)

如第 69.2 节和第 37.13.1 节所述,除 plain 之外所有 storage 值都意味着该数据类型的函数能够处理经过 TOAST 处理的值。具体指定哪一种其他值,只是决定可 TOAST 数据类型列的默认 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 子句携带排序规则信息。是否实际使用这些排序规则信息取决于操作该类型的函数实现;仅仅把类型标记为支持排序规则并不会自动实现这一点。

数组类型

每当创建用户定义类型时,PostgreSQL 都会自动创建一个关联的数组类型,其名称由元素类型名前加一个下划线组成;必要时还会截断,以保持其长度小于 NAMEDATALEN 字节。(如果这样生成的名称与现有类型名冲突,就会重复这一过程,直到找到不冲突的名称。)这种隐式创建的数组类型是变长的,并使用内置输入/输出函数 array_in 和 array_out。该数组类型会跟踪其元素类型的拥有者或模式的任何变化,并在元素类型被删除时一并删除。

如果系统会自动创建正确的数组类型,你可能会合理地问,为什么还需要 ELEMENT 选项。ELEMENT 唯一有用的情况是:你正在创建一种定长类型,而它在内部恰好是若干相同元素组成的数组,并且除了为整个类型提供的操作之外,你还希望允许通过下标直接访问这些元素。例如,类型 point 在内部就表示为两个浮点数,可以用 point[0] 和 point[1] 访问。注意,这种机制只适用于内部形式恰好是一串相同定长字段的定长类型。可用下标访问的变长类型必须采用 array_in 和 array_out 使用的通用内部表示。由于历史原因(也就是说这显然不对,但现在改已经太晚了),定长数组类型的下标从零开始,而变长数组则从一开始。

参数

name

要创建的类型名称(可选地带有模式限定)。

attribute_name

复合类型的一个属性(列)的名称。

data_type

将成为复合类型一列的现有数据类型名称。

collation

要与复合类型的某一列或范围类型关联的现有排序规则名称。

label

表示枚举类型某个值所关联文本标签的字符串字面量。

subtype

范围类型所表示范围的元素类型名称。

subtype_operator_class

子类型的 B-树操作符类名称。

canonical_function

范围类型规范化函数的名称。

subtype_diff_function

子类型差分函数的名称。

input_function

将数据从类型的外部文本形式转换为内部形式的函数名。

output_function

将数据从类型的内部形式转换为外部文本形式的函数名。

receive_function

将数据从类型的外部二进制形式转换成内部形式的函数名。

send_function

将数据从类型的内部形式转换为外部二进制形式的函数名。

type_modifier_input_function

将类型的修饰符数组转换为内部形式的函数名。

type_modifier_output_function

将类型修饰符的内部形式转换为外部文本形式的函数名。

analyze_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.64。你也可以选择其他 ASCII 字符来创建自定义类别。

preferred

若该类型是其类型类别中的首选类型,则为真,否则为假。默认值为假。在现有类型类别中创建新的首选类型时要格外小心,因为这可能导致出人意料的行为变化。

default

该数据类型的默认值。若省略,默认值为 null。

element

正在创建的类型是数组;该参数指定数组元素类型。

delimiter

在由该类型构成的数组中各值之间使用的分隔符字符。

collatable

如果该类型的操作可以使用排序规则信息,则为真。默认为假。

注解

由于数据类型一旦创建,其使用方式就不再受限制,因此创建基础类型或范围类型,相当于对类型定义中提到的那些函数授予公共执行权限。这对适合用于类型定义的那类函数来说通常不是问题。但如果设计一种类型时,需要在把它转换为外部形式或从外部形式转换回来时使用“秘密”信息,就应当三思。

在 PostgreSQL 8.3 之前,自动生成的数组类型名称总是恰好等于元素类型名称前加一个下划线字符(_)。(因此,类型名称的长度限制比其他名称少一个字符。)虽然现在通常仍是这样,但在名称达到最大长度或与以下划线开头的用户类型名冲突时,数组类型名称可能与此不同。因此,依赖这一约定编写代码的做法已经弃用。请改用 pg_type.typarray 来定位与给定类型关联的数组类型。

建议避免使用以下划线开头的类型名和表名。虽然服务器会改变生成的数组类型名称以避免与用户给定的名称冲突,但仍然存在混淆风险,特别是对旧客户端软件而言,它们可能会假定以下划线开头的类型名总是表示数组。

在 PostgreSQL 8.2 之前,不存在 shell 类型创建语法 CREATE TYPE name。创建新基础类型的做法是先创建它的输入函数。在这种做法下,PostgreSQL 会首先把新数据类型名视为输入函数的返回类型。此时 shell 类型会被隐式创建,然后就可以在其余 I/O 函数的定义中引用它。这种做法仍然有效,但已弃用,并且可能在未来某个版本中被禁止。另外,为避免由于函数定义中的简单拼写错误而意外使系统目录充满 shell 类型,只有在输入函数用 C 编写时,才会以这种方式创建 shell 类型。

在 PostgreSQL 7.3 以前的版本中,通常完全不创建 shell 类型,而是将函数中对类型名的前向引用替换为占位伪类型 opaque。在 7.3 以前,cstring 参数和结果也必须声明为 opaque。为了支持载入旧转储文件,CREATE TYPE 会接受使用 opaque 声明的 I/O 函数,但会发出通知,并修改函数声明以使用正确的类型。

示例

这个示例创建一种复合类型,并在函数定义中使用它:

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
);

更多示例(包括配套的输入和输出函数)请见第 37.13 节。

兼容性

第一种 CREATE TYPE 形式,即创建复合类型的形式,符合 SQL 标准。其他形式都是 PostgreSQL 扩展。SQL 标准中的 CREATE TYPE 语句还定义了 PostgreSQL 尚未实现的其他形式。

支持创建零属性的复合类型,是 PostgreSQL 对标准的一种特有背离(类似于 CREATE TABLE 中的同类情况)。

报告文档问题

阅读 上游文档. 反馈更正前请先核对 当前版本手册.