8.16 Composite Types 复合类型
复合类型表示行或记录的结构;它本质上只是字段名及其数据类型的列表。PostgreSQL允许以许多与简单类型相同的方式使用复合类型。例如,表的列可以声明为组合类型。
8.16.1. 复合类型的声明
下面是定义复合类型的两个简单示例:
CREATETYPE complex AS(
r doubleprecision,
i doubleprecision);CREATETYPE inventory_item AS(
name text,
supplier_id integer,
price numeric);
它的语法与CREATE TABLE类似,只是只能指定字段名和类型;目前不能包含任何约束(如NOT NULL)。注意AS关键字是必要的;如果没有它,系统将认为是另一种CREATE TYPE命令,并且您将得到奇怪的语法错误。
定义了类型之后,我们可以使用它们来创建表:
CREATETABLE on_hand (
item inventory_item,
count integer);
INSERTINTO on_hand VALUES(ROW('fuzzy dice',42,1.99),1000);
或功能(函数):
CREATEFUNCTION price_extension(inventory_item,integer)RETURNSnumericAS'SELECT $1.price * $2'LANGUAGESQL;
SELECT price_extension(item,10)FROM on_hand;
无论何时创建表,都会自动创建与表同名的复合类型,以表示表的行类型。例如,如果我们说:
CREATETABLE inventory_item (
name text,
supplier_id integerREFERENCES suppliers,
price numericCHECK(price >0));
然后,上面所示的相同的inventory_item复合类型将作为byprod t产生,并且可以像上面一样使用。但是,请注意当前实现的一个重要限制:由于没有约束与组合类型相关联,表定义中显示的约束不适用于表外的组合类型值。(要解决这个问题,可以在复合类型上创建一个域,并将所需的约束应用为域的CHECK约束。)
8.16.2. 构建组合值
若要将复合值写成文字常量,请将字段值括在圆括号内,并用逗号分隔。您可以在任何字段值周围加上双引号,如果它包含逗号或圆括号,则必须这样做。(详情见下文。)因此,复合常量的一般格式如下:
( val1 , val2 , ... )
一个例子是
("fuzzy dice",42,1.99)
它将是上面定义的inventory_item类型的有效值。要使一个字段为NULL,在它在列表中的位置上不写入任何字符。例如,这个常量指定了一个NULL第三个字段:
("fuzzy dice",42,)
如果你想要一个空字符串而不是NULL,写双引号:
("",42,)
这里第一个字段是一个非NULL空字符串,第三个字段是NULL。
(这些常量实际上只是第4.2.7节中讨论的泛型类型常量的特殊情况。常量最初被视为字符串,并传递给复合类型输入con版本例程。可能需要一个显式的类型规范来说明将常量转换为哪种类型。)
ROW表达式语法还可以用于构造复合值。在大多数情况下,这比字符串-文字语法使用起来要简单得多,因为您不必担心多层引用。我们已经在上面使用了这个方法:
ROW('fuzzy dice',42,1.99)ROW('',42,NULL)
ROW关键字实际上是可选的,只要你在表达式中有多个字段,所以这些可以简化为:
('fuzzy dice',42,1.99)('',42,NULL)
ROW表达式语法将在4.2.13节中详细讨论。
8.16.3. 访问复合类型
要访问复合列的字段,需要写入一个点和字段名,就像从表名中选择字段一样。事实上,这与从表名中进行选择非常相似,您经常不得不使用圆括号来防止解析器混淆。例如,你可以尝试从on_hand示例表中选择一些子字段,如下所示:
SELECT item.name FROM on_hand WHERE item.price >9.99;
这将不起作用,因为根据SQL语法规则,name项是一个表名,而不是on_hand的列名。你必须这样写:
SELECT(item).name FROM on_hand WHERE(item).price >9.99;
或者如果你也需要使用表名(例如在多表查询中),就像这样:
SELECT(on_hand.item).name FROM on_hand WHERE(on_hand.item).price >9.99;
现在括号内的对象被正确地解释为对item列的引用,然后可以从中选择子字段。
每当从复合值中选择字段时,都会出现类似的语法问题。例如,要从返回复合值的函数的结果中只选择一个字段,你需要编写如下代码:
SELECT(my_func(...)).field FROM...
如果没有额外的括号,这将生成一个语法错误。
特殊字段名*表示“所有字段”,如第8.16.5节所述。
8.16.4. 修改复合类型
下面是一些插入和更新组合列的正确语法示例。首先,插入或更新整个列.
INSERTINTO mytab (complex_col)VALUES((1.1,2.2));UPDATE mytab SET complex_col =ROW(1.1,2.2)WHERE...;
第一个示例省略了ROW,第二个示例使用它;两种方法都可以。
我们可以更新复合列的单个子字段:
UPDATE mytab SET complex_col.r =(complex_col).r +1WHERE...;
请注意,我们不需要(实际上也不能)在SET之后的列名ap pearing周围加上括号,但当引用表达式sion中等号右侧的同一列时,我们确实需要括号。
我们也可以指定子字段作为INSERT的目标:
INSERTINTO mytab (complex_col.r, complex_col.i)VALUES(1.1,2.2);
如果我们没有为列的所有子字段提供值,其余子字段将被填充为空值。
8.16.5. 在查询中使用复合类型
查询中有各种与复合类型相关的特殊语法规则和行为。这些规则提供了有用的快捷方式,但如果不了解它们背后的逻辑,可能会令人困惑。
在PostgreSQL中,查询中对表名(或别名)的引用实际上是对表当前行的复合值的引用。例如,如果我们有一个表inventory_item,如上面所示,我们可以这样写:
SELECT c FROM inventory_item c;
这个查询产生了一个组合值列,所以我们可能会得到如下的输出:
c(“fuzzy dice”,42,1.99)
但是请注意,简单名称在表名之前匹配列名,所以这个示例只适用于查询的表中没有名为c的列。
普通的限定列名语法table_name。Column_name可以理解为将字段选择应用于表当前行的复合值。(出于效率考虑,它实际上并不是这样实现的。)
当我们写作时
SELECT c.*FROM inventory_item c;
然后,根据SQL标准,我们应该将表的内容展开为单独的列:
namesupplier_idpricefuzzy dice421.99
就好像这个查询是
SELECT c.name, c.supplier_id, c.price FROM inventory_item c;
PostgreSQL将这种展开行为应用于任何组合值表达式,尽管如上所示,当.*不是一个简单的表名时,您需要在应用的值周围写圆括号。例如,如果myfunc()是一个返回包含列a、b和c的复合类型的函数,那么这两个查询有相同的结果:
SELECT(myfunc(x)).*FROM some_table;SELECT(myfunc(x)).a,(myfunc(x)).b,(myfunc(x)).c FROM some_table;
PostgreSQL通过实际将第一种形式转换为第二种形式来处理列展开。因此,在本例中,myfunc()将被任意一种语法每行调用三次。如果它是一个昂贵的函数,你可能希望避免它,你可以通过查询这样做:
SELECT m.*FROM some_table, LATERAL myfunc(x)AS m;
将函数放置在LATERAL FROM项中可以防止它在每行被调用多次。m.*仍然展开为m.a, m.b, m.c,但现在这些变量只是对FROM项输出的引用。(LATERAL关键字在这里是可选的,但我们显示它是为了阐明函数是从some_table中获取x。)
composite_value。当它出现在SELECT输出列表、INSERT/UPDATE/DELETE中的RETURNING列表、VALUES子句或行构造函数的顶层时,*语法会导致这种列展开。在所有其他上下文中(包括嵌套在这些结构体中),将.*附加到复合值并不会改变该值,因为它意味着“所有列”,因此将再次生成相同的复合值。例如,如果somefunc()接受一个以com#为值的参数,这些查询是相同的:
SELECT somefunc(c.*)FROM inventory_item c;SELECT somefunc(c)FROM inventory_item c;
在这两种情况下,inventory_item的当前行都作为一个复合值为的参数传递给函数。尽管.*在这种情况下什么也不做,但使用它是一种很好的风格,因为它清楚地表明要使用复合值。特别地,解析器会认为c.中的c指的是表名或别名,而不是列名,这样就不会有歧义;而如果没有。,则不清楚c表示表名还是列名,事实上,如果有一个名为c的列,则首选列名解释
另一个例子说明了这些概念,所有这些查询都意味着同样的事情:
SELECT*FROM inventory_item c ORDERBY c;SELECT*FROM inventory_item c ORDERBY c.*;SELECT*FROM inventory_item c ORDERBYROW(c.*);
所有这些ORDER BY子句都指定了行的复合值,从而根据第9.24.6节中描述的规则对行进行排序。但是,如果inventory_item包含一个名为c的列,那么第一种情况将与其他情况不同,因为它意味着只根据该列进行排序。给定前面显示的列名,这些查询也等价于上面的查询:
SELECT*FROM inventory_item c ORDERBYROW(c.name, c.supplier_id, c.price);SELECT*FROM inventory_item c ORDERBY(c.name, c.supplier_id, c.price);
(最后一种情况使用了省略关键字row的行构造函数。)
与组合值相关的另一个特殊语法行为是,我们可以使用函数符号提取组合值的字段。解释这一点的简单方法是,表示法字段(表)和表。场是可互换的。例如,这些查询是等价的:
SELECT c.name FROM inventory_item c WHERE c.price >1000;SELECT name(c)FROM inventory_item c WHERE price(c)>1000;
此外,如果我们有一个接受复合类型的单个参数的函数,我们可以用任何一种表示法调用它。这些查询都是等价的:
SELECT somefunc(c)FROM inventory_item c;SELECT somefunc(c.*)FROM inventory_item c;SELECT c.somefunc FROM inventory_item c;
函数式表示法和字段表示法之间的这种等价性使得在组合类型上使用函数来实现“计算字段”成为可能。使用上面最后一个查询的应用程序不需要直接知道somefunc不是表的真实列。
由于这种行为,给一个接受单个复合类型参数的函数与该复合类型的任何字段同名是不明智的。如果存在歧义,则如果使用字段名语法,则选择字段名解释,而如果使用函数调用语法,则选择函数。然而,11之前的PostgreSQL版本总是选择字段名解释,除非调用的语法要求它是一个函数调用。在旧版本中强制函数解释的一种方法是对函数名进行模式限定,即编写schema.func(compositevalue)。
8.16.6. 复合类型输入和输出语法
复合值的外部文本表示形式包括根据各个字段类型的I/O转换规则解释的项,以及指示复合结构的修饰。修饰由整个值周围的圆括号((和))和相邻项之间的逗号(,)组成。圆括号外的空格将被忽略,但在圆括号内,它被认为是字段值的一部分,根据字段数据类型的输入转换规则,它可能重要,也可能不重要。例如,在:
( 42)
如果字段类型是整数,空格将被忽略,但如果字段类型是文本,则不会。
如前所述,在编写复合值时,可以在任何indi vidual字段值周围写入双引号。如果字段值会混淆组合值解析器,则必须这样做。特别是,包含括号、逗号、双引号或反斜杠的字段必须使用双引号。若要在带引号的复合字段值中放入双引号或反斜杠,请在其前面加上反斜杠。(同样,双引号字段值中的一对双引号表示一个双引号字符,类似于SQL字面值字符串中的单引号规则。)或者,您可以避免引用并使用反斜杠转义来保护所有数据字符,否则这些数据字符将被当作复合语法。
一个完全空的字段值(逗号或括号之间没有字符)表示NULL。要写入一个空字符串而不是NULL值,请写入""。
如果字段值是空字符串或包含圆括号、逗号、双引号、反斜杠或空格,复合输出例程将在字段值周围加上双引号。(在空白区域这样做不是必需的,但有助于可读性。)嵌入在字段值中的双引号和反斜杠将被加倍。
请记住,您在SQL命令中编写的内容将首先被解释为字符串字面量,然后被解释为组合。这将使您需要的反斜杠的数量增加一倍(假设使用转义字符串语法)。例如,要在复合值中插入一个包含双引号和反斜杠的文本字段,你需要这样写:
INSERT ... VALUES ('("\"\\")');
字符串字面值处理程序删除了一级反斜杠,因此到达复合值解析器的内容看起来像("\"\\")。反过来,输入到文本数据类型的输入例程的字符串变成“\”。(如果我们使用的数据类型的输入例程也专门处理反斜杠,例如bytea,我们可能需要多达8个反斜杠才能在存储的复合字段中获得一个反斜杠。)可以使用美元报价(参见第4.2.4节)来避免双反斜杠的需要。
在SQL命令中编写复合值时,ROW构造函数语法通常比复合文字语法更容易使用。在ROW中,单个字段值的书写方式与它们不是组合成员时的书写方式相同。
版权归原作者 自己的九又四分之三站台 所有, 如有侵权,请联系我们删除。