PostgreSQL 9.4文档 第4章 SQL语法

时间:2021-09-02 05:41:59

本章介绍SQL的语法。它是理解后面章节的基础,它们详细描述了SQL如何用于定义和修改数据。

对于已经熟悉SQL的用户,同样应该仔细阅读本章,因为它包含了各个SQL数据库中实现方式不同的一些规则和概念,这些是PostgreSQL特有的属性。

4.1. 词法结构

SQL输入由一系列命令组成。命令由一系列符号组成,以分号(“;”)结束。输入流的结束也会结束一个命令。每个命令能够使用的符号不尽相同。

符号可以是关键字、标识符、引用标识符、字符串(或常量)或者特殊字符符号。符号通常用空白(空格、制表符、换行)进行分隔,但是如果不存在歧义时可以不需要分隔(通常只有特殊字符紧挨着其他符号类型的情况)。

例如,以下是一些有效(语法上)的SQL输入:

SELECT * FROM MY_TABLE;

UPDATE MY_TABLE SET A = 5;

INSERT INTO MY_TABLE VALUES (3, 'hi there');

以上包含3个命令,每行一个(每行可以包行多个命令,命令也可以跨多个行)。

除此之外,SQL输入中可以使用注释。注释不是符号,它们实际上等效于空白。

SQL语法中,对于哪些符号表示命令,以及哪些符号是操作数或参数并没有一致的规定。开始的几个符号通常是命令名,因此在上面的示例中,我们通常说成一个SELECT命令,一个UPDATE命令和一个INSERT命令。但是对于UPDATE命令,总是需要一个SET符号,而对于INSERT需要一个VALUES。每个命令的准确的语法规则可以参考第VI部分。

4.1.1. 标识符与关键字

类似上例中的SELECT、UPDATE或者VALUES的符号称为关键字,它们是SQL语言中具有固定意义的单词。MY_TABLE和A是标识符。它们标识了命令中使用的表、列或者其他数据库对象的名称。因此它们有时候被简称为“名称”。关键字和标识符的词法结构相同,意味着如果不了解语言的话,无法知道一个符号是标识符还是关键字。关于关键字的完整列表可以参考附件C。

SQL标识符和关键字必须以一个字母(a-z,但是也可以是带变音符的字母和非拉丁字母)或者下划线(_)开头。后面的字符可以是字母、下划线、数字(0-9)或者美元符号($)。注意,SQL标准中不允许标识符使用美元符合,因此在PostgreSQL中使用会降低可移植性。SQL标准不会定义包含数字或者以下划线开始或者结束的关键字,因此这种形式的标识符不会与未来标准的扩展冲突。

系统不会使用长度大于NAMEDATALEN-1字节的标识符;命令中可以使用更长的名称,但是会被截断。默认情况下,NAMEDATALEN为64,因此标识符最大长度为63字节。如果该限制存在问题,可以通过修改src/include/pg_config_manual.h文件中的NAMEDATALEN常量增大限制。

关键字和非引用标识符不区分大小写。因此:

UPDATE MY_TABLE SET A = 5;

等价于:

uPDaTE my_TabLE SeT a = 5;

通常的习惯是关键字大写,名称小写,例如:

UPDATE my_table SET a = 5;

标识符还存在另一种形式:界定标识符或者引用标识符。它的形式是使用双引号包含的任意字符序列。界定标识符总是属于一个标识符,而不会是一个关键字。因此“select”可以用于引用一个名为“select”的字段或者表,而一个没有引号的select将会被作为一个关键字,并且如果把它用于表或者字段名时会产生一个解析错误。以上示例可以写成如下引用标识符的形式:

UPDATE "my_table" SET "a" = 5;

引用标识符可以包含任何字符,除了编码为0的字符。(如果想要包含一个双引号,可以连写两个双引号。)这种方法能够创建其他方法无法实现的表名或字段名,例如包含空格和&符号。这种方法同样受到标识符长度的限制。

引用标识符的另一种方式是允许包含转义Unicode字符,使用字符的代码点表示。这种形式在开双引号之间使用U&(大写或小写U带一个&),中间没有空格,例如U&"foo"。(注意这种用法会导致运算符&的模糊性。在运算符&两边使用空格可以避免该问题。)在引号中,Unicode字符可以写成一个反斜杠加4位十六进制代码点数字或者一个反斜杠加一个加号和6位十六进制代码点数字的转义形式。例如,标识符“data”可以写成:

U&"d\0061t\+000061"

以下示例表示西里尔字母中的俄语单词“slon”(大象):

U&"\0441\043B\043E\043D"

如果不想使用反斜杠作为转义字符,可以在字符串之后使用UESCAPE子句指定转义字符,例如:

U&"d!0061t!+000061" UESCAPE '!'

转义字符可以是任何单个字符,除了十六进制数、加号、单引号、双引号或者空白字符之外。注意,转义字符写在单引号中,而不是双引号。

如果要在标识符中包含转义符自身,连写两次该转义字符。

Unicode转义的语法格式只有当服务器编码为UTF8时有效。如果服务器使用其他编码方式,只能使用ASCII范围(最大\007F)的代码点。4位数字和6位数字的形式都可以用于指定UTF-16代理对构成代码点大于U+FFFF的字符,尽管6位数字的形式不需要这样做。(代理对不会直接存储,而是合并成单个代码点,然后使用UTF-8进行编码。)

引用标识符区分大小写,而非应用的名称总是转换为小写形式。例如,标识符FOO、foo和“foo”在PostgreSQL中是一样的,但是“Foo”和“FOO”和前面三个以及它们之间是不同的。(PostgreSQL中将非引用的名称转换为小写的方法与SQL标准不兼容,后者规定应该将非引用的名称转换为大写形式。因此,按照SQL标准,foo应该等于“FOO”,而不是“foo”。如果想要编写可移植的应用,最好总是引用某个名称,或者永远都不引用某个名称。)

4.1.2. 常量

PostgreSQL中存在3种隐式类型的常量:字符串、位串以及数字。常量也可以通过显式类型指定,这样能够表示的更精确,并且系统处理效率更高。接下来分别讨论这两种方式。

4.1.2.1. 字符串常量

SQL中的字符串常量是一个单引号(')包围的任意字符序列,例如,'This is a string'。要想在字符串常量中包含一个单引号字符,可以连写两个单引号,例如,'Dianne''s horse'。注意,这不是一个双引号字符(")。

如果两个字符串常量之间只有空白,并且其中至少包括一个换行,它们将被连接在一起,并且被当作一个字符串常量。例如:

SELECT 'foo'

'bar';

相当于:

SELECT 'foobar';

但是:

SELECT 'foo'      'bar';

是一个无效的语法。(这种略微有点奇怪的行为是SQL标准规定的;PostgreSQL遵循该标准。)

4.1.2.2. C风格转义字符串常量

PostgreSQL同样接受“转义”字符串常量,这是对SQL标准的一个扩展。转义字符串的形式为在开单引号之前输入字母E(大写或小写),例如E'foo'。(多个跨多行连接转义字符串常量,只需要在第一个开引号之前输入E。)在转义字符串内部,反斜杠(\)字符表示一个C风格的反斜杠转义序列,反斜杠和随后的字符代表一个特殊的字节值,如表4-1所示。

表4-1. 反斜杠转义序列

反斜杠转义序列 说明
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表
\o, \oo, \ooo (o = 0 -7) 八进制字节值
\xh, \xhh (h = 0 - 9, A - F) 十六进制字节值
\uxxxx, \Uxxxxxxxx (x = 0 - 9, A - F) 16位或32位十六进制Unicode字符值

反斜杠之后的任何其他字符都按照字面处理。因此,要表示一个反斜杠字符,可以写入两个反斜杠(\\)。此外,转义字符串中的单引号可以写成\',另一个写法就是''。

你需要自己负责你创建的字节序列能够组成服务器字符集编码中的有效字符,尤其是使用八进制或十六进制转义字符时。当服务器编码为UTF-8时,应该使用Unicode转义或者第4.1.2.3小节中的另一个Unicode转义语法。(另一个Unicode转义语法需要手动进行UTF-8编码并且写出字节,将会非常麻烦。)

Unicode转义的语法格式只有当服务器编码为UTF8时才会完全有效。如果服务器使用其他编码方式,只能使用ASCII范围(最大\u007F)的代码点。4位数字和8位数字的形式都可以用于指定UTF-16代理对构成代码点大于U+FFFF的字符,尽管8位数字的形式不需要这样做。(当服务器编码为UTF8时,代理对首先合并成单个代码点,然后使用UTF-8进行编码。)

警告:如果配置参数standard_conforming_strings设置为off,PostgreSQL能够识别常规的字符串常量和转义字符串常量中的反斜杠转移符。但是,从PostgreSQL 9.1开始,该参数默认值为on,意味着只能识别出转义字符串常量中的反斜杠转义符。这种行为更符合标准,但是可能会破坏基于历史方式的应用,这些应用总是能够识别反斜杠转义符。作为一种替代方法,你可以将该参数设置为off,但是最好不要使用反斜杠转义。如果需要使用反斜杠转义表示一个特殊字符,在字符串常量之前使用E。

除了standard_conforming_strings之外,配置参数escape_string_warning和backslash_quote也会控制字符串常量中的反斜杠处理。

代码为零的字符不能用于字符串常量。

4.1.2.3. UNICODE转义字符串常量

PostgreSQL还支持另一种类型的字符串转义语法,允许使用代码点指定任意Unicode字符。Unicode转义字符串常量以U&(大写或者小写的U加一个&)开始,紧跟一个开引号,中间没有空格,例如U&'foo'。(注意这种用法会导致运算符&的模糊性。在运算符&两边使用空格可以避免该问题。)在引号中,Unicode字符可以写成一个反斜杠加4位十六进制代码点数字或者一个反斜杠加一个加号和6位十六进制代码点数字的转义形式。例如,字符串'data'可以写成:

U&'d\0061t\+000061'

以下示例表示西里尔字母中的俄语单词“slon”(大象):

U&'\0441\043B\043E\043D'

如果不想使用反斜杠作为转义字符,可以在字符串之后使用UESCAPE子句指定转义字符,例如:

U&"d!0061t!+000061" UESCAPE '!'

转义字符可以是任何单个字符,除了十六进制数、加号、单引号、双引号或者空白字符之外。

Unicode转义的语法格式只有当服务器编码为UTF8时有效。如果服务器使用其他编码方式,只能使用ASCII范围(最大、007F)的代码点。4位数字和6位数字的形式都可以用于指定UTF-16代理对构成代码点大于U+FFFF的字符,尽管6位数字的形式不需要这样做。(代理对不会直接存储,而是合并成单个代码点,然后使用UTF-8进行编码。)

另外,字符串常量的Unicode转义语法只有当配置参数standard_conforming_strings打开时有效。这是因为如果不这样的话,这种语法将会混淆解析SQL语句的客户端,可能导致SQL注入或者类似安全问题。如果该参数设置为off,该语法将会被拒绝,并显示一个错误信息。

如果要在标识符中包含转义符自身,连写两次该转义字符。

4.1.2.4. 美元符号引用的字符串常量

虽然指定字符串常量的标准语法通常很方便,但是当字符串需要包含单引号或者反斜杠时很难理解,因为这些字符必须写两次。为了允许在这种情况下使用可读性更强的查询,PostgreSQL提供另一种字符串常量方式,称为“美元符号引用”。美元符号引用的字符串常量由一个美元符号($),可选的零或多个字符的“标签”,另一个美元符号,构成字符串内容的任意字符序列,一个美元符号,与前面相同的标签,以及一个美元符号组成。例如,以下是使用美元符号引用方式指定字符串"Dianne's horse"的两种不同形式:

$$Dianne's horse$$

$SomeTag$Dianne's horse$SomeTag$

注意,在美元符号引用的字符串中,单引号可以直接使用,不需要转义。实际上,美元符号引用的字符串中任何字符都不需要转义:字符串内容总是按照字面值表示。反斜杠也不例外,美元符号也不需要转义,除非存在部分序列与开始的标签相同。

通过在不同的嵌套级别使用不同的标签,可以嵌套使用美元引用的字符串常量。这种方法最常见的用途就是函数定义。例如:

$function$

BEGIN

    RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);

END;

$function$

其中,序列$q$[\t\r\n\v\\]$q$表示美元符号引用的字符串$q$[\t\r\n\v\\]$q$,当PostgreSQL执行函数体时将会识别该字符串。但是由于该序列不匹配外部的美元符号引用限定符$function$,就外部字符串而言,它只是常量中的另一些字符而已。

如果美元符号引用字符串中包含标签,该标签遵循与非引用标识符相同的规则,除了它不能包含美元符号之外。标签区分大小写,因此$tag$String content$tag$是正确的,但是$TAG$String content$tag$是错误的。

关键字或者标识符后面的美元符号引用字符串必须与前者之间使用空白分隔;否则美元符号引用限定符将被当作前者的一部分。

美元符号引用不是SQL标准中的一部分,但是它通常比兼容标准的单引号语法更便于编写复杂的字符串常量。它对于在其他常量中表示字符串常量时尤其有用,比如经常用于存储函数定义中。如果使用单引号语法,上例中的每个反斜杠需要使用4个反斜杠表示,在解析原始字符串常量时会减少为2个,然后在函数执行时再次解析内部字符串常量时减少为1个。

4.1.2.5. 位串常量

位串常量看起来与常规字符串常量类似,以B(大写或者小写)开头,紧跟一个开引号(中间没有空白),例如B'1001'。位串常量中只能使用0和1。

除此之外,位串常量可以使用十六进制表示法指定,使用一个前导X(大写或者小写),例如X'1FF'。这种方法等价于用一个十六进制数表示四个二进制数的位串常量。

位串常量的这两种方法都支持跨行连接,与常规字符串常量相同。美元符号引用不能用于位串常量。

4.1.2.6. 数字常量

数字常量接受以下通用格式:

digits

digits.[digits][e[+-]digits]

[digits].digits[e[+-]digits]

digitse[+-]digits

其中digits是一个或者多个十进制数字(0到9)。如果存在小数点,那么小数点前或者后至少需要一个数字。如果存在指数标记(e),后面至少需要一个数字。常量中间不能存在任何空格或者其他字符。注意,任何前导的加号或者减号不被看作常量的一部分;它是一个作用到该常量的一个运算符。

以下是一些有效的数字常量:

42

3.5

4.

.001

5e2

1.925e-3

对于既不包含小数点也不包含指数的数字常量,如果它的值满足integer类型(32位),初始化时将被看作integer类型;否则,如果它的值满足bigint类型(64位),将被看作bigint;否则,将被作为numeric类型。包含小数点和/或指数的数字常量总是被当作numeric类型。

数字常量初始化的类型只是类型解析算法处理的起点。大多数情况下,常量将会自动转换为最符合上下文的类型。必要的时候,你可以通过类型转换将数字值强制转换为特定的数据类型。例如,你可以使用以下方式将一个数字值强制转换为real类型(float4):

REAL '1.23'  -- string style

1.23::REAL   -- PostgreSQL (historical) style

实际上这些只是下文将要讨论的通用转换方法的特殊情况。

4.1.2.7. 其他类型常量

任何类型的常量都可以使用以下方法表示:

type 'string'

'string'::type

CAST ( 'string' AS type )

字符串常量形式的文本将被传给类型为type的输入转换函数进行处理。输出结果为指定类型的常量。如果常量的所属类型不存在歧义(例如,当常量直接赋值给表的字段时),可以忽略显式类型转换,此时将会自动进行类型转换。

字符串常量可以写成常规SQL形式或者美元符号引用的形式。

还可以使用类似函数的语法指定类型转换:

typename ( 'string' )

但不是所有的类型都支持这种方式;详细信息参考4.2.9节。

::,CAST ( )和函数调用的语法形式也可以用于指定任何表达式的运行时类型转换,参考4.2.9节。为了避免语法歧义,type 'string'语法形式只能用于指定简单文字常量类型。type 'string'语法的另一个限制就是不能用于数组类型;可以使用::或者CAST ( )指定数组常量类型。

CAST ( )语法符合SQL标准。type 'string'语法是对标准的泛化:SQL只为一些数据类型规定了该语法,但是PostgreSQL

支持所有的类型。::和函数调用的语法是PostgreSQL的历史用法。

4.1.3. 运算符

运算符的名称由以下字符序列组成,最多NAMEDATALEN-1(默认63)个字符:

+ - * / < > = ~ ! @ # % ^ & | ` ?

运算符的名称存在以下限制:

• --和/*不能出现在运算符名称中,因为它们会被当作注释的开始。

• 包含多个字符的运算符名称不能以+或-结束,除非名称中至少包含一个以下字符:

  ~ ! @ # % ^ & | ` ?

  例如,@-是有效的运算符名称,但*-不是。这种限制允许PostgreSQL能够解析遵循SQL的查询,而不需要在查询的符号之间使用空格。

当使用非SQL标准的运算符名称时,通常需要使用空格分隔相邻的运算符,以避免歧义。例如,假设你定义了一个名为@的左一元运算符,不能使用X*@Y;必须写成X* @Y,以确保PostgreSQL将它读取为两个运算符而不是一个。

4.1.4. 特殊字符

一些非字母数字的字符拥有特殊的意义,与运算符不同。详细信息可以参考相应的语法元素描述。本节只是概述这些字符的作用。

• 美元符号($)加数字用于表示函数定义或者预编译语句中的位置参数。其他情况下,美元符号可以是标识符或者美元符号引用字符串常量的一部分。

• 圆括号(())通常意味着表达式分组或者强制优先级。某些情况下,括号是特定SQL命令的固定语法的一部分。

• 方括号([])用于选择数组的元素。关于数组参考8.15节。

• 逗号(,)在某些句法结构中用于分隔列表的元素。

• 分号(;)终止一个SQL命令。它不能出现在命令中的任何位置,除非字符串常量或者引用标识符中。

• 冒号(:)用于选择数组的“片段”。(参考8.15节。)在某些SQL方言中(),分号用于变量名前缀。

• 星号(*)在某些情况下表示表或者复合值的全部字段。当用于聚合函数的参数时,它表示一个特殊的意义,也就是该聚合函数不需要任何显式的参数。

• 句号(.)可以用于数字常量,或者分隔模式、表名以及列名。

4.1.5. 注释

注释是以双破折号开始的一个字符序列,直到当前行的结束,例如:

-- This is a standard SQL comment

此外,还可以使用C风格的注释块:

/* multiline comment

 * with nesting: /* nested block comment */

 */

其中,注释以/*开始,直到匹配的*/。SQL标准定义的注释块嵌套,与C不同,可以注释可能包含其他注释块的更大的代码块。

注释在进行语法分析之前就从输入流中删除,并且使用空白进行替代。

4.1.6. 运算符优先级

表4-2列出了PostgreSQL中的运算符的优先级和结合性。大多数相同优先级的运算符拥有左结合性。运算符的优先级和结合性是硬编码到解析器中的。这样可能会导致非直观的行为;例如布尔运算符<和>与布尔运算符<=和>=的优先级不同。另外,当你组合使用二元运算符和一元运算符时,可能需要使用括号。例如:

SELECT 5 ! - 6;

将被解析为:

SELECT 5 ! (- 6);

因为解析器不知道!被定义为一个后缀运算符,而不是中缀运算符(知道的时候已经晚了)。此时,如果要想得到正确的行为,必须写成:

SELECT (5 !) - 6;

这是可扩展性需要付出的代价。

表4-2. 运算符优先级(递减)

运算符/元素 结合性 描述
. 表名/列名分隔符
:: PostgreSQL风格类型转换
[ ] 数组元素选择
+ - 一元正号,一元负号
^ 乘方
* / % 乘法,除法,取模
+ - 加法,减法
IS   IS TRUE,IS FALSE,IS NULL等
ISNULL   测试空
NOTNULL   测试非空
(任何其他) 任何其他原生或者用户定义的运算符
IN   集合成员
BETWEEN   范围控制
OVERLAPS   时间间隔重叠
LIKE ILIKE SIMILAR   字符串模式匹配
< >   小于,大于
= 等于,赋值
NOT 逻辑非
AND 逻辑与
OR 逻辑或

注意,以上运算符优先级规则同样适用于用户定义的与内置运算符同名的运算符。例如,如果你为某些自定义数据类型定义了一个“+”运算符,它和内置的“+”运算符优先级相同。

如果在OPERATOR语法中使用了一个模式限定的运算符名称,例如:

SELECT 3 OPERATOR(pg_catalog.+) 4;

OPERATOR构造函数拥有表4-2中的“任何其他”运算符的优先级。无论OPERATOR ( )中的运算符是什么,优先级都是如此。

4.2. 值表达式

值表达式可以用于各种场景,例如SELECT命令的目标列表,INSERT或者UPDATE的新列值,或者许多命令的搜索条件。值表达式的结果有时候称为标量,用于区别表表达式的结果(该结果是一个表)。因此值表达式也被称为标量表达式(或者简称为表达式)。表达式语法允许对原始部分的计算使用算数、逻辑、集合以及其他运算。

值表达式可以是以下形式之一:

• 常量或者字面值

• 列引用

• 函数定义或者预编译语句中的位置参数引用

• 下标表达式

• 字段选择表达式

• 运算符调用

• 函数调用

• 聚合表达式

• 窗口函数调用

• 类型转换

• 排序规则表达式

• 标量子查询

• 数组构造函数

• 行构造函数

• 括号中的值表达式(用于子表达式分组和优先级覆盖)

除了以上列表之外,还有许多构造函数可以归类为表达式,但是它们没有使用通用语法规则。它们通常具有函数或者运算符的语义,分别在第9章中相应部分进行描述。例如IS NULL子句。

常量已经在4.1.2节中讨论过,接下来讨论剩余部分。

4.2.1. 列引用

字段可以使用以下形式进行引用:

correlation.columnname

correlation是一个表名(可能包含模式限定),或者FROM子句中定义的表的别名。如果列名在当前查询所访问的所有表中是唯一的,可以忽略前面的关系名称和点号。(参考第7章。)

4.2.2. 位置参数

位置参数引用用于表示外表传递给SQL语句的值。参数用于SQL函数定义和预编译查询。一些客户端程序库还支持在SQL命令字符串之外指定数据值,这种情况下参数用于引用这些外部数据值。参数引用的格式如下:

$number

例如,函数dept的定义如下:

CREATE FUNCTION dept(text) RETURNS dept

    AS $$ SELECT * FROM dept WHERE name = $1 $$

    LANGUAGE SQL;

其中,$1引用了函数调用时的第一个参数值。

4.2.3. 下标表达式

如果一个表达式创建了一个数组类型的值,那么可以使用以下方式获取数组的特定元素:

expression[subscript]

或者使用以下方式获取多个连续的元素(一个“数组切片”):

expression[lower_subscript:upper_subscript]

(其中,方括号[ ]具有字面意义,而不是表示可选项。)每个下标都是一个表达式,结果必须为整型值。

通常该数组表达式必须使用括号包围,但是如果表达式是一个列引用或者位置参数,可以省略括号。另外,如果源数组是多维数组,可以连续指定多个下标。例如:

mytable.arraycolumn[4]

mytable.two_d_column[17][34]

$1[10:42]

(arrayfunction(a,b))[42]

最后一个示例的括号不能省略。更多关于数组的信息可以参考8.15节。

4.2.4. 字段选择

如果一个表达式创建了一个复合类型(行类型)的值,可以使用以下方式获取该行的特定字段:

expression.fieldname

通常该行表达式必须使用括号包围,但是如果表达式是一个表引用或者位置参数,可以省略括号。例如:

mytable.mycolumn

$1.somecolumn

(rowfunction(a,b)).col3

(因此,一个限定的列引用实际上是字段选择语法的特殊形式。)一个重要的特殊形式就是从复合类型的列中获取一个字段:

(compositecol).somefield

(mytable.compositecol).somefield

此处的括号是必需的,用于说明compositecol是一个列名而不是表名,或者说明mytable是一个表名而不是模式名。

在查询的选择列表中(参见7.3节),可以使用.*指定一个复合值的所有字段:

(compositecol).*

4.2.5. 运算符调用

运算符调用可以使用以下三种语法:

expression operator expression (二元中缀运算符)

operator expression (一元前缀运算符)

expression operator (一元后缀运算符)

其中,operator符号遵循4.13节中描述的语法,或者是关键字AND,OR和NOT,或者是以下形式的限定运算符:

OPERATOR(schema.operatorname)

运算符的数量和种类(一元或者二元)取决于系统和用户已经定义的运算符。第9章描述了内置的运算符。

4.2.6. 函数调用

函数调用的语法形式为一个函数名(可能包含模式名限定符),加上括号包围的参数列表:

function_name ([expression [, expression ... ]] )

例如,以下表达式计算2的平方跟:

sqrt(2)

第9章列出了内置函数的列表。用户可以添加自定义的函数。

函数调用时的参数也可以使用参数名指定。参见4.3节。

注意:只有一个复合类型参数的函数可以使用字段选择的语法进行调用,反之字段选择也可以写成函数的风格。也就是,col( table )与table.col可以互相替换。这种特性不属于SQL标准,但是PostgreSQL支持,因为它运行使用函数模拟“计算字段”。更多信息参考35.4.3。

4.2.7. 聚合表达式

聚合表达式就是查询结果行上的一个聚合函数。聚合函数将多个输入输出为单个结果值,例如求和或者平均值。聚合表达式可以使用以下语法:

aggregate_name (expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]

aggregate_name (ALL expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]

aggregate_name (DISTINCT expression [ , ... ] [ order_by_clause ] ) [ FILTER ( WHERE filter_clause ) ]

aggregate_name ( * ) [ FILTER ( WHERE filter_clause ) ]

aggregate_name ( [ expression [ , ... ] ] ) WITHIN GROUP ( order_by_clause ) [ FILTER ( WHERE filter_clause ) ]

其中,aggregate_name是一个预定义的聚合函数(可能包含模式名限定符),expression可以是任何不包含其他聚合表达式或窗口函数调用的值表达式。order_by_clause和filter_clause可选。

聚合表达式的第一种格式对于每一行输入都执行一次聚合计算。第二种格式与第一个相同,因为ALL是默认值。第三种格式对于表达式的每个不同的值(对于多个表达式,不同的一组值)执行一次聚合计算。第四种格式对于每个输入行执行一次聚合计算;由于没有指定具体的输入值,它通常只用于count( * )聚合函数。最后一种格式用于有序集合聚合函数,下文将进行描述。

大多数聚合函数都会忽略空值输入,因此一个或者多个表达式的结果为空的输入行将会被丢弃。所有的内置函数都是这样处理的,除非另有说明。

例如,count( * )计算输入行的总数;count( f1 )计算输入行中f1不为空的行数,因为count忽略空值;count( distinct f1 )计算f1不为空并且值不同的行数。

通常情况下,输入行以不确定的顺序传入聚合函数。许多情况下这都不是什么问题;例如,min函数的参数无论以什么顺序输入,结果都一样。但是,某些聚合函数的结果取决于输入的顺序(例如array_agg和string_agg)。使用这类聚合函数时,可以通过order_by_clause指定输入的顺序。order_by_clause与查询的ORDER BY子句语法相同,参见7.5节,唯一的不同就是它的表达式只能是表达式,并且不能是输出列的名称或者编号。例如:

SELECT array_agg(a ORDER BY b DESC) FROM table;

当使用多参数的聚合函数时,注意ORDER BY子句必须写在所有的参数之后。例如:

SELECT string_agg(a, ',' ORDER BY a) FROM table;

而不能写成以下形式:

SELECT string_agg(a ORDER BY a, ',') FROM table;  -- incorrect

后者在句法结构上没有问题,但是它表示一个单一参数的聚合函数,同时包含两个ORDER BY键(第二个键是一个常量,没有什么用处)。

如果聚合函数除了order_by_clause之外还使用了DISINTCT,那么所有的ORDER BY表达式必须是聚合函数的常规参数;也就是说,你不能基于DISTINCT列表中不存在的表达式进行排序。

注意:在聚合函数中同时使用DISTINCT和ORDER BY是PostgreSQL的一个扩展特性。

如前所述,将ORDER BY写到聚合函数的常规参数列表可以用于“常规”聚合函数的输入排序。聚合函数存在一个子集,称为有序集合聚合函数,它们的order_by_clause是必需选项,通常是因为这些聚合函数只有按照特定的输入顺序进行计算才有意义。典型的有序集合聚合函数包括排名和百分比计算。对于有序集合聚合函数,order_by_clause写在WITHIN GROUP (...),如上面最后一种格式所示。order_by_clause中的表达式对于每行输入执行一次,这与常规聚合参数一样,然后按照每个order_by_clause的要求进行排序,最后作为入参传递给聚合函数。(这与非WITHIN GROUP中的order_by_clause不同,后者不被作为聚合函数的参数。)WITHN GROUP前面的参数表达式,如果存在的话,被称为直接参数,用于区别order_by_clause中的聚合参数。与常规聚合参数不同,直接参数对于每次聚合函数调用值计算一次,而不是每行输入一次。这意味着它们只能包含通过GROUP BY分组后的变量;这种限制同样适用于不在聚合表达式中的直接参数。直接参数通常用于那些对于每次聚合计算只产生单个值的聚合函数,例如百分比分数。直接参数列表可以为空;这种情况的话,写成( )而不是( * )。(实际上PostgreSQL两种写法都支持,但是只有前者符合SQL标准。)一个有序集合聚合函数调用的示例如下:

SELECT percentile_disc(0.5) WITHIN GROUP (ORDER BY income) FROM households;

 percentile_disc

-----------------

           50489

以上查询获取表households中income列的第50个百分位或者中间值。其中,0.5是一个直接参数;百分比是相对于所有行而言,针对不同的行计算出不同的百分比分数没有意义。

如果指定了FILTER,只有filter_clause的结果为真的行才会传入聚合函数;其他的行将会被丢弃。例如:

SELECT

    count(*) AS unfiltered,

    count(*) FILTER (WHERE i < 5) AS filtered

FROM generate_series(1,10) AS s(i);

 unfiltered | filtered

------------+----------

         10 |        4

(1 row)

第9.20节描述了预定义的聚合函数。用户可以创建自定义的聚合函数。

聚合函数只能出现在SELECT命令的结果列表或者HAVING子句中。其他的子句不能使用聚合函数,例如WHERE,因为这些子句逻辑上是在聚合结果之前进行处理的。

如果在子查询中使用聚合函数(参见4.2.11节和9.22节),聚合函数通常是基于子查询的结果行进行计算。但是存在一个例外,就是当聚合函数的参数(以及可能的filter_clause)只包含外层变量时:聚合函数此时属于最近的这类外层查询,因此基于该查询的结果行进行计算。于是,该聚合表达式作为一个整体,变成了子查询的一个外部引用,并且当做子查询计算时的一个常量。聚合函数只能出现在SELECT命令的结果列表或HAVING子句中的限制是对于该聚合函数所在的查询层次而言的。

4.2.8. 窗口函数调用

窗口函数表示基于查询结果的部分行执行的类似聚合函数。与常规聚合函数不同的是,窗口函数不是将选定的行组输出为单个结果,而是每一行仍然保留一行输出。但是窗口函数同样可以扫描所有的行,根据分组规范(PARTITION BY列表)判断是否属于当前行的组。窗口函数可以使用以下语法:

function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER window_name

function_name ([expression [, expression ... ]]) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )

function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER window_name

function_name ( * ) [ FILTER ( WHERE filter_clause ) ] OVER ( window_definition )

其中,window_definition的语法如下:

[ existing_window_name ]

[ PARTITION BY expression [, ...] ]

[ ORDER BY expression [ ASC | DESC | USING operator ] [ NULLS { FIRST | LAST } ] [, ...] ]

[ frame_clause ]

frame_clause可以是以下格式之一:

[ RANGE | ROWS ] frame_start

[ RANGE | ROWS ] BETWEEN frame_start AND frame_end

frame_start和frame_end可以是以下格式之一:

UNBOUNDED PRECEDING

value PRECEDING

CURRENT ROW

value FOLLOWING

UNBOUNDED FOLLOWING

其中,expression表示不包含窗口函数的任何值表达式。

window_name是对在查询的WINDOW子句中定义的一个命名窗口规范的引用。或者,可以在括号中给出一个完整的window_definition,语法形式与WINDOW子句中的命名窗口定义相同;更多信息参考SELECT命令。需要指出的是OVER wname并不完全等同于OVER ( wname );后者意味着复制和修改窗口定义,并且如果引用的窗口规范包含框架子句,将会被拒绝。

PARTITION BY选项将查询的行分组为分区,窗口函数将会分别处理每个分区。PARTITION BY类似于查询的GROUP BY子句,只是它的表达式只能是纯粹的表达式,而不能是输出的列名或者编号。如果没有PARTITION BY,查询的所有行都当作一个分区进行处理。ORDER BY选项决定了窗口函数处理分区中的行的顺序。它与查询的ORDER BY子句类似,但是同样不能使用输出的列名或者编号。如果没有ORDER BY,行处理的顺序是不确定的。

frame_clause为那些基于窗口框架而不是整个分区执行的窗口函数指定了组成窗口框架的行集,窗口框架是当前分区的一个子集。框架可以使用RANGE或者ROWS方式指定;不论哪种方式,框架都包括从frame_start到frame_end的行。如果省略了frame_end,默认为CURRENT ROW。

frame_start取值UNBOUNDED PRECEDING意味着框架从分区的第一行开始,同样frame_end取值UNBOUNDED FOLLOWING意味着框架直到分区的最后一行结束。

在RANGE方式中,frame_start取值CURRENT ROW意味着框架从当前行的第一个等值行(ORDER BY得到的与当前行相等的行),而frame_end取值CURRENT ROW意味着框架直到最后一个等值的ORDER BY行结束。在ROWS方式中,CURRENT ROW就是当前行。

value PRECEDING和value FOLLOWING当前只能用于ROWS方式。它们表示框架从/到当前行之前/之后指定行数开始/结束。value必须是一个整数表达式,不能包含变量,聚合函数或者窗口函数。该值不能为空或者负数;但是可以为零,意味着当前行。

默认的框架选项为RANGE UNBOUNDED PRECEDING,效果与RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW相同。此时如果存在ORDER BY,框架将包含从分区开始直到当前行最后一个等值行的所有行。如果不存在ORDER BY,分区的所有行都属于窗口框架,因为所有的行都是当前行的等值行。

frame_start不能是UNBOUNDED FOLLOWING,frame_end不能是UNBOUNDED PRECEDING,同时frame_end不能是以上列表中比frame_start更靠前的值,例如frame_end取值RANGE BETWEEN CURRENT ROW AND value PRECEDING是不允许的。

如果指定了FILTER,只有那些满足filter_cluase的值为真的行才会传入窗口函数;其他的行将被丢弃。只有聚合计算的窗口函数能够使用FILTER子句。

表9-53列出了内置的窗口函数。用户可以创建自定义的窗口函数。另外,任何内置或者用户定义的常规聚合函数都能作为窗口函数。但是,有序集合聚合函数当前不能作为窗口函数。

使用*的语法用于将无参数的聚合函数作为窗口函数,例如count(*) OVER (PARTITION BY x    ORDER BY y)。星号(*)通常不用于非聚合的窗口函数。聚合窗口函数,与常规聚合函数不同,不允许在函数参数列表中使用DISTINCT或者ORDER BY。

窗口函数只允许用于查询的SELECT列表和ORDER BY子句中。

更多关于窗口函数的信息可以参考3.5节,9.21节以及7.2.4节。

4.2.9. 类型转换

类型转换指定了从一个数据类型到另一个数据类型的转换操作。PostgreSQL接受两种等效的类型转换语法:

CAST ( expression AS type )

expression::type

其中CAST语法符合SQL标准;带::的语法是PostgreSQL的历史用法。

当类型转换应用于一个已知类型的值表达式时,它表示一个运行时类型转换。该转换只有在已经定义了合适的类型转换操作的情况下才会成功。注意,这与4.1.2.7节中常量的类型转换略有不同。纯字符串常量的类型转换表示赋予字符串常量值一个初始类型,因此对于任何类型都会成功(如果字符串字面值的内容对于该数据类型而言是有效的数据)。

如果表达式结果的类型不存在歧义(例如,当表达式赋值给表的字段时),可以忽略显式类型转换;此时系统将会自动应用类型转换。但是,自动转换只能用于在系统数据中被标记为“允许隐式转换”的类型转换。其他的类型转换必须使用显式转换语法。这个限制是为了防止隐式执行的意外转换。

还可以使用函数风格的类型转换语法:

typename ( expression )

但是,这种语法只能用于那些名字能够作为有效函数名的类型。例如,double precision不能使用这种方法,但是float8可以。另外,interval,time和timestamp只有位于双引号内才能使用这种方式,因为存在语法冲突。因此,使用函数风格的类型转换语法会导致不一致性,应该尽量避免使用。

注意:函数风格的类型转换实际上就是一个函数调用。当使用两种标准的类型转换语法执行运行时转换时,实际上内部将会调用一个已注册的函数执行该转换。按照惯例,这些转换函数具有和输出类型相同的名称,因此“函数风格的语法”只不过是直接调用底层的转换函数。显示,这种特性不具备可移植性。更多信息可以参考CREATE CAST。

4.2.10. 排序表达式

COLLATE子句覆盖了表达式的排序规则。它位于相应表达式的后面:

expr COLLATE collation

其中collation是一个模式限定的标识符。COLLATE子句比运算符的结合性更紧;必要时可以使用括号。

如果没有明确指定排序规则,数据库系统将会使用表达式中的列的排序规则,如果表达式中没有使用列,将会使用数据库的默认排序规则。

COLLATE子句的两个常见的用法包括在ORDER BY子句中覆盖排序顺序,例如:

SELECT a, b, c FROM tbl WHERE ... ORDER BY a COLLATE "C";

以及覆盖区域敏感的函数或者运算符的排序规则,例如:

SELECT * FROM tbl WHERE a > 'foo' COLLATE "C";

注意,在后一个示例中,我们将COLLATE子句作用于运算符的第二个参数。COLLATE子句关联到运算符或函数的哪个参数都无所谓,因为该排序规则将会应用到所有的参数上,而且显式的COLLATE子句将会覆盖所有其他参数的排序规则。(然而,在多个参数上应用不同的COLLATE子句将会产生错误。更多信息可以参见22.2节。)因此,以下语句与上一个示例的结果相同:

SELECT * FROM tbl WHERE a COLLATE "C" > 'foo';

但是,下面是一个错误的写法:

SELECT * FROM tbl WHERE (a > 'foo') COLLATE "C";

因为它将排序规则应用到了运算符>的结果上,而运算符>的结果是一个无法应用排序规则的boolean类型。

4.2.11. 标量子查询

标量子查询是括号包围的一个普通SELECT查询,该查询只返回一行一列的结果。(关于查询可以参考第7章。)该SELECT查询在外部的值表达式中执行,然后返回的单一值又被用于外部值表达式。如果一个标量子查询返回多行或者多列,将会产生一个错误。(但是,如果子查询没有返回任何行,不会产生错误;该标量查询的结果为空。)子查询可以引用外部查询的变量,该变量在每次执行子查询时被当作常量使用。关于子查询表达式可以参考9.22节。

例如,以下查询返回了每个州的最大的城市人口:

SELECT name, (SELECT max(pop) FROM cities WHERE cities.state = states.name)

    FROM states;

4.2.12. 数组构造函数

数组构造函数是一个表达式,它通过提供成员元素的值构建一个数组值。一个简单的数组构造函数包括关键字ARRAY,一个左方括号[,指定数组元素值的表达式列表(逗号分隔),以及一个结束的右方括号]。例如:

SELECT ARRAY[1,2,3+4];

  array

---------

 {1,2,7}

(1 row)

默认情况下,数组元素的类型与成员表达式的类型相同,使用与UNION或CASE结构相同的判定规则(参见10.5节)。你可以通过显式类型转换覆盖默认的类型,例如

SELECT ARRAY[1,2,22.7]::integer[];

  array

----------

 {1,2,23}

(1 row)

这种方法与分别转换每个表达式的效果相同。更多关于类型转换的信息可以参考4.2.9节。

多维数组值可以通过嵌套数组构造函数进行创建。内层的构造函数的关键字ARRAY可以省略。例如,以下两个语句的结果相同:

SELECT ARRAY[ARRAY[1,2], ARRAY[3,4]];

     array

---------------

 {{1,2},{3,4}}

(1 row)


SELECT ARRAY[[1,2],[3,4]];

     array

---------------

 {{1,2},{3,4}}

(1 row)

由于多维数组必须是矩形结构的,同一级别的内层构造函数必须创建相同纬度的子数组。作用到外层ARRAY构造函数的任何类型转换都会自动应用到所有的内层构造函数。

多维数组构造函数的元素可以是能够产生一个类型合适的数组的任何事物,而不仅仅只能是一个子数组构造函数。例如:

CREATE TABLE arr(f1 int[], f2 int[]);


INSERT INTO arr VALUES (ARRAY[[1,2],[3,4]], ARRAY[[5,6],[7,8]]);


SELECT ARRAY[f1, f2, '{{9,10},{11,12}}'::int[]] FROM arr;

                     array

------------------------------------------------

 {{{1,2},{3,4}},{{5,6},{7,8}},{{9,10},{11,12}}}

(1 row)

你可以构造一个空数组,但是因为数组必须属于某个类型,你必须进行显式类型转换。例如:

SELECT ARRAY[]::integer[];

 array

-------

 {}

(1 row)

还可以使用子查询的结果构造一个数组。在这种方式中,数组构造函数的格式为关键字ARRAY加一个圆括号(不是方括号)包围的子查询。例如:

SELECT ARRAY(SELECT oid FROM pg_proc WHERE proname LIKE 'bytea%');

                                 array

-----------------------------------------------------------------------

 {2011,1954,1948,1952,1951,1244,1950,2005,1949,1953,2006,31,2412,2413}

(1 row)

子查询必须返回单列。在结果的一维数组中,子查询结果的每一行表示一个元素,元素类型与子查询的结果列一致。

使用ARRAY创建的数组值的下标总是从1开始。更多关于数组的信息可以参考8.15节。

4.2.13. 行构造函数

行构造函数是一个表达式,它通过提供成员字段构建一个行值(也称为复合值)。一个行构造函数包括关键字ROW,一个左括号,指定行的字段值的零个或多个表达式(逗号分隔),以及一个结束的右括号。例如:

SELECT ROW(1,2.5,'this is a test');

如果列表中的表达式多于一个,关键字ROW可以省略。

行构造函数可以包含语法rowvalue.*,它将会扩展为该行值的元素的列表,就像在顶层SELECT列表中的.*语法一样。例如,假设表t包含列f1和f2,以下写法的结果相同:

SELECT ROW(t.*, 42) FROM t;

SELECT ROW(t.f1, t.f2, 42) FROM t;

注意:在PostgreSQL 8.2之前,.*语法不会进行扩展,所以ROW(t.*, 42)将会创建一个两个字段的行值,其中第一个字段是另一个行值。新版的方式通常更用途。如果需要实现旧版的嵌套行值,可以不使用.*,而使用ROW(t, 42)的形式。

默认情况下,ROW表达式创建的值的类型为匿名记录类型。如果需要的话,可以将它转换为一个命名的复合类型--可以是一个表的行类型,或者是一个CREATE TYPE AS创建的复合类型。为了避免歧义,可能需要使用一个显式类型转换。例如:

CREATE TABLE mytable(f1 int, f2 float, f3 text);


CREATE FUNCTION getf1(mytable) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;


-- No cast needed since only one getf1() exists

SELECT getf1(ROW(1,2.5,'this is a test'));

 getf1

-------

     1

(1 row)


CREATE TYPE myrowtype AS (f1 int, f2 text, f3 numeric);


CREATE FUNCTION getf1(myrowtype) RETURNS int AS 'SELECT $1.f1' LANGUAGE SQL;


-- Now we need a cast to indicate which function to call:

SELECT getf1(ROW(1,2.5,'this is a test'));

ERROR:  function getf1(record) is not unique


SELECT getf1(ROW(1,2.5,'this is a test')::mytable);

 getf1

-------

     1

(1 row)


SELECT getf1(CAST(ROW(11,'this is a test',2.5) AS myrowtype));

 getf1

-------

    11

(1 row)

行构造函数构建的复合值可以用于存储到表的复合类型列,或者传递给接受复合参数的函数。另外,还可以将两个行值进行比较,或者使用IS NULL或IS NOT NULL测试行值,例如:

SELECT ROW(1,2.5,'this is a test') = ROW(1, 3, 'not the same');

SELECT ROW(table.*) IS NULL FROM table;  -- detect all-null rows

更多信息可以参考9.23节。行构造函数还可以与子查询结合使用,参见9.22节。

4.2.14. 表达式计算规则

子表达式的计算顺序没有规定。特别是对于运算符或者函数的输入,不需要按照从左至右或者任何其他固定顺序进行计算。

此外,如果表达式的结果能够由部分运算决定,那么其他的子表达式可能根本不会执行。例如:

SELECT true OR somefunc();

somefunc( )很可能不会被调用。以下写法的结果也是一样:

SELECT somefunc() OR true;

注意,这与某些编程语言中布尔运算符的从左至右“短路”运算不同。

因此,最好不要使用存在副作用的函数作为复杂表达式的一部分。在WHERE子句和HAVING子句中依赖副作用或者计算顺序是非常危险的,因为在创建执行计划时,这些子句通常都会重新处理。这些子句中的布尔表达式(AND/OR/NOT组合)可以按照布尔算数规定的任何方式进行重组。

如果一定需要按照某种顺序进行计算,可以使用CASE结构(参考9.17节)。例如,使用以下方法避免除零操作是不可靠的:

SELECT ... WHERE x > 0 AND y/x > 1.5;

但以下方式是安全的:

SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

这种形式的CASE结构将会无法进行优化,所以应该只在必要的时候使用。(就这个示例而言,最好的方法是写成y > 1.5*x的形式,避免可能的问题。)

这种方法存在一个限制,就是CASE不能防止其中的聚合表达式的计算,因为SELECT列表或者HAVING子句中的聚合表达式在“标量”表达式之前进行计算。例如,以下查询可能会导致除零错误,尽管好像已经防止了该错误:

SELECT CASE WHEN min(employees) > 0

            THEN avg(expenses / employees)

       END

    FROM departments;

聚合函数min( )和avg( )对于每一行输入同时进行计算,因此如果某一行的employees等于零,在测试min( )之前将会产生除零错误。反之,使用WHERE或者FILTER子句能够从源头上阻止存在问题的行输入聚合函数。

4.3. 函数调用

PostgreSQL允许使用位置表示法或者命名表示法调用带命名参数的函数。命名表示法对于带有大量参数的函数尤其有用,因为它使得形参和实参之间的关系更加清晰可靠。在位置表示法中,函数调用的参数按照它们在函数声明时相同的顺序进行传递。在命名表示法中,实参按照名字与形参进行匹配,可以使用任意顺序编写。

对于任一表示法,在函数声明中指定了默认值的参数在调用时可以不用传值。这对于命名表示法尤其有用,因为可以省略任意参数;而在位置表示法中,只能从右至左进行省略。

PostgreSQL还可以支持混合表示法,它结合了位置表示法和命名表示法。这种情况下,先写位置参数,然后是命名参数。

接下来的示例将会使用以下函数定义演示所有三种表示法:

CREATE FUNCTION concat_lower_or_upper(a text, b text, uppercase boolean DEFAULT false)

RETURNS text

AS

$$

 SELECT CASE

        WHEN $3 THEN UPPER($1 || ' ' || $2)

        ELSE LOWER($1 || ' ' || $2)

        END;

$$

LANGUAGE SQL IMMUTABLE STRICT;

函数concat_lower_or_upper拥有两个强制参数,a和b。另外还包含一个可选参数uppercase,默认值为false。入参a和b将会进行连接,并且基于uppercase参数转换为大写或者小写。该函数定义中的其他细节在此处不重要(更多信息参考第35章)。

4.3.1. 位置表示法

位置表示法是PostgreSQL中函数传参的传统方法。例如:

SELECT concat_lower_or_upper('Hello', 'World', true);

 concat_lower_or_upper

-----------------------

 HELLO WORLD

(1 row)

所有参数按照顺序进行指定。因为uppercase指定为true,结果为大写形式。另一个例子如下:

SELECT concat_lower_or_upper('Hello', 'World');

 concat_lower_or_upper

-----------------------

 hello world

(1 row)

其中,uppercase参数被忽略,因此使用默认值false,结果为小写形式。在位置表示法中,参数可以从右至左进行忽略,只要它们存在默认值。

4.3.2. 命名表示法

在命名表示法中,每个参数的名称使用:=指定,以便与参数表达式区分。例如

SELECT concat_lower_or_upper(a := 'Hello', b := 'World');

 concat_lower_or_upper

-----------------------

 hello world

(1 row)

同样,因为忽略了uppercase参数,所以隐式使用默认的false。命名表示法的一个优点是可以按照任意顺序指定参数,例如:

SELECT concat_lower_or_upper(a := 'Hello', b := 'World', uppercase := true);

 concat_lower_or_upper

-----------------------

 HELLO WORLD

(1 row)


SELECT concat_lower_or_upper(a := 'Hello', uppercase := true, b := 'World');

 concat_lower_or_upper

-----------------------

 HELLO WORLD

(1 row)

4.3.3. 混合表示法

混合表示法结合了位置表示法和命名表示法。但是,正如前文所说,命名参数不能位于位置参数之前。例如:

SELECT concat_lower_or_upper('Hello', 'World', uppercase := true);

 concat_lower_or_upper

-----------------------

 HELLO WORLD

(1 row)

在以上查询中,参数a和b通过位置指定,而uppercase通过名称指定。对于拥有多个存在默认值的参数的复杂函数而言,命名表示法或者混合表示法能够节省大量书写,同时减少了出错的可能。