Postgresql 中文操作指南

CREATE TYPE

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

Synopsis

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 ]
    [ , MULTIRANGE_TYPE_NAME = multirange_type_name ]
)

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 ]
    [ , 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 TYPE name

Description

CREATE TYPE 注册一个新数据类型,供当前数据库使用。定义类型者成为该类型的所有者。

如果给出了模式名称,则将在指定的模式中创建类型。否则,将在当前模式中创建它。类型名称必须与同一模式中任何现有类型或域的名称不同。(因为表具有关联的数据类型,因此类型名称也必须与同一模式中任何现有表的名称不同。)

共有五种形式的 CREATE TYPE ,如上面语法概要中所示。它们分别创建一个 composite type ,一个 enum type ,一个 range type ,一个 base type 或一个 shell type 。前四个将在下面依次讨论。外壳类型只是一个占位符,用于稍后定义的类型;它是通过发行 CREATE TYPE 来创建的,除了类型名称之外没有其他参数。在创建范围类型和基本类型时,需要外壳类型作为前向引用,如在那些部分中所讨论的。

Composite Types

第一种形式的 CREATE TYPE 创建一个复合类型。复合类型由一组属性名称和数据类型指定。如果其数据类型是可以比较的,则也可以指定属性的校对规则。复合类型本质上与表的行类型相同,但使用 CREATE TYPE 可以避免在只想定义类型时创建实际表。独立的复合类型很有用,例如,作为函数的参数或返回类型。

为了能够创建复合类型,您必须对所有属性类型拥有 USAGE 权限。

Enumerated Types

第二种形式的 CREATE TYPE 创建一个枚举 (enum) 类型,如 Section 8.7 中所述。枚举类型采用一组带引号的标签,每个标签的长度必须小于 NAMEDATALEN 字节(在标准 PostgreSQL 构建中为 64 字节)。(可以创建一个没有标签的枚举类型,但在使用 ALTER TYPE 添加至少一个标签之前,无法使用此类类型来保存值。)

Range Types

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

范围类型的 subtype 可以是任何具有关联 b 树运算符类的类型(用于确定范围类型值的排序)。通常使用子类型的默认 b 树运算符类来确定排序;要使用非默认运算符类,请使用 subtype_opclass 指定其名称。如果子类型是可以比较的,并且您希望在范围的排序中使用非默认校对规则,请使用 collation 选项指定所需的校对规则。

可选的 canonical 函数必须采用一个正在定义的范围类型作为参数,并返回同类型的返回值。这用于在适用时将范围值转换为标准形式。有关更多信息,请参见 Section 8.17.8 。创建 canonical 函数有点棘手,因为它必须在声明范围类型之前进行定义。为此,您必须首先创建一个外壳类型,这是一种占位符类型,除了名称和所有者之外没有其他属性。这是通过发出命令 CREATE TYPE _name_ 完成的,无需其他参数。然后可以使用外壳类型作为参数和结果来声明函数,最后可以使用相同的名称来声明范围类型。这会自动将外壳类型条目替换为有效的范围类型。

可选的 subtype_diff 函数必须以 subtype 类型为参数,并返回一个 double precision 值,表示在给予值的差值。这是可选的。提供它允许更大的 GiST 索引效率在范围内类型列上。更多信息,请参见 Section 8.17.8

可选的 multirange_type_name 参数指定相应的多范围类型的名称。如果未指定,此名称将按如下方式自动选择。如果范围类型名称包含子字符串 range ,那么该多范围类型名称将通过用 multirange 替换范围类型名称中的 range 子字符串而形成。否则,则通过向范围类型名称追加一个 _multirange 后缀而形成多范围类型名称。

Base Types

CREATE TYPE 的第四种形式创建一个新的基本类型(标量类型)。要创建一个新的基本类型,您必须是超级用户。(做出此限制是因为一个错误的类型定义会使服务器产生混乱甚至崩溃。)

参数可以按任意顺序出现,而不仅仅是在上文中的图示中,而且大多数的参数是可选的。在定义类型之前,你必须注册两个或更多函数(使用 CREATE FUNCTION )。需要支持函数 input_functionoutput_function ,但是函数 receive_functionsend_functiontype_modifier_input_functiontype_modifier_output_functionanalyze_functionsubscript_function 是可选的。通常,这些函数必须用 C 或其他低等级语言编码。

input_function 会将该类型的外部文本表示形式转换成内部表示形式,用于该类型中定义的操作符和函数。 output_function 执行相反的transformation。输入函数可以被声明为采取一个 cstring 类型参数,或采取三个 cstringoidinteger 类型的参数。第一个参数是作为 C 字符串输入文本,第二个参数是该类型的 OID(数组类型除外,数组类型会收到其元素类型的 OID),第三个参数是已知目标列的 typmod (如果未知,将传递 -1)。输入函数必须返回其自身数据类型的值。通常,输入函数应该被声明为 STRICT;如果不是,在读取 NULL 输入值时,它会被调用,第一个参数为 NULL。在这种情况下,函数必须仍然返回 NULL,除非它引发错误。(这一情况主要用于支持可能会需要拒绝 NULL 输入的域输入函数。)输出函数必须声明为采取新数据类型的一个参数。输出函数必须返回 cstring 类型。对于 NULL 值,不会调用输出函数。

可选的 receive_function 会将该类型的外部二进制表示形式转换成内部表示形式。如果没有提供此函数,该类型则无法参与二进制输入。应选择二进制表示格式,以便在转换为内部格式的同时开销尽可能少,同时具有合理的移植性。(例如,标准整数数据类型使用网络字节序作为外部二进制表示形式,而内部表示形式是机器的原生字节序。)接收函数应该执行适当的检查以确保该值有效。接收函数可以被声明为采取一个 internal 类型参数,或采取三个 internaloidinteger 类型的参数。第一个参数是 StringInfo 缓冲区的指针,该缓冲区包含接收到的字节字符串;可选参数与文本输入函数相同。接收函数必须返回其自身数据类型的值。通常,接收函数应该被声明为 STRICT;如果不是,在读取 NULL 输入值时,它会被调用,第一个参数为 NULL。在这种情况下,函数必须仍然返回 NULL,除非它引发错误。(这一情况主要用于支持可能会需要拒绝 NULL 输入的域接收函数。)类似地,可选的 send_function 会将内部表示形式转换为外部二进制表示形式。如果没有提供此函数,该类型则无法参与二进制输出。发送函数必须声明为采取新数据类型的一个参数。发送函数必须返回 bytea 类型。对于 NULL 值,不会调用发送函数。

现在,你可能会好奇在创建新类型之前必须创建输入和输出函数,那么输入和输出函数如何被声明为具有新类型的结果或参数。答案是该类型应首先被定义为 shell type ,这是一种占位符类型,除了一个名称和一个所有者,没有其他属性。通过发出 CREATE TYPE _name_ 命令(无其他参数)来执行此操作。然后可以引用 shell 类型来定义 C I/O 函数。最后,具有完整定义的 CREATE TYPE 会使用一个完整且有效类型定义替换 shell 项,此后,新类型可以正常使用。

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

可选的 analyze_function 为该数据类型列执行特定于类型的统计信息收集。默认情况下,如果为该类型有一个默认 b 树操作符类, ANALYZE 会尝试使用该类型的 “equals” 和 “less-than” 操作符收集统计信息。对于非标量类型,此行为可能不适合,因此可以通过指定一个自定义分析函数来覆盖它。分析函数必须被声明为采取一个 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 中的更简单的代码,也可能会有用。有关其他信息,请参阅下面的 Array Types

虽然新类型的内部表示的详细信息只为 I/O 函数及其他用于处理该类型的函数所知,但是必须向 PostgreSQL 声明内部表示的若干属性。其中最重要的是 internallength 。基本数据类型可以是固定长度的(在这种情况下 internallength 是一个正整数),或者可变长度的,由将 internallength 设置为 VARIABLE 指明。(在内部,这是通过将 typlen 设置为 -1 来表示的。)所有可变长度类型的内部表示都必须从一个给出该类型的该值的总长度的 4 字节 integer 开始。(请注意长度字段经常被编码,如 Section 73.2 中所述;直接访问它不明智。)

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

alignment 参数指定了数据类型所需的存储对齐方式。允许的值与在 1、2、4 或 8 字节边界对齐相等。请注意,可变长度类型必须至少有 4 字节对齐,因为它们必然包含 int4 作为其第一个组件。

storage 参数允许为可变长度数据类型选择存储策略。(对于固定长度类型,只允许 plain 。) plain 指定该类型的数据将始终以单行形式存储,并且不进行压缩。 extended 指定系统将首先尝试压缩一个长数据值,如果它仍然太长,则会将该值移出主表行。 external 允许将该值移出主表,但是系统不会尝试将其压缩。 main 允许压缩,但强烈建议不要将该值移出主表。(如果别无他法,将一行内容放进去,则具有此存储策略的数据项仍然可能会被移出主表,但是,它们将在主表中优先保留,而不是 extendedexternal 项。)

plain 之外的所有 storage 值都暗示数据类型可以处理已被 toasted 的值,如 Section 73.2Section 38.13.1 所描述的。给出的具体其他值仅仅决定了可 toast 数据类型列的默认 TOAST 存储策略;用户可以使用 ALTER TABLE SET STORAGE 为各个列选择其他策略。

like_type 参数提供了一个指定数据类型基本表示属性的替代方法:从某个现有类型复制它们。 internallengthpassedbyvaluealignmentstorage 的值从所命名的类型复制。(虽然通常不值得这么做,但有可能通过使用 LIKE 子句指定这些值来覆盖其中一些值。)以这种方式指定表示形式在低等级实现新类型以某种方式 “背向” 某个现有类型时尤为有用。

categorypreferred 参数可用于帮助控制在模棱两可的情况下应用哪种隐式转换。每个数据类型都属于以单个 ASCII 字符命名的类别,并且每个类型在其类别中都是“首选”或非首选。当此规则有助于解析重载函数或运算符时,解析器更偏好转换为首选类型(但仅从同一类别中的其他类型转换)。有关更多详细信息,请参见 Chapter 10 。对于没有到或从任何其他类型的隐式转换的类型,将这些设置保留为默认值就足够了。但是,对于具有一组隐式转换的相关类型,经常将它们全部标记为属于某个类别,并在类别中将一个或两个“最一般的”类型选为首选。在向现有内置类别(例如数值或字符串类型)添加用户定义类型时, category 参数特别有用。然而,也可以创建全新的完全用户定义类型类别。选择除大写字母以外的任何 ASCII 字符来命名此类类别。

如果用户希望数据类型列默认为非空值,则可以指定一个默认值。使用 DEFAULT 关键字指定默认值。(此类默认值可被附加到特定列的显式 DEFAULT 子句覆盖。)

要表示类型为定长数组类型,请使用 ELEMENT 关键字指定数组元素的类型。例如,要定义一个 4 字节整数数组 ( int4 ),请指定 ELEMENT = int4 。有关更多详细信息,请参阅下面的 Array Types

为了指示在此类数组的外部表示中各值之间使用的分隔符,可以将 delimiter 设置为特定的字符。默认分隔符为逗号 ( , )。请注意,分隔符与数组元素类型关联,而不是与数组类型本身关联。

如果可选的布尔参数 collatable 为真,那么该类型的列定义和表达式可以通过使用 COLLATE 子句来传递排序信息。实际利用排序信息是操作此类型的函数的实现决定的;这不会仅仅通过标记类型为可排序而自动发生。

Array Types

每当创建用户定义类型时,PostgreSQL 都会自动创建一个关联的数组类型,其名称由元素类型的名称前加上一个下划线组成,并在必要时截断以保持其长度小于 NAMEDATALEN 字节。(如果如此生成的名称与现有类型名称冲突,那么该进程会重复,直到找到不产生冲突的名称为止。)此隐式创建的数组类型是可变长度的,并使用内置的输入和输出函数 array_inarray_out 。此外,对于诸如 ARRAY[] 之类的针对用户定义类型的构造,系统会使用此类型。数组类型会跟踪其元素类型的拥有者或架构中的任何更改,并且会随元素类型的删除而删除。

如果你提出合理的疑问,即如果系统自动生成正确的数组类型,那么为什么会有 ELEMENT 选项,这是合理的。使用 ELEMENT 有用的主要情况是,当你创建一个固定长度类型时,该类型在内部实际上是一个包含多个相同内容的数组,并且你想允许通过下标访问这些内容,除了那些你计划为该类型作为一个整体提供的操作之外。例如,类型 point 表示为只有两个浮点数,可以使用 point[0]point[1] 访问这些浮点数。请注意,此工具仅适用于其内部形式恰好是多个相同固定长度字段序列的固定长度类型。由于历史原因(即,这显然是错误的,但现在修改它已经为时已晚了),固定长度数组类型从零开始而不是从一(如可变长度数组)开始下标。

指定 SUBSCRIPT 选项允许数据类型被下标,即使系统不是将它当作数组类型对待。针对固定长度数组恰好实现的刚刚描述的行为实际上由 SUBSCRIPT 处理程序函数 raw_array_subscript_handler 实现,如果你为固定长度类型指定 ELEMENT 而没有写 SUBSCRIPT ,该函数会自动使用。

指定自定义 SUBSCRIPT 函数时,除非 SUBSCRIPT 处理程序函数需要查阅 typelem 以找出要返回哪些内容,否则不必指定 ELEMENT 。请注意,指定 ELEMENT 会导致系统认定新类型包含元素类型或在物理上依赖于元素类型;因此,例如,如果存在任何相关类型的列,则不允许更改元素类型的属性。

Parameters

  • 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

    • 数据类型的存储对齐要求。如果指定,则必须为 charint2int4double ;默认值为 int4

  • storage

    • 数据类型的存储策略。如果指定,则必须为 plainexternalextendedmain ;默认值为 plain

  • like_type

    • 现有数据类型的名称,新类型将具有与之相同的表示形式。 internallengthpassedbyvaluealignmentstorage 的值将从该类型复制,除非在此 CREATE TYPE 命令的其他位置通过明确指定进行覆盖。

  • category

    • 此类型的类别代码(单个 ASCII 字符)。对于“用户定义类型”,默认值为 'U' 。可以在 Table 53.65 中找到其他标准类别代码。您还可以选择其他 ASCII 字符来创建自定义类别。

  • preferred

    • 如果该类型在其类型类别中为首选类型,则为真,否则为假。默认值为 false。在现有类型类别中创建新的首选类型时务必小心,因为这可能会导致行为发生令人惊讶的变化。

  • default

    • 数据类型的默认值。如果省略此项,则默认值为 null。

  • element

    • 创建的类型是一个数组;这指定了数组元素的类型。

  • delimiter

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

  • collatable

    • 如果此类型的操作可以使用整理信息,则为真。默认值为 false。

Notes

由于在创建数据类型后不再对使用它有任何限制,因此创建基本类型或范围类型等同于授予对类型定义中提到的函数的公共执行权限。这通常不是类型定义中可用的函数类型的问题。但是,在设计类型方式时,您可能需要三思而后行,而考虑在将其转换为外部形式或从外部形式转换为外部形式时可能需要使用“机密”信息。

在 PostgreSQL 8.3 版本之前,生成的数组类型的名称始终都是元素类型的名称,在其前面加上一个下划线字符 ( _ )。(因此,类型名称的长度被限制为比其他名称少一个字符。)尽管通常仍然如此,但遇到长度最长的名称或与以下划线开头的用户类型名称发生冲突时,数组类型名称可能有所不同。因此,编写依赖于此约定的代码已弃用。相反,请使用 pg_type . typarray 来查找与给定类型关联的数组类型。

最好避免使用以下划线开头的类型和表名称。虽然服务器会更改生成的数组类型名称以避免与用户给定名称发生冲突,但仍然存在混淆的风险,尤其是对于可能假定以下划线开头的类型名称始终表示数组的旧客户端软件。

在 PostgreSQL 8.2 版本之前,没有外壳类型创建语法 CREATE TYPE _name_ 。创建新基本类型的方法是首先创建其输入函数。在此方法中,PostgreSQL 首先将新数据类型的名称视为输入函数的返回类型。外壳类型在此情况下被隐式创建,然后可以在其余 I/O 函数的定义中引用它。此方法仍然有效,但已弃用,并且在某些未来版本中可能被禁止。此外,为了避免由于函数定义中简单的错别字而意外使目录杂乱不堪,外壳类型仅当输入函数是用 C 编写的时才会以这种方式创建。

在 PostgreSQL 版本 16 及更高版本中,希望基类型输入函数使用新 errsave() / ereturn() 机制返回“软”错误,而不是像以前版本中那样抛出 ereport() 异常。请参阅 src/backend/utils/fmgr/README 以了解更多信息。

Examples

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

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

这样,可以通过下标访问框值的分量编号。否则,该类型的行为与以前相同。

此示例创建一个大对象类型并在表定义中使用它:

CREATE TYPE bigobj (
    INPUT = lo_filein, OUTPUT = lo_fileout,
    INTERNALLENGTH = VARIABLE
);
CREATE TABLE big_objs (
    id integer,
    obj bigobj
);

更多示例(包括合适的输入和输出函数)位于 Section 38.13 中。

Compatibility

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

使用零个属性创建复合类型的能力是 PostgreSQL 对标准的特定偏差(类似于 CREATE TABLE 中的同类情况)。