您的位置:首页 > 科技 > IT业 > b2c电商平台开发_中国建筑报道网_网站域名在哪买_seo薪资

b2c电商平台开发_中国建筑报道网_网站域名在哪买_seo薪资

2024/12/23 10:39:40 来源:https://blog.csdn.net/lovechris00/article/details/142289786  浏览:    关键词:b2c电商平台开发_中国建筑报道网_网站域名在哪买_seo薪资
b2c电商平台开发_中国建筑报道网_网站域名在哪买_seo薪资

本文翻译整理自:https://www.postgresql.org/docs/15/sql.html


文章目录

  • 第4章 SQL语法
    • 4.1 词汇结构
      • 4.1.1标识符和关键词
      • 4.1.2常数
        • 4.1.2.1字符串常量
        • 4.1.2.2 具有C样式转义的字符串常量
        • 4.1.2.3 具有Unicode转义的字符串常量
        • 4.1.2.4美元报价字符串常量
        • 4.1.2.5位串常量
        • 4.1.2.6数值常数
        • 4.1.2.7其他类型的常数
      • 4.1.3 操作符
      • 4.1.4特殊字符
      • 4.1.5评论
      • 4.1.6运算符优先级
    • 4.2 值表达式
      • 4.2.1列引用
      • 4.2.2位置参数
      • 4.2.3下标
      • 4.2.4领域选择
      • 4.2.5 操作符调用
      • 4.2.6函数调用
      • 4.2.7 聚合表达式
      • 4.2.8窗口函数调用
      • 4.2.9类型转换
      • 4.2.10排序规则表达式
      • 4.2.11标量子查询
      • 4.2.12数组构造函数
      • 4.2.13行构造函数
      • 4.2.14表达式评估规则
    • 4.3.调用函数
      • 4.3.1 位置符号的使用
      • 4.3.2使用命名符号
      • 4.3.3使用混合符号
  • 第5章 数据定义
    • 5.1 表格基础
    • 5.2.默认值
    • 5.3.生成的列
    • 5.4 限制
      • 5.4.1检查约束
      • 5.4.2 非空约束
      • 5.4.3独特的限制
      • 5.4.4主键
      • 5.4.5 外键
      • 5.4.6排除限制
    • 5.5.系统列
    • 5.6.修改表
      • 5.6.1添加列
      • 5.6.2删除列
      • 5.6.3 添加约束
      • 5.6.4 移除约束
      • 5.6.5更改列的默认值
      • 5.6.6更改列的数据类型
      • 5.6.7重命名列
      • 5.6.8重命名表
    • 5.7.特权
    • 5.8.行安全策略
    • 5.9.模式
      • 5.9.1创建架构
      • 5.9.2公共模式
      • 5.9.3架构搜索路径
      • 5.9.4架构和特权
      • 5.9.5系统目录模式
      • 5.9.6 使用模式
      • 5.9.7便携性
    • 5.10.继承
      • 5.10.1注意事项
    • 5.11 表分区
      • 5.11.1概述
      • 5.11.2 声明式分区
        • 5.11.2.1例子
        • 5.11.2.2分区维护
        • 5.11.2.3 限制
      • 5.11.3使用继承进行分区
        • 5.11.3.1例子
        • 5.11.3.2继承分区的维护
        • 5.11.3.3注意事项
      • 5.11.4分区修剪
      • 5.11.5分区和约束排除
      • 5.11.6声明式分区的最佳实践
    • 5.12 外部数据
    • 5.13 其他数据库对象
    • 5.14 依赖跟踪
  • 第6章 数据操作
    • 6.1 插入数据
    • 6.2.更新数据
    • 6.3.删除数据
    • 6.4.从修改的行返回数据
  • 第7章 查询
    • 7.1 概述
    • 7.2.表格表达式
      • 7.2.1`FROM`条款
        • 7.2.1.1 联接表
        • 7.2.1.2表和列别名
        • 7.2.1.3子查询
        • 7.2.1.4表函数
        • 7.2.1.5`LATERAL`子查询
      • 7.2.2. The `WHERE` Clause
      • Note
      • 7.2.3 `GROUP BY`和`HAVING`条款
      • 7.2.4 `GROUPING SETS`、`CUBE`和`ROLLUP`
      • 7.2.5窗口函数处理
    • 7.3.选择列表
      • 7.3.1选择-列表项目
      • 7.3.2列标签
      • 7.3.3.`DISTINCT`
    • 7.4.组合查询(`UNION`、`INTERSECT`、`EXCEPT`)
    • 7.5.排序行(`ORDER BY`)
    • 7.6.`LIMIT`和`OFFSET`
    • 7.7.`VALUES`清单
    • 7.8.`WITH`查询(通用表表达式)
      • 7.8.1`WITH`中的`SELECT`
      • 7.8.2递归查询
        • 7.8.2.1搜索顺序
        • 7.8.2.2循环检测
      • 7.8.3公用表表达式物化
      • 7.8.4数据修改`WITH`
  • 第8章 数据类型
      • 兼容性
    • 8.1 数字类型
      • 8.1.1整数类型
      • 8.1.2任意精度数字
      • 8.1.3浮点类型
      • 8.1.4 串行类型
    • 8.2. Monetary Types
    • 8.3.字符类型
    • 8.4.二进制数据类型
      • 8.4.1-byte-byte-byte-byte-by-Hex格式-字节`bytea`
      • 8.4.2 `bytea`转义格式
      • 8.5.1日期/时间输入
        • 8.5.1.1日期
        • 8.5.1.2时报
        • 8.5.1.3时间戳
        • 8.5.1.4特殊价值
      • 8.5.2日期/时间输出
      • 8.5.3时区
      • 8.5.4间隔输入
    • 8.7.枚举类型
      • 8.7.1列举类型的声明
      • 8.7.2点餐
      • 8.7.3类型安全
      • 8.7.4实施细节
    • 8.8.几何类型
      • 8.8.1点
      • 8.8.2. Lines
      • 8.8.3. Line Segments
      • 8.8.4. Boxes
      • 8.8.5. Paths
      • 8.8.6多边形
      • 8.8.7圆圈
    • 8.9.网络地址类型
      • 8.9.1`inet`
      • 8.9.2.`cidr`
      • 8.9.3`inet`与`cidr`
      • 8.9.4 macaddr
      • 8.9.5.`macaddr8`
      • 8.10 位字符串类型
    • 8.10 位字符串类型
    • 8.11 文本搜索类型
      • 8.11.1.`tsvector`
      • 8.11.2.`tsquery`
    • 8.12.UUID类型
    • 8.13 XML类型
      • 8.13.1创建XML值
      • 8.13.2编码处理
      • 8.13.3访问XML值
    • 8.14 JSON类型
      • 8.14.1JSON输入输出语法
      • 8.14.2设计JSON文档
      • 8.14.3. `jsonb` Containment and Existence
      • 8.14.4`jsonb`索引
      • 8.14.5`jsonb`订阅
      • 8.14.6转换
      • 8.14.7jsonpath类型
    • 8.15 数组
      • 8.15.1数组类型声明
      • 8.15.2数组值输入
      • 8.15.3 访问数组
      • 8.15.4修改数组
      • 8.15.6数组输入输出语法
    • 8.16 复合类型
      • 8.16.1复合类型声明
      • 8.16.2构建综合价值观
      • 8.16.3访问复合类型
      • 8.16.4修改复合类型
      • 8.16.5在查询中使用复合类型
      • 8.16.6复合类型输入输出语法
    • 8.17 范围类型
      • 8.17.1内置范围和多范围类型
      • 8.17.2实例
      • 8.17.3包容性和排他性界限
      • 8.17.4无限(无界)范围
      • 8.17.5范围输入/输出
      • 8.17.6构建范围和多范围
      • 8.17.7离散范围类型
      • 8.17.8定义新的范围类型
      • 8.17.9索引
      • 8.17.10范围限制
    • 8.18 域类型
    • 8.19 对象标识符类型


这部分描述了PostgreSQL中SQL语言的使用。
我们从描述SQL的一般语法开始,然后解释如何创建结构来保存数据,如何填充数据库,以及如何查询它。
中间部分列出了SQL命令中可用的数据类型和函数。
其余部分处理了对调整数据库以获得最佳性能很重要的几个方面。

这一部分的信息是这样安排的,以便新手用户可以从头到尾地阅读,从而获得对主题的全面理解,而不必向前看太多遍。
这些章节旨在独立,以便高级用户可以根据自己的选择单独阅读章节。
这一部分的信息以专题单元的叙述方式呈现。
寻找特定命令的完整描述的读者应该参见第六部分。

这部分的读者应该知道如何连接到PostgreSQL数据库并发出SQL命令。
鼓励不熟悉这些问题的读者先阅读第一部分。
SQL命令通常使用PostgreSQL交互式终端psql输入,但也可以使用具有类似功能的其他程序。


本章描述了SQL的语法。
它为理解以下章节奠定了基础,这些章节将详细介绍如何应用SQL命令来定义和修改数据。

我们还建议已经熟悉SQL的用户仔细阅读本章,因为它包含几个在SQL数据库中实现不一致或特定于PostgreSQL的规则和概念。


第4章 SQL语法

本章描述了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');

这是三个命令的序列,每行一个(尽管这不是必需的;一行上可以有多个命令,并且可以有效地跨行拆分命令)。

此外,注释可以出现在SQL输入中。
它们不是标记,它们实际上等同于空格。

关于哪些标记标识命令以及哪些是操作数或参数,SQL语法并不是很一致。
前几个标记通常是命令名称,所以在上面的例子中,我们通常会说“SELECT”、“UPDATE”和“INSERT”命令。
但是例如,UPDATE命令总是需要一个SET标记出现在某个位置,INSERT的这种特殊变体也需要一个VALUES才能完整。
每个命令的精确语法规则在第六部分中描述。


4.1.1标识符和关键词

上面示例中的SELECTUPDATEVALUES等标记是关键字的示例,即在SQL语言中具有固定含义的单词。
标记MY_TABLEA标识符的示例。
它们根据使用它们的命令标识表、列或其他数据库对象的名称。
因此它们有时被简单地称为“名称”。
关键字和标识符具有相同的词汇结构,这意味着在不了解语言的情况下无法知道标记是标识符还是关键字。
关键字的完整列表可以在附录C.

SQL标识符和关键字必须以字母(a-z,但也包括带有变音符号和非拉丁字母的字母)或下划线(_)开头。
标识符或关键字中的后续字符可以是字母、下划线、数字(0-9)或美元符号($)。
请注意,根据SQL标准的字母,标识符中不允许使用美元符号,因此它们的使用可能会降低应用程序的可移植性。
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;

带引号的标识符可以包含任何字符,代码为零的字符除外。
(要包含双引号,请写两个双引号。)这允许构造否则不可能的表或列名,例如包含空格或&符号的名称。
长度限制仍然适用。

引用标识符也使其区分大小写,而未加引号的名称总是折叠成小写。
例如,标识符FOOfoo"foo"被PostgreSQL认为是相同的,但是"Foo""FOO"与这三个不同,并且彼此不同。
(在PostgreSQL中将未加引号的名称折叠成小写与SQL标准不兼容,该标准规定未加引号的名称应该折叠成大写。
因此,foo应该等同于"FOO"而不是"foo"根据标准。
如果您想编写可移植应用程序,建议您始终引用特定名称或永远不要引用它。)

带引号标识符的变体允许包含由其代码点标识的转义Unicode字符。
该变体以U&(大写或小写字母U后跟&符号)开头,紧接开头双引号之前,例如,中间没有任何空格U&"foo".(请注意,这会与运算符产生歧义&
在运算符周围使用空格来避免此问题。)在引号内,可以通过写入反斜杠后跟四位十六进制代码点号或反斜杠后跟加号后跟六位十六进制代码点号来指定Unicode字符的转义形式。
例如,标识符"data"可以写成

U&"d\0061t\+000061"

下面这个不那么琐碎的例子用西里尔字母写了俄语单词“slon”(大象):

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

如果需要与反斜杠不同的转义字符,可以在字符串后使用UESCAPE子句指定,例如:

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

转义字符可以是除十六进制数字、加号、单引号、双引号或空格字符以外的任何单个字符。
请注意,转义字符在UESCAPE之后是用单引号而不是双引号写的。

要按字面意思在标识符中包含转义字符,请写入两次。

4位或6位转义形式都可以用于指定UTF-16代理对,以组成代码点大于U+FFFF的字符,尽管6位形式的可用性在技术上使这变得不必要。
(代理对不直接存储,而是组合成一个代码点。)

如果服务器编码不是UTF-8,则由这些转义序列之一标识的Unicode代码点将转换为实际的服务器编码;如果不可能,则报告错误。


4.1.2常数

PostgreSQL中有三种隐式类型的常量:字符串、位字符串和数字。
常量也可以用显式类型指定,这可以使系统更准确地表示和更有效地处理。
这些替代方案将在以下小节中讨论。


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字符值

反斜杠后面的任何其他字符都是字面意思。
因此,要包含反斜杠字符,请写入两个反斜杠(\\)。
此外,除了''的正常方式之外,还可以通过写入\'在转义字符串中包含单引号。

创建的字节序列(尤其是使用八进制或十六进制转义)在服务器字符集编码中构成有效字符是您的责任。
一个有用的替代方法是使用Unicode转义或第4.1.2.3节中解释的替代Unicode转义语法;然后服务器将检查字符转换是否可能。


Caution : 如果配置参数standard_conforming_strings是off,那么PostgreSQL可以识别常规和转义字符串常量中的反斜杠转义。
但是,从PostgreSQL9.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字符。
例如,字符串'data'可以写成

U&'d\0061t\+000061'

下面这个不那么琐碎的例子用西里尔字母写了俄语单词“slon”(大象):

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

如果需要与反斜杠不同的转义字符,可以在字符串后使用UESCAPE子句指定,例如:

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

转义字符可以是除十六进制数字、加号、单引号、双引号或空格字符以外的任何单个字符。

要按字面意思在字符串中包含转义字符,请编写两次。

4位或6位转义形式都可以用于指定UTF-16代理对,以组成代码点大于U+FFFF的字符,尽管6位形式的可用性在技术上使这变得不必要。
(代理对不直接存储,而是组合成一个代码点。)

如果服务器编码不是UTF-8,则由这些转义序列之一标识的Unicode代码点将转换为实际的服务器编码;如果不可能,则报告错误。

此外,字符串常量的Unicode转义语法仅在配置参数standard_conforming_strings打开时有效。
否则,此语法可能会混淆解析SQL语句的客户端,从而导致SQL注入和类似的安全问题。
如果参数设置为off,此语法将被拒绝并显示错误消息。


4.1.2.4美元报价字符串常量

虽然指定字符串常量的标准语法通常很方便,但当所需的字符串包含许多单引号时,可能很难理解,因为每个单引号都必须加倍。
为了在这种情况下允许更具可读性的查询,PostgreSQL提供了另一种方法,称为“美元报价”,用于编写字符串常量。
美元报价的字符串常量由一个美元符号($)、一个可选的“标签”零个或多个字符组成,另一个美元符号,组成字符串内容的任意字符序列,一个美元符号,开始这个美元报价的同一个标签,以及一个美元符号。
例如,这里有两种不同的方法来指定字符串“黛安的马”使用美元报价:

$$Dianne's horse$$
$SomeTag$Dianne's horse$SomeTag$

请注意,在美元引号的字符串中,可以使用单引号而不需要转义。
事实上,美元引号字符串中的任何字符都不会转义:字符串内容始终按字面意思书写。
反斜杠并不特殊,美元符号也不特殊,除非它们是与开始标记匹配的序列的一部分。

可以通过在每个嵌套级别选择不同的标签来嵌套美元引号的字符串常量。
这最常用于编写函数定义。
例如:

$function$
BEGINRETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$function$

这里,序列$q$[\t\r\n\v\\]$q$代表一个美元引号的文字字符串[\t\r\n\v\\],当函数体被PostgreSQL执行时,它将被识别。
但是由于序列不匹配外部美元引号分隔符$function$,就外部字符串而言,它只是常量中的一些字符。

美元引号字符串的标记(如果有)遵循与未引号标识符相同的规则,只是它不能包含美元符号。
标记区分大小写,因此$tag$String content$tag$是正确的,但$TAG$String content$tag$不是。

关键字或标识符后面的美元引号字符串必须用空格分隔;否则美元引号分隔符将被视为前面标识符的一部分。

美元引用不是SQL标准的一部分,但它通常比符合标准的单引号语法更方便地编写复杂的字符串文字。
当在其他常量中表示字符串常量时,它特别有用,这在过程函数定义中经常需要。
使用单引号语法,上面示例中的每个反斜杠必须写成四个反斜杠,在解析原始字符串常量时,反斜杠将减少到两个,然后在函数执行期间重新解析内部字符串常量时,反斜杠将减少到一个。


4.1.2.5位串常量

位串常量看起来像普通字符串常量,在开头引号前有一个B(大写或小写)(没有中间空格),例如B'1001'
位串常量中允许的唯一字符是01

或者,位串常量可以用十六进制表示法指定,使用前导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节。

第4.2.9节所述,::``CAST()和函数调用语法也可用于指定任意表达式的运行时类型转换。
为了避免语法歧义, 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方言(如嵌入式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中运算符的优先级和关联性。
大多数运算符具有相同的优先级,并且是左关联的。
运算符的优先级和关联性是硬连线到解析器中的。
如果您希望以优先规则所暗示的其他方式解析包含多个运算符的表达式,请添加括号。


表4.2 运算符优先级(从最高到最低)

运算符/元素关联性描述
.表/列名分隔符
::PostgreSQL风格的类型转换
[ ]数组元素选择
+ -一元加号,一元减号
COLLATE排序规则选择
ATAT TIME ZONE
^
/ %`乘法,除法,模
+ -加法,减法
(任何其他运算符)所有其他本机和用户定义的运算符
BETWEEN IN LIKE ILIKE SIMILAR范围包含,设置成员资格,字符串匹配
< > = <= >= <>比较运算符
IS ISNULL NOTNULLIS TRUE``IS FALSE``IS NULLIS DISTINCT FROM
NOT逻辑否定
AND逻辑连词
OR逻辑析取

请注意,运算符优先级规则也适用于与上述内置运算符同名的用户定义运算符。
例如,如果您为某些自定义数据类型定义了“+”运算符,则无论您做什么,它都将具有与内置“+”运算符相同的优先级。

OPERATOR语法中使用模式限定的运算符名称时,例如:

SELECT 3 OPERATOR(pg_catalog.+) 4;

对于"任何其他运算符",OPERATOR构造具有表4.2中所示的默认优先级。
无论哪个特定运算符出现在OPERATOR()中,都是如此。


Note : PostgreSQL9.5之前的版本使用了略有不同的运算符优先级规则。
特别是,<= >=<>曾经被视为通用运算符;IS测试曾经具有更高的优先级;和NOT BETWEEN和相关构造的行为不一致,在某些情况下被认为具有NOT而不是BETWEEN的优先级。
这些规则被更改是为了更好地符合SQL标准,并减少逻辑等价构造处理不一致造成的混淆。
在大多数情况下,这些更改不会导致行为更改,或者可能“没有这样的运算符”可以通过添加括号来解决的故障。
然而,在某些极端情况下,查询可能会在没有报告任何解析错误的情况下更改行为。


4.2 值表达式

值表达式用于各种上下文中,例如在SELECT命令的目标列表中,作为INSERTUPDATE中的新列值,或者在许多命令的搜索条件中。
值表达式的结果有时称为标量,以区别于表表达式(表)的结果。
因此,值表达式也称为标量表达式(甚至简称表达式)。
表达式语法允许使用算术、逻辑、集合和其他操作从原始部分计算值。

值表达式是以下之一:

  • 常量或文字值
  • 列引用
  • 函数定义或预准备语句正文中的位置参数引用
  • 下标表达式
  • 字段选择表达式
  • 操作符调用
  • 函数调用
  • 聚合表达式
  • 窗口函数调用
  • A type cast
  • 排序规则表达式
  • 标量子查询
  • 数组构造函数
  • 行构造函数
  • 括号中的另一个值表达式(用于对子表达式进行分组并覆盖优先级)

除了这个列表之外,还有许多结构可以归类为表达式,但不遵循任何一般语法规则。
这些通常具有函数或运算符的语义学,并在第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 deptAS $$ SELECT * FROM dept WHERE name = $1 $$LANGUAGE SQL;

这里$1在函数被调用时引用第一个函数参数的值。


4.2.3下标

如果表达式产生数组类型的值,则可以通过写入来提取数组值的特定元素

expression[subscript]

或者可以通过写入提取多个相邻元素(“数组切片”)

expression[lower_subscript:upper_subscript]

(这里,括号[ ]是字面意思。)每个 subscript 本身就是一个表达式,它将被四舍五入到最接近的整数值。

一般来说,数组 expression 必须加括号,但是当要下标的表达式只是列引用或位置参数时,括号可以省略。
此外,当原始数组是多维的时,可以连接多个下标。例如:

mytable.arraycolumn[4]
mytable.two_d_column[17][34]
$1[10:42]
(arrayfunction(a,b))[42]

最后一个例子中的括号是必需的。
有关数组的更多信息,请参见第8.15节。


4.2.4领域选择

如果表达式产生复合类型(行类型)的值,则可以通过写入来提取行的特定字段

expression.fieldname

一般来说,行 expression 必须加括号,但是当要选择的表达式只是一个表引用或位置参数时,括号可以省略。
例如:

mytable.mycolumn
$1.somecolumn
(rowfunction(a,b)).col3

(因此,限定的列引用实际上只是字段选择语法的一个特例。)


一个重要的特例是从复合类型的表列中提取字段:

(compositecol).somefield
(mytable.compositecol).somefield

这里需要用括号来表示compositecol是列名而不是表名,或者在第二种情况下,mytable是表名而不是模式名。

您可以通过写入来请求复合值的所有字段`. :

(compositecol).*

这种符号的行为因上下文而异;详见第8.16.5节。


4.2.5 操作符调用

运算符调用有两种可能的语法:

  • expression operator expression (二进制中缀运算符)
  • operator expression (一元前缀运算符)

其中 operator 标记遵循第4.1.3节的语法规则,或者是关键字ANDORNOT之一,或者是以下形式的限定运算符名称:

OPERATOR(schema.operatorname)

存在哪些特定的运算符以及它们是一元还是二进制取决于系统或用户定义了哪些运算符。
第9章描述了内置运算符。


4.2.6函数调用

函数调用的语法是函数的名称(可能用模式名称限定),后跟括号中的参数列表:

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

例如,以下计算2的平方根:

sqrt(2)

内置功能列表在第9章。用户可以添加其他功能。

在某些用户不信任其他用户的数据库中发出查询时,请在编写函数调用时遵守第10.3节中的安全预防措施。

参数可以选择附加名称。有关详细信息,请参阅第4.3节。


Note : 采用复合类型的单个参数的函数可以选择使用字段选择语法调用,相反,字段选择可以用函数式编写。
也就是说,符号col(table)table.col是可以互换的。
这种行为不是SQL标准的,而是在PostgreSQL中提供的,因为它允许使用函数来模拟“计算字段”。
有关详细信息,请参阅第8.16.5节。


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_aggstring_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 之外还指定了DISTINCT,则所有ORDER BY表达式必须匹配聚合的正则参数;也就是说,您不能对未包含在DISTINCT列表中的表达式进行排序。


Note : 在聚合函数中指定DISTINCTORDER BY的能力是PostgreSQL扩展。

ORDER BY放入聚合的常规参数列表中(如前所述),用于对通用聚合和统计聚合的输入行进行排序,对于这些聚合,排序是可选的。
聚合函数的子类称为有序集聚合, order_by_clause 是必需的,通常是因为聚合的计算仅在其输入行的特定排序方面是合理的。
有序集聚合的典型示例包括秩和百分位数计算。
对于有序集聚合, order_by_clause 写在WITHIN GROUP (...)中,如上面的最终语法替代所示。
order_by_clause 中的表达式与常规聚合参数一样,每个输入行评估一次,根据 order_by_clause 的要求进行排序,并作为输入参数提供给聚合函数。
(这与非WITHIN GROUP order_by_clause 的情况不同,它不被视为聚合函数的参数。)前面的参数表达式WITHIN GROUP(如果有的话)被调用直接参数,以将它们与 order_by_clause 中列出的聚合参数<–atag–25/>区分开来。
与常规聚合参数不同,直接参数在每次聚合调用中只计算一次,而不是在每个输入行中计算一次。
这意味着只有当这些变量按GROUP BY分组时,它们才能包含变量。
这种限制与直接参数根本不在聚合表达式中一样。
直接参数通常用于百分位数分数之类的东西,它只作为每次聚合计算的单个值有意义。
直接参数列表可以为空;在这种情况下,只写()而不是(*)
(PostgreSQL实际上会接受任一拼写,但只有第一种方式符合SQL标准。)


有序集聚合调用的一个示例是:

SELECT percentile_cont(0.5) WITHIN GROUP (ORDER BY income) FROM households;percentile_cont |
| -- |
| 50489

它从表households中获得income列的第50个百分位数或中位数。
这里,0.5是直接参数;百分位数分数是跨行变化的值是没有意义的。

如果指定了FILTER,则仅将 filter_clause 计算结果为true的输入行提供给聚合函数;其他行将被丢弃。
例如:

SELECTcount(*) AS unfiltered,count(*) FILTER (WHERE i < 5) AS filtered
FROM generate_series(1,10) AS s(i);
unfilteredfiltered
     10 | 4 |

(1 row)


预定义的聚合函数在第9.21节中描述。
用户可以添加其他聚合函数。

聚合表达式只能出现在SELECT命令的结果列表或HAVING中。
禁止在其他子句中使用,例如WHERE,因为这些子句在形成聚合结果之前进行逻辑评估。

当聚合表达式出现在子查询中时(参见第4.2.11节和第9.23节),聚合通常会对子查询的行进行评估。
但是如果聚合的参数(以及 filter_clause 如果有的话)仅包含外部级别变量,则会发生异常:聚合属于最接近的外部级别,并在该查询的行上进行评估。
聚合表达式作为一个整体是它出现的子查询的外部引用,并充当该子查询的任何一个评估的常量。
关于仅出现在结果列表或HAVING子句中的限制适用于聚合所属的查询级别。


4.2.8窗口函数调用

一个窗口函数调用表示对查询所选行的某些部分应用一个类似聚合的函数。
与非窗口聚合调用不同,这与将所选行分组为单个输出行无关——每行在查询输出中保持独立。
但是,根据窗口函数调用的分组规范(PARTITION BYlist),窗口函数可以访问属于当前行组的所有行。
窗口函数调用的语法如下:

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 | GROUPS } frame_start [ frame_exclusion ]
{ RANGE | ROWS | GROUPS } BETWEEN frame_start AND frame_end [ frame_exclusion ]

在那里 frame_start 和 frame_end 可以成为

UNBOUNDED PRECEDING
offset PRECEDING
CURRENT ROW
offset FOLLOWING
UNBOUNDED FOLLOWING

而 frame_exclusion 可以成为

EXCLUDE CURRENT ROW
EXCLUDE GROUP
EXCLUDE TIES
EXCLUDE NO OTHERS

这里, 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 指定构成窗口框架的行集合,它是当前分区的子集。
框架中的行集合可以根据当前行的行而变化。
框架可以在RANGEROWSGROUPS模式下指定;在每种情况下,它都从 frame_start 运行到 frame_end 。
如果省略 frame_end ,则结束默认为CURRENT ROW

一个 frame_start 的UNBOUNDED PRECEDING意味着帧从分区的第一行开始,同样 frame_end 的UNBOUNDED FOLLOWING意味着帧以分区的最后一行结束。

RANGEGROUPS模式下,CURRENT ROWframe_start表示帧从当前行的第一个对等行开始(窗口的ORDER BY子句将该行排序为与当前行等效),而CURRENT ROWframe_end表示帧以当前行的最后一个对等行结束。
ROWS模式下,CURRENT ROW仅表示当前行。

在 offset PRECEDING和 offset FOLLOWING帧选项中, offset 必须是不包含任何变量、聚合函数或窗口函数的表达式。
offset 的含义取决于帧模式:

  • ROWS模式下, offset 必须产生一个非空、非负整数,该选项意味着帧在当前行之前或之后开始或结束指定数量的行。
  • GROUPS模式下, offset 必须再次产生一个非空、非负整数,并且该选项意味着帧在当前行的对等组之前或之后开始或结束指定数量的对等组,其中对等组是一组在ORDER BY排序中等效的行。
    (窗口定义中必须有一个ORDER BY子句才能使用GROUPS模式。)
  • RANGE模式下,这些选项要求ORDER BY子句仅指定一列。
    offset 指定当前行中该列的值与其在帧的前行或后行中的值之间的最大差值。
    offset 表达式的数据类型因排序列的数据类型而异。
    对于数字排序列,它通常与排序列的类型相同,但对于日期时间排序列,它是一个interval
    例如,如果排序列是datetimestamp类型,则可以写入RANGE BETWEEN '1 day' PRECEDING AND '10 days' FOLLOWING
    offset 仍然需要为非空和非负,尽管“非负”的含义取决于其数据类型。

在任何情况下,到框架末尾的距离都受到到分区末尾的距离的限制,因此对于靠近分区末尾的行,框架可能包含比其他地方更少的行。

请注意,在ROWSGROUPS模式下,0 PRECEDING0 FOLLOWING等价于CURRENT ROW
这通常也适用于RANGE模式,对于特定于数据类型的适当含义“零”。

该 frame_exclusion 选项允许从框架中排除当前行周围的行,即使它们将根据框架开始和框架结束选项被包括在内。
EXCLUDE CURRENT ROW从框架中排除当前行。
EXCLUDE GROUP从框架中排除当前行及其排序对等点。
EXCLUDE TIES从框架中排除当前行的任何对等点,但不排除当前行本身。
EXCLUDE NO OTHERS仅明确指定不排除当前行或其对等点的默认行为。

默认的成帧选项是RANGE UNBOUNDED PRECEDING,与RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW相同。
使用ORDER BY时,会将帧设置为从分区开始到当前行的最后一个ORDER BY对等体的所有行。
如果没有ORDER BY,这意味着分区的所有行都包含在窗口框架中,因为所有行都成为当前行的对等体。

限制是 frame_start 不能是UNBOUNDED FOLLOWING, frame_end 不能是UNBOUNDED PRECEDING,并且 frame_end 选项不能比 frame_start选项更早出现在上面的frame_start 和 frame_end 选项列表中-例如RANGE BETWEEN CURRENT ROW AND offset PRECEDING是不允许的。
但是,例如,ROWS BETWEEN 7 PRECEDING AND 8 PRECEDING是允许的,即使它永远不会选择任何行。

如果指定了FILTER,则仅将 filter_clause 计算结果为true的输入行提供给窗口函数;其他行将被丢弃。
只有聚合的窗口函数接受FILTER子句。

内置窗口函数如表9.63所示。
用户可以添加其他窗口函数。
此外,任何内置或用户定义的通用或统计聚合都可以用作窗口函数。
(有序集和假设集聚合目前不能用作窗口函数。)

使用 的语法用于将无参数聚合函数作为窗口函数调用,例如count(*) OVER (PARTITION BY x ORDER BY y)。 星号( )通常不用于特定于窗口的函数。
特定于窗口的函数不允许在函数参数列表中使用DISTINCTORDER BY

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

有关窗口函数的更多信息,请参见第3.5节、第9.22节和第7.2.5节。


4.2.9类型转换

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

CAST ( expression AS type )
expression::type

CAST语法符合SQL;该语法与::是历史PostgreSQL用法。

当强制转换应用于已知类型的值表达式时,它表示运行时类型转换。
只有在定义了合适的类型转换操作时,强制转换才会成功。
请注意,这与使用带有常量的强制转换略有不同,如第4.1.2.7节。
应用于未经修饰的字符串文字的强制转换表示将类型初始分配给文字常量值,因此对于任何类型都将成功(如果字符串文字的内容是数据类型可接受的输入语法)。

如果值表达式必须生成的类型没有歧义(例如,当它被分配给表列时),则通常可以省略显式类型转换;在这种情况下,系统将自动应用类型转换。
但是,自动转换仅针对系统目录中标记为“可以隐式应用”的转换。
必须使用显式转换语法调用其他转换。
此限制旨在防止意外转换被静默应用。

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

typename ( expression )

然而,这仅适用于名称也有效作为函数名称的类型。
例如,double precision不能以这种方式使用,但等效的float8可以。
此外,名称intervaltimetimestamp只能以这种方式使用,如果它们是双引号,因为语法冲突。
因此,使用类似函数的强制转换语法会导致不一致,应该避免。


Note : 类函数语法实际上只是一个函数调用。
当使用两种标准强制转换语法中的一种进行运行时转换时,它将在内部调用已注册的函数来执行转换。
按照惯例,这些转换函数与其输出类型具有相同的名称,因此“类函数语法”只不过是对底层转换函数的直接调用。
显然,这不是可移植应用程序应该依赖的东西。
有关详细信息,请参阅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附加到多个参数是错误的。
有关详细信息,请参阅第24.2节。)因此,这给出了与前面示例相同的结果:

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

但这是一个错误:

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

因为它尝试将排序规则应用于>运算符的结果,该结果属于不可排序数据类型boolean


4.2.11标量子查询

标量子查询是括号中的普通SELECT查询,它只返回一行和一列。
(有关编写查询的信息,请参见第7章。)执行SELECT查询,并在周围的值表达式中使用单个返回值。
使用返回多行或多列的查询作为标量子查询是错误的。
(但是,如果在特定执行期间,子查询没有返回任何行,则没有错误;标量结果被视为null。)子查询可以引用周围查询中的变量,这些变量将在子查询的任何一次评估期间充当常量。
另请参见第9.23节有关涉及子查询的其他表达式。

例如,以下查找每个州最大的城市人口:

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)


默认情况下,数组元素类型是成员表达式的通用类型,使用与UNIONCASE构造相同的规则确定(参见第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构造函数的任何强制转换都会自动传播到所有内部构造函数。

多维数组构造函数元素可以是任何产生适当类型数组的元素,而不仅仅是子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}

(1 row)


SELECT ARRAY(SELECT ARRAY[i, i*2] FROM generate_series(1,5) AS a(i));
array
{{1,2},{2,4},{3,6},{4,8},{5,10}}

(1 row)


子查询必须返回单个列。
如果子查询的输出列是非数组类型,则生成的一维数组将对子查询结果中的每一行都有一个元素,元素类型与子查询的输出列匹配。
如果子查询的输出列是数组类型,则结果将是相同类型但更高维度的数组;在这种情况下,所有子查询行必须生成相同维度的数组,否则结果不会是矩形的。

使用ARRAY构建的数组值的下标总是以1开头。
有关数组的更多信息,请参阅第8.15节。


4.2.13行构造函数

行构造函数是使用其成员字段的值构建行值(也称为复合值)的表达式。
行构造函数由关键字ROW、左括号、行字段值的零个或多个表达式(以逗号分隔)以及右括号组成。
例如:

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

当列表中有多个表达式时,关键字ROW是可选的。

行构造函数可以包含语法 rowvalue . ,它将扩展为行值元素的列表,就像在SELECT列表的顶层使用. 语法一样(参见第8.16.5节)。
例如,如果表tf1f2列,它们是相同的:

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

Note : 在PostgreSQL8.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 uniqueSELECT 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 NULLIS 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.24节。
行构造函数也可以用于子查询,如第9.23节所述。


4.2.14表达式评估规则

未定义子表达式的求值顺序。
特别是,运算符或函数的输入不一定按从左到右或任何其他固定顺序求值。

此外,如果一个表达式的结果可以通过仅计算它的某些部分来确定,那么其他子表达式可能根本不会被计算。
例如,如果一个人写道:

SELECT true OR somefunc();

那么就somefunc()根本不会被调用。
如果一个人写道:

SELECT somefunc() OR true;

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

因此,使用具有副作用的函数作为复杂表达式的一部分是不明智的。
WHEREHAVING子句中依赖副作用或评估顺序尤其危险,因为这些子句作为开发执行计划的一部分被广泛地重新处理。
这些子句中的布尔表达式(AND/OR/NOT组合)可以以布尔代数定律允许的任何方式重组。

当必须强制执行评估顺序时,可以使用CASE构造(参见第9.18节)。
例如,这是一种不可信的方法,试图避免在WHERE中被零除:

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并不是解决这些问题的万灵药。
上述技术的一个限制是它不能阻止常量子表达式的早期求值。
如第38.7节所述,标记为IMMUTABLE的函数和运算符可以在计划查询时而不是执行查询时求值。
因此,例如

SELECT CASE WHEN x > 0 THEN x ELSE 1/0 END FROM tab;

由于规划器试图简化常量子表达式,即使表中的每一行都x > 0,因此在运行时永远不会输入ELSEarm,也可能导致除以零失败。

虽然这个特定的例子可能看起来很傻,但在函数内执行的查询中可能会出现不明显涉及常量的相关情况,因为函数参数和局部变量的值可以作为常量插入到查询中以进行规划。
例如,在PL/pgSQL函数中,使用IF-THEN-ELSE语句来保护有风险的计算比仅仅将其嵌套在CASE表达式中要安全得多。

同样的另一个限制是CASE不能阻止计算包含在其中的聚合表达式,因为聚合表达式是在考虑SELECT列表或HAVING中的其他表达式之前计算的。
例如,以下查询可能会导致被零除法错误,尽管看起来已经对此进行了保护:

SELECT CASE WHEN min(employees) > 0THEN avg(expenses / employees)ENDFROM departments;

在所有输入行上同时计算min()avg()聚合,因此如果任何行的employees等于零,则在有机会测试min()的结果之前就会发生除以零错误。
相反,请使用WHEREFILTER子句来防止有问题的输入行首先到达聚合函数。


4.3.调用函数

PostgreSQL允许使用位置表示法或命名表示法调用具有命名参数的函数。
命名表示法对于具有大量参数的函数特别有用,因为它使参数和实际参数之间的关联更加明确和可靠。
在位置表示法中,函数调用以与函数声明中定义的参数值相同的顺序写入。
在命名表示法中,参数按名称与函数参数匹配,并且可以以任何顺序写入。
对于每种表示法,还要考虑函数参数类型的影响,记录在第10.3节中。

在这两种表示法中,具有函数声明中给出的默认值的参数根本不需要写入调用中。
但是这在命名表示法中特别有用,因为可以省略任何参数组合;而在位置表示法中,参数只能从右到左省略。

PostgreSQL还支持混合表示法,它结合了位置和命名表示法。
在这种情况下,首先写入位置参数,然后出现命名参数。

以下示例将使用以下函数定义说明所有三种符号的用法:

CREATE FUNCTION concat_lower_or_upper(a text, b text, uppercase boolean DEFAULT false)
RETURNS text
AS
$$SELECT CASEWHEN $3 THEN UPPER($1 || ' ' || $2)ELSE LOWER($1 || ' ' || $2)END;
$$
LANGUAGE SQL IMMUTABLE STRICT;

函数concat_lower_or_upper有两个强制参数,ab
此外,还有一个默认为false的可选参数uppercase
ab输入将被连接,并根据uppercase参数强制为大写或小写。
这个函数定义的其余细节在这里并不重要(更多信息见第38章)。


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)


支持基于“:=”的旧语法以实现向后兼容性:

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)


在上面的查询中,参数ab是按位置指定的,而uppercase是按名称指定的。
在这个例子中,除了留档之外,这几乎没有增加什么。
对于一个更复杂的函数,有许多具有默认值的参数,命名或混合表示法可以节省大量的编写时间,减少错误的机会。


Note : 调用聚合函数时,目前不能使用命名和混合调用符号(但当聚合函数用作窗口函数时,它们确实有效)。


第5章 数据定义

本章介绍如何创建保存数据的数据库结构。
在关系数据库中,原始数据存储在表中,因此,本章的大部分内容致力于解释如何创建和修改表,以及哪些功能可用于控制表中存储的数据。
随后,我们将讨论如何将表组织成模式,以及如何为表分配权限。
最后,我们将简要介绍影响数据存储的其他功能,如继承、表分区、视图、函数和触发器。


5.1 表格基础

关系数据库中的表很像纸上的表:它由行和列组成。
列的数量和顺序是固定的,每列都有一个名称。
行数是可变的——它反映了在给定时刻存储了多少数据。
SQL不保证表中行的顺序。

当读取表时,行将以未指定的顺序出现,除非明确要求排序。这将在第7章中介绍。
此外,SQL不为行分配唯一标识符,因此表中可能有几行完全相同。
这是作为SQL基础的数学模型的结果,但通常是不可取的。
在本章后面,我们将看到如何处理这个问题。

每一列都有一个数据类型。
数据类型限制可以分配给列的一组可能值,并为存储在列中的数据分配语义学,以便它可以用于计算。
例如,声明为数字类型的列不接受任意文本字符串,存储在这样的列中的数据可以用于数学计算。
相比之下,声明为字符串类型的列几乎可以接受任何类型的数据,但它不适合数学计算,尽管还有其他操作,如字符串连接。

PostgreSQL包含大量适合许多应用程序的内置数据类型。
用户还可以定义自己的数据类型。
大多数内置数据类型都有明显的名称和语义学,因此我们将详细解释推迟到第8章。
一些常用的数据类型是整数的integer,可能的小数的numeric,字符串的text,日期的date,一天中的时间值的time,以及包含日期和时间的值的timestamp

要创建表,请使用适当命名的CREATE TABLE命令。
在此命令中,您至少指定新表的名称、列的名称和每列的数据类型。
例如:

CREATE TABLE my_first_table (first_column text,second_column integer
);

这将创建一个名为my_first_table的表,其中包含两列。
第一列名为first_column,数据类型为text;第二列的名称second_column和类型为integer
表和列名遵循第4.1.1节中解释的标识符语法。
类型名称通常也是标识符,但也有一些例外。
请注意,列列表以逗号分隔,并用括号括起来。

当然,前面的例子是精心设计的。
通常,您会为表和列命名,以传达它们存储的数据类型。
所以让我们看一个更现实的例子:

CREATE TABLE products (product_no integer,name text,price numeric
);

numeric类型可以存储小数部分,这是典型的货币金额。)


Tips : 当您创建许多相互关联的表时,明智的做法是为表和列选择一致的命名模式。
例如,可以选择使用单数或复数名词来命名表名,这两种名称都受到一些理论家或其他人的青睐。

一个表可以包含多少列是有限制的。
根据列类型,它在250到1600之间。
然而,定义一个接近这么多列的表是非常不寻常的,而且通常是一个有问题的设计。

如果不再需要表,可以使用DROP TABLE命令将其删除。

DROP TABLE my_first_table;
DROP TABLE products;

尝试删除不存在的表是错误的。
然而,在SQL脚本文件中,在创建每个表之前无条件地尝试删除它是很常见的,忽略任何错误消息,这样无论表是否存在,脚本都可以工作。
(如果您愿意,您可以使用DROP TABLE IF EXISTS变体来避免错误消息,但这不是标准的SQL。)

如果您需要修改已经存在的表,请参阅本章后面的第5.6节。

通过目前讨论的工具,您可以创建功能齐全的表。
本章的其余部分涉及向表定义添加功能以确保数据完整性、安全性或便利性。
如果您现在渴望用数据填充表,您可以跳到第6章,稍后阅读本章的其余部分。


5.2.默认值

可以为列指定默认值。
当创建新行并且没有为某些列指定值时,这些列将用它们各自的默认值填充。
数据操作命令还可以显式请求将列设置为其默认值,而无需知道该值是什么。
(关于数据操作命令的详细信息在第6章。)

如果没有显式声明默认值,则默认值为空值。
这通常是有意义的,因为可以认为空值代表未知数据。

在表定义中,默认值列在列数据类型之后。
例如:

CREATE TABLE products (product_no integer,name text,price numeric DEFAULT 9.99
);

默认值可以是一个表达式,每当插入默认值时(而不是创建表时),都会对其进行评估。
一个常见的例子是timestamp列的默认值为CURRENT_TIMESTAMP,以便将其设置为行插入的时间。
另一个常见的例子是为每一行生成一个“序列号”。
在PostgreSQL中,这通常是通过以下方式完成的:

CREATE TABLE products (product_no integer DEFAULT nextval('products_product_no_seq'),...
);

其中nextval()函数提供来自序列对象的连续值(参见第9.17节)。
这种安排非常常见,以至于有一个特殊的简写:

CREATE TABLE products (product_no SERIAL,...
);

SERIAL速记将在第8.1.4节进一步讨论。


5.3.生成的列

生成的列是一个特殊的列,它总是从其他列计算出来的。
因此,它对于列就像视图对于表一样。
生成的列有两种:存储的和虚拟的。
存储的生成列在写入(插入或更新)时被计算,并像正常列一样占用存储空间。
虚拟生成的列不占用存储空间,在读取时被计算。
因此,虚拟生成的列类似于视图,存储的生成列类似于物化视图(除了它总是自动更新)。
PostgreSQL目前只实现存储生成的列。

要创建生成的列,请使用CREATE TABLE中的GENERATED ALWAYS AS子句,例如:

CREATE TABLE people (...,height_cm numeric,height_in numeric GENERATED ALWAYS AS (height_cm / 2.54) STORED
);

必须指定关键字STORED以选择生成列的存储类型。
有关详细信息,请参阅CREATE TABLE。

不能直接写入生成的列。
INSERTUPDATE命令中,不能为生成的列指定值,但可以指定关键字DEFAULT

考虑具有默认值的列和生成列之间的差异。
如果没有提供其他值,则在首次插入行时评估列默认值一次;每当行发生变化时,生成的列都会更新,并且不能被覆盖。
列默认值可能不引用表的其他列;生成表达式通常会这样做。
列默认值可以使用易失性函数,例如random()或引用当前时间的函数;生成的列不允许这样做。

有几个限制适用于生成列和涉及生成列的表的定义:

  • 生成表达式只能使用不可变函数,不能使用子查询或以任何方式引用当前行以外的任何内容。
  • 生成表达式不能引用另一个生成的列。
  • 生成表达式不能引用系统列,除了tableoid
  • 生成的列不能具有列默认值或标识定义。
  • 生成的列不能是分区键的一部分。
  • 外部表可以生成列。
    有关详细信息,请参阅CREATE FOREIGN TABLE。
  • 继承:
    • 如果父列是生成的列,则子列也必须是使用相同表达式生成的列。
      在子列的定义中,省略GENERATED子句,因为它将从父列复制。

    • 在多重继承的情况下,如果一个父列是生成的列,则所有父列都必须是生成的列并且具有相同的表达式。

    • 如果父列不是生成的列,则子列可以定义为生成的列或不是生成的列。


其他注意事项适用于生成列的使用。

  • 生成的列与它们的底层基本列分开维护访问权限。
    因此,可以对其进行安排,以便特定角色可以从生成的列中读取,但不能从底层基本列中读取。
  • 从概念上讲,生成的列在BEFORE触发器运行后更新。
    因此,对BEFORE触发器中的基本列所做的更改将反映在生成的列中。
    但相反,不允许访问BEFORE触发器中的生成列。
  • 生成的列将跳过逻辑复制,并且不能在CREATE PUBLICATION列列表中指定。

5.4 限制

数据类型是一种限制可以存储在表中的数据类型的方法。
然而,对于许多应用程序,它们提供的约束过于粗糙。
例如,包含产品价格的列可能只接受正值。
但是没有只接受正数的标准数据类型。
另一个问题是,您可能希望相对于其他列或行约束列数据。
例如,在包含产品信息的表中,每个产品编号应该只有一行。

为此,SQL允许您定义列和表的约束。
约束使您可以根据需要尽可能多地控制表中的数据。
如果用户试图将数据存储在违反约束的列中,则会引发错误。
即使该值来自默认值定义,这也适用。


5.4.1检查约束

检查约束是最通用的约束类型。
它允许您指定某个列中的值必须满足布尔(真值)表达式。
例如,要要求正数产品价格,您可以使用:

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price > 0)
);

如您所见,约束定义在数据类型之后,就像默认值定义一样。
默认值和约束可以按任何顺序列出。
检查约束由关键字CHECK和括号中的表达式组成。
检查约束表达式应该涉及这样约束的列,否则约束没有太大意义。

您还可以为约束指定一个单独的名称。
这澄清了错误消息,并允许您在需要更改约束时引用它。
语法是:

CREATE TABLE products (product_no integer,name text,price numeric CONSTRAINT positive_price CHECK (price > 0)
);

因此,要指定命名约束,请使用关键字CONSTRAINT,后跟标识符,后跟约束定义。
(如果您没有以这种方式指定约束名称,系统会为您选择一个名称。)

检查约束也可以引用几个列。
假设您存储一个正常价格和一个折扣价格,并且您希望确保折扣价格低于正常价格:

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price > 0),discounted_price numeric CHECK (discounted_price > 0),CHECK (price > discounted_price)
);

前两个约束应该看起来很熟悉。第三个使用新语法。
它不附加到特定列,而是在逗号分隔的列列表中作为单独的项目显示。
列定义和这些约束定义可以按混合顺序列出。

我们说前两个约束是列约束,而第三个是表约束,因为它与任何一个列定义分开编写。
列约束也可以写成表约束,而相反的情况不一定可能,因为列约束应该只引用它所附加的列。
(PostgreSQL不强制执行该规则,但如果您希望您的表定义与其他数据库系统一起使用,您应该遵循它。)上面的示例也可以写成:

CREATE TABLE products (product_no integer,name text,price numeric,CHECK (price > 0),discounted_price numeric,CHECK (discounted_price > 0),CHECK (price > discounted_price)
);

甚至:

CREATE TABLE products (product_no integer,name text,price numeric CHECK (price > 0),discounted_price numeric,CHECK (discounted_price > 0 AND price > discounted_price)
);

这是品味的问题。

名称可以像列约束一样分配给表约束:

CREATE TABLE products (product_no integer,name text,price numeric,CHECK (price > 0),discounted_price numeric,CHECK (discounted_price > 0),CONSTRAINT valid_discount CHECK (price > discounted_price)
);

应该注意的是,如果检查表达式的计算结果为真或空值,则满足检查约束。
由于如果任何操作数为空,大多数表达式都会计算为空值,因此它们不会阻止约束列中的空值。
为了确保列不包含空值,可以使用下一节中描述的非空约束。


Note : PostgreSQL不支持引用正在检查的新行或更新行以外的表数据的CHECK约束。
虽然违反此规则的CHECK约束在简单测试中可能有效,但它不能保证数据库不会达到约束条件为假的状态(由于随后涉及的其他行发生更改)。
这将导致数据库转储和恢复失败。
即使完整的数据库状态与约束一致,由于没有按满足约束的顺序加载行,恢复也可能失败。
如果可能,请使用UNIQUEEXCLUDEFOREIGN KEY约束来表示跨行和跨表限制。

如果您希望在行插入时对其他行进行一次性检查,而不是continuously-maintained一致性保证,则可以使用自定义触发器来实现。
(这种方法避免了转储/恢复问题,因为pg_dump在恢复数据之前不会重新安装触发器,因此在转储/恢复期间不会强制执行检查。)


Note : PostgreSQL假设CHECK约束的条件是不可变的,也就是说,它们总是为相同的输入行给出相同的结果。
这个假设证明了只有在插入或更新行时才检查CHECK约束,而不是在其他时候。
(上面关于不引用其他表数据的警告实际上是这种限制的一个特例。)

打破这种假设的一个常见方法是在CHECK表达式中引用用户定义的函数,然后更改该函数的行为。
PostgreSQL并不禁止这样做,但它不会注意到表中是否有行现在违反了CHECK约束。
这将导致后续的数据库转储和恢复失败。
处理这种更改的推荐方法是删除约束(使用ALTER TABLE),调整函数定义,并重新添加约束,从而针对所有表行重新检查它。


5.4.2 非空约束

非空约束只是指定列不得采用空值。
一个语法示例:

CREATE TABLE products (product_no integer NOT NULL,name text NOT NULL,price numeric
);

非空约束总是写成列约束。
非空约束在功能上等同于创建检查约束CHECK ( column_name IS NOT NULL),但在PostgreSQL中创建显式非空约束更有效。
缺点是不能为以这种方式创建的非空约束指定显式名称。

当然,一列可以有多个约束。
只需一个接一个地编写约束:

CREATE TABLE products (product_no integer NOT NULL,name text NOT NULL,price numeric NOT NULL CHECK (price > 0)
);

顺序无关紧要。它不一定决定检查约束的顺序。

NOT NULL约束相反:NULL约束。这并不意味着列必须为空,这肯定是无用的。相反,这只是选择列可能为空的默认行为。
SQL标准中不存在NULL约束,不应在可移植应用程序中使用。
(它只被添加到PostgreSQL中以与其他一些数据库系统兼容。)然而,一些用户喜欢它,因为它可以轻松地切换脚本文件中的约束。
例如,您可以从以下内容开始:

CREATE TABLE products (product_no integer NULL,name text NULL,price numeric NULL
);

然后在需要的地方插入NOT关键字。


Tips : 在大多数数据库设计中,大多数列都应标记为不为空。


5.4.3独特的限制

唯一约束确保一列或一组列中包含的数据在表中的所有行中是唯一的。
语法是:

CREATE TABLE products (product_no integer UNIQUE,name text,price numeric
);

当写入列约束时,并且:

CREATE TABLE products (product_no integer,name text,price numeric,UNIQUE (product_no)
);

当写成表约束时。

要为一组列定义唯一约束,请将其编写为表约束,列名以逗号分隔:

CREATE TABLE example (a integer,b integer,c integer,UNIQUE (a, c)
);

这指定指示列中的值组合在整个表中是唯一的,尽管任何一列不必(通常也不是)唯一的。

您可以通过通常的方式为唯一约束分配自己的名称:

CREATE TABLE products (product_no integer CONSTRAINT must_be_different UNIQUE,name text,price numeric
);

添加唯一约束将自动在约束中列出的列或列组上创建唯一的B树索引。
仅覆盖某些行的唯一性限制不能写成唯一约束,但可以通过创建唯一的部分索引来强制执行这样的限制。

通常,如果表中有多行约束中包含的所有列的值相等,则违反唯一约束。
默认情况下,在此比较中,两个空值不被视为相等。
这意味着即使存在唯一约束,也可以在至少一个受约束列中存储包含空值的重复行。
可以通过添加子句NULLS NOT DISTINCT来更改此行为,例如

CREATE TABLE products (product_no integer UNIQUE NULLS NOT DISTINCT,name text,price numeric
);

或者

CREATE TABLE products (product_no integer,name text,price numeric,UNIQUE NULLS NOT DISTINCT (product_no)
);

可以使用NULLS DISTINCT显式指定默认行为。
唯一约束中的默认null处理根据SQL标准implementation-defined,其他实现具有不同的行为。
因此,在开发旨在可移植的应用程序时要小心。


5.4.4主键

主键约束表示一列或一组列可以用作表中行的唯一标识符。
这要求值既是唯一的,也不为空。
因此,以下两个表定义接受相同的数据:

CREATE TABLE products (product_no integer UNIQUE NOT NULL,name text,price numeric
);
CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric
);

主键可以跨越多个列;语法类似于唯一约束:

CREATE TABLE example (a integer,b integer,c integer,PRIMARY KEY (a, c)
);

添加主键将自动在主键中列出的列或列组上创建唯一的B树索引,并强制将列标记为NOT NULL

一个表最多可以有一个主键。
(可以有任意数量的唯一和非空约束,它们在功能上几乎是一样的,但只有一个可以被识别为主键。)关系数据库理论规定每个表都必须有一个主键。
PostgreSQL不强制执行此规则,但通常最好遵循它。

主键对于留档目的和客户端应用程序都很有用。
例如,允许修改行值的GUI应用程序可能需要知道表的主键才能唯一地标识行。
如果已声明主键,数据库系统也有多种方式使用主键;例如,主键为引用其表的外键定义默认目标列。


5.4.5 外键

外键约束指定列(或一组列)中的值必须与另一个表的某行中出现的值匹配。
我们说这维护了两个相关表之间的引用完整性。

假设您有我们已经使用过几次的产品表:

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric
);

我们还假设您有一个存储这些产品订单的表。
我们希望确保订单表只包含实际存在的产品订单。
因此,我们在订单表中定义了一个引用产品表的外键约束:

CREATE TABLE orders (order_id integer PRIMARY KEY,product_no integer REFERENCES products (product_no),quantity integer
);

现在无法创建未出现在产品表中的非NULLproduct_no条目的订单。

我们说在这种情况下,订单表是引用表,产品表是引用表。
同样,有引用和引用列。

您还可以将上述命令缩短为:

CREATE TABLE orders (order_id integer PRIMARY KEY,product_no integer REFERENCES products,quantity integer
);

因为在没有列列表的情况下,被引用表的主键被用作被引用的列。

您可以按照通常的方式为外键约束分配自己的名称。

外键还可以约束和引用一组列。
像往常一样,它需要以表约束形式编写。
这是一个人为的语法示例:

CREATE TABLE t1 (a integer PRIMARY KEY,b integer,c integer,FOREIGN KEY (b, c) REFERENCES other_table (c1, c2)
);

当然,约束列的数量和类型需要与引用列的数量和类型相匹配。

有时外键约束的“其他表”是同一个表很有用;这称为自引用外键。
例如,如果您希望表的行表示树结构的节点,您可以编写

CREATE TABLE tree (node_id integer PRIMARY KEY,parent_id integer REFERENCES tree,name text,...
);

顶级节点将具有NULL的parent_id,而非NULL的parent_id 条目将被限制为引用表的有效行。

一个表可以有多个外键约束。这用于实现表之间的多对多关系。假设你有关于产品和订单的表格,但现在你想允许一个订单可能包含许多产品(上述结构不允许)。您可以使用此表结构:

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric
);CREATE TABLE orders (order_id integer PRIMARY KEY,shipping_address text,...
);CREATE TABLE order_items (product_no integer REFERENCES products,order_id integer REFERENCES orders,quantity integer,PRIMARY KEY (product_no, order_id)
);

请注意,主键与最后一个表中的外键重叠。

我们知道外键不允许创建与任何产品无关的订单。
但是如果在创建引用它的订单后删除了产品怎么办?SQL也允许您处理这个问题。
直观地说,我们有几个选项:

  • 禁止删除引用的产品
  • 也删除订单
  • 还要别的吗?

为了说明这一点,让我们在上面的多对多关系示例中实现以下策略:当有人想要删除订单仍然引用的产品(通过order_items)时,我们不允许它。
如果有人删除了订单,订单项目也会被删除:

CREATE TABLE products (product_no integer PRIMARY KEY,name text,price numeric
);CREATE TABLE orders (order_id integer PRIMARY KEY,shipping_address text,...
);CREATE TABLE order_items (product_no integer REFERENCES products ON DELETE RESTRICT,order_id integer REFERENCES orders ON DELETE CASCADE,quantity integer,PRIMARY KEY (product_no, order_id)
);

限制和级联删除是两个最常见的选项。
RESTRICT可防止删除引用行。
NO ACTION意味着如果在检查约束时仍然存在任何引用行,则会引发错误;如果不指定任何内容,则这是默认行为。
(这两种选择的本质区别在于NO ACTION允许将检查推迟到事务的后期,而RESTRICT不允许。)CASCADE指定当删除引用行时,引用它的行也应自动删除。
还有另外两个选项:SET NULLSET DEFAULT
当删除引用行时,这些选项会导致引用行中的引用列分别设置为空值或其默认值。
请注意,这些不能免除您观察任何约束。
例如,如果一个操作指定了SET DEFAULT,但默认值不满足外键约束,则该操作将失败。

ON DELETE操作的适当选择取决于相关表所代表的对象类型。
当引用表所代表的对象是被引用表所表示对象的组成部分并且不能独立存在时,CASCADE可能是合适的。
如果两个表表示独立的对象,则RESTRICTNO ACTION更合适;实际想要删除两个对象的应用程序必须明确说明这一点并运行两个删除命令。
在上面的示例中,订单项是订单的一部分,如果订单被删除,它们会自动删除,这很方便。
但是产品和订单是不同的东西,因此自动删除产品会导致某些订单项的删除可能被认为是有问题的。
如果外键关系表示可选信息,则可以使用SET NULLSET DEFAULT操作。
例如,如果产品表包含对产品经理的引用,并且产品经理条目被删除,则将产品的产品经理设置为null或默认值可能会很有用。

操作SET NULLSET DEFAULT可以采用列列表来指定要设置的列。
通常,设置外键约束的所有列;在某些特殊情况下,仅设置子集很有用。
考虑以下示例:

CREATE TABLE tenants (tenant_id integer PRIMARY KEY
);CREATE TABLE users (tenant_id integer REFERENCES tenants ON DELETE CASCADE,user_id integer NOT NULL,PRIMARY KEY (tenant_id, user_id)
);CREATE TABLE posts (tenant_id integer REFERENCES tenants ON DELETE CASCADE,post_id integer NOT NULL,author_id integer,PRIMARY KEY (tenant_id, post_id),FOREIGN KEY (tenant_id, author_id) REFERENCES users ON DELETE SET NULL (author_id)
);

如果没有指定列,外键也会将列tenant_id设置为null,但该列仍然需要作为主键的一部分。

类似于ON DELETE,还有ON UPDATE,当引用的列被更改(更新)时调用。
可能的操作是相同的,只是不能为SET NULLSET DEFAULT指定列列表。
在这种情况下,CASCADE意味着引用列的更新值应该被复制到引用行中。

通常,如果引用行的任何引用列为null,则引用行不需要满足外键约束。
如果将MATCH FULL添加到外键声明中,则只有当所有引用列都为null时,引用行才会转义满足约束(因此,null值和非null值的混合保证无法通过MATCH FULL约束)。
如果您不希望引用行能够避免满足外键约束,请将引用列声明为NOT NULL

外键必须引用的列要么是主键,要么形成唯一约束,要么是来自非部分唯一索引的列。
这意味着被引用的列总是有一个索引,以便有效地查找引用行是否匹配。
由于从被引用表中DELETE一行或UPDATE被引用列需要扫描引用表以查找与旧值匹配的行,因此通常最好也对引用列进行索引。
因为这并不总是需要的,并且有许多关于如何索引的选择,所以外键约束的声明不会自动在引用列上创建索引。

有关更新和删除数据的更多信息,请参见第6章。
另请参阅CREATE TABLE参考留档中的外键约束语法描述。


5.4.6排除限制

排除约束确保如果使用指定运算符在指定列或表达式上比较任何两行,则这些运算符比较中至少有一个将返回false或null。
语法是:

CREATE TABLE circles (c circle,EXCLUDE USING gist (c WITH &&)
);

另请参见CREATE TABLE ... CONSTRAINT ... EXCLUDE了解详细信息。

添加排除约束将自动创建约束声明中指定的类型的索引。


5.5.系统列

每个表都有几个由系统隐式定义的系统列。
因此,这些名称不能用作用户定义列的名称。
(请注意,这些限制与名称是否为关键字是分开的;引用名称不会让您逃脱这些限制。)您实际上不需要关心这些列;只要知道它们存在。

  • tableoid

    包含此行的表的OID。
    对于从分区表(参见第5.11节)或继承层次结构(参见第5.10节)中选择的查询,此列特别方便,因为没有它,很难判断一行来自哪个单独的表。
    可以将tableoidpg_class``oid列连接以获取表名。

  • xmin

    此行版本的插入事务的标识(事务ID)。
    (行版本是行的单个状态;行的每次更新都会为同一逻辑行创建一个新的行版本。)

  • cmin

    插入事务中的命令标识符(从零开始)。

  • xmax

    删除事务的标识(事务ID),未删除的行版本为零。
    此列在可见行版本中可能不为零。
    这通常表明删除事务尚未提交,或者尝试的删除已回滚。

  • cmax

    删除事务中的命令标识符,或零。

  • ctid

    行版本在其表中的物理位置。
    请注意,虽然ctid可用于非常快速地定位行版本,但如果VACUUM FULL更新或移动行,则行的ctid将发生变化。
    因此,ctid作为长期行标识符毫无用处。
    应使用主键来标识逻辑行。


事务标识符也是32位的数量。
在长期存在的数据库中,事务ID可以环绕。
如果有适当的维护程序,这不是一个致命的问题;详见第25章。
然而,长期依赖事务ID的唯一性是不明智的(超过10亿笔事务)。

命令标识符也是32位数量。
这会在单个事务中创建232(40亿)SQL命令的硬限制。
实际上,这个限制不是问题——请注意,限制是SQL命令的数量,而不是处理的行数。
此外,只有实际修改数据库内容的命令才会使用命令标识符。


5.6.修改表

当您创建一个表并意识到您犯了一个错误,或者应用程序的需求发生了变化时,您可以删除该表并重新创建它。
但是,如果表已经被数据填充,或者表被其他数据库对象引用(例如外键约束),这不是一个方便的选择。
因此,PostgreSQL提供了一系列命令来修改现有表。
请注意,这在概念上不同于更改表中包含的数据:这里我们对更改表的定义或结构感兴趣。

您可以:

  • 添加列
  • 删除列
  • 添加约束
  • 移除约束
  • 更改默认值
  • 更改列数据类型
  • 重命名列
  • 重命名表

所有这些操作都是使用ALTER TABLE命令执行的,其参考页面包含的详细信息超出了此处给出的内容。


5.6.1添加列

要添加列,请使用以下命令:

ALTER TABLE products ADD COLUMN description text;

新列最初用给定的任何默认值填充(如果未指定DEFAULT,则为null)。


Tips : 从PostgreSQL11中,添加默认值不变的列不再意味着在执行ALTER TABLE时需要更新表的每一行,而是在下次访问该行时返回默认值,并在重写表时应用,使得即使在大型表上ALTER TABLE也非常快。

但是,如果默认值是易失性的(例如,clock_timestamp()),则每行都需要使用执行ALTER TABLE时计算的值进行更新。
为了避免潜在的冗长更新操作,特别是如果您无论如何都打算用大部分非默认值填充列,最好添加没有默认值的列,使用UPDATE插入正确的值,然后添加任何所需的默认值,如下所述。

您还可以同时使用常用语法对列定义约束:

ALTER TABLE products ADD COLUMN description text CHECK (description <> '');

事实上,所有可以应用于CREATE TABLE列描述的选项都可以在这里使用。
但是请记住,默认值必须满足给定的约束,否则ADD将失败。
或者,您可以在正确填写新列后稍后添加约束(见下文)。


5.6.2删除列

要删除列,请使用以下命令:

ALTER TABLE products DROP COLUMN description;

列中的任何数据都会消失。
涉及该列的表约束也会被删除。
但是,如果该列被另一个表的外键约束引用,PostgreSQL不会静默删除该约束。
您可以通过添加CASCADE来授权删除依赖于该列的所有内容:

ALTER TABLE products DROP COLUMN description CASCADE;

请参阅第5.14节,了解这背后的一般机制。


5.6.3 添加约束

要添加约束,请使用表约束语法。
例如:

ALTER TABLE products ADD CHECK (name <> '');
ALTER TABLE products ADD CONSTRAINT some_name UNIQUE (product_no);
ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups;

要添加不能写入表约束的非空约束,请使用以下语法:

ALTER TABLE products ALTER COLUMN product_no SET NOT NULL;

将立即检查约束,因此表数据必须满足约束才能添加。


5.6.4 移除约束

要删除约束,你需要知道它的名字。
如果你给它起了名字,那就很容易了。
否则系统会分配一个生成的名字,你需要找到它。
psql命令\d tablename 在这里很有帮助;其他接口也可能提供一种检查表详细信息的方法。
然后命令是:

ALTER TABLE products DROP CONSTRAINT some_name;

(如果您正在处理生成的约束名称,例如$2,请不要忘记您需要双引号才能使其成为有效标识符。)

与删除列一样,如果要删除其他依赖的约束,则需要添加CASCADE
例如,外键约束依赖于引用列上的唯一键或主键约束。

这对除非空约束之外的所有约束类型都适用。
要删除非空约束,请使用:

ALTER TABLE products ALTER COLUMN product_no DROP NOT NULL;

(回想一下,非空约束没有名称。)


5.6.5更改列的默认值

要为列设置新的默认值,请使用以下命令:

ALTER TABLE products ALTER COLUMN price SET DEFAULT 7.77;

请注意,这不会影响表中的任何现有行,它只是更改未来INSERT命令的默认值。

要删除任何默认值,请使用:

ALTER TABLE products ALTER COLUMN price DROP DEFAULT;

这实际上与将默认值设置为null相同。
因此,删除未定义的默认值不是错误,因为默认值隐含为空值。


5.6.6更改列的数据类型

要将列转换为不同的数据类型,请使用以下命令:

ALTER TABLE products ALTER COLUMN price TYPE numeric(10,2);

只有当列中的每个现有条目都可以通过隐式强制转换转换为新类型时,这才会成功。
如果需要更复杂的转换,您可以添加一个USING子句,指定如何从旧值计算新值。

PostgreSQL将尝试将列的默认值(如果有)转换为新类型,以及涉及该列的任何约束。
但是这些转换可能会失败,或者可能会产生令人惊讶的结果。
通常最好在更改列类型之前删除列上的任何约束,然后再添加经过适当修改的约束。


5.6.7重命名列

要重命名列:

ALTER TABLE products RENAME COLUMN product_no TO product_number;

5.6.8重命名表

要重命名表:

ALTER TABLE products RENAME TO items;

5.7.特权

当一个对象被创建时,它被分配了一个所有者。
所有者通常是执行创建语句的角色。
对于大多数类型的对象,初始状态是只有所有者(或超级用户)可以对该对象做任何事情。
要允许其他角色使用它,必须授予权限。

有不同种类的特权:SELECTINSERTUPDATEDELETETRUNCATEREFERENCESTRIGGERCREATECONNECTTEMPORARYEXECUTEUSAGESETALTER SYSTEM
适用于特定对象的特权取决于对象的类型(表、函数等)。
有关这些特权含义的更多详细信息如下。
以下部分和章节还将向您展示如何使用这些特权。

修改或销毁对象的权利是作为对象所有者所固有的,并且不能被授予或撤销。
(但是,像所有特权一样,该权利可以由拥有角色的成员继承;见第22.3节。)

可以使用对象的适当类型的ALTER命令将对象分配给新所有者,例如

ALTER TABLE table_name OWNER TO new_owner;

超级用户总是可以做到这一点;普通角色只有在他们既是对象的当前所有者(或拥有角色的成员)又是新拥有角色的成员时才能做到这一点。

要分配权限,使用GRANT命令。
例如,如果joe是现有角色,而accounts是现有表,则可以通过以下方式授予更新表的权限:

GRANT UPDATE ON accounts TO joe;

写入ALL以代替特定权限将授予与对象类型相关的所有权限。

特殊的“角色”名称PUBLIC可用于向系统上的每个角色授予特权。
此外,当数据库有许多用户时,可以设置“组”角色来帮助管理权限——详情见第22章。

要撤销先前授予的权限,请使用适当命名的REVOKE命令:

REVOKE ALL ON accounts FROM PUBLIC;

通常,只有对象的所有者(或超级用户)可以授予或撤销对象的特权。
但是,可以授予特权“带授予选项”,这赋予接收者反过来授予其他人的权利。
如果授予选项随后被撤销,那么所有从该接收者那里获得特权的人(直接或通过授予链)都将失去特权。
有关详细信息,请参阅GRANT和REVOKE参考页面。

对象的所有者可以选择撤销自己的普通权限,例如将表设为自己和他人的只读。
但是所有者总是被视为持有所有授予选项,因此他们总是可以重新授予自己的权限。

可用的特权是:

  • SELECT

    允许从表、视图、物化视图或其他类似表的对象的任何列或特定列中SELECT
    还允许使用COPY TO
    引用UPDATEDELETEMERGE中的现有列值也需要此特权。
    对于序列,此特权还允许使用currval函数。
    对于大型对象,此特权允许读取对象。

  • INSERT

    允许将新行INSERT到表、视图等中。
    可以授予特定列,在这种情况下,只能在INSERT命令中分配这些列(因此其他列将接收默认值)。
    还允许使用COPY FROM

  • UPDATE

    允许UPDATE表、视图等的任何列或特定列(实际上,任何重要的UPDATE命令也需要SELECT权限,因为它必须引用表列来确定要更新哪些行,和/或计算列的新值。)SELECT ... FOR UPDATESELECT ... FOR SHARE除了SELECT权限之外,还需要至少一列的此权限。
    对于序列,此权限允许使用nextvalsetval函数。
    对于大型对象,此权限允许写入或截断对象。

  • DELETE

    允许从表、视图等中DELETE行(实际上,任何重要的DELETE命令也需要SELECT权限,因为它必须引用表列来确定要删除哪些行。)

  • TRUNCATE

    允许对表TRUNCATE

  • REFERENCES

    允许创建引用表或表的特定列的外键约束。

  • TRIGGER

    允许在表、视图等上创建触发器。

  • CREATE

    对于数据库,允许在数据库中创建新的模式和发布,并允许在数据库中安装受信任的扩展。
    对于架构,允许在架构中创建新对象。
    要重命名现有对象,您必须拥有该对象并对包含的架构拥有此权限。
    对于表空间,允许在表空间内创建表、索引和临时文件,并允许创建将表空间作为其默认表空间的数据库。
    请注意,撤销此权限不会改变现有对象的存在或位置。

  • CONNECT

    允许被授予者连接到数据库。
    此特权在连接启动时检查(除了检查pg_hba.conf施加的任何限制)。

  • TEMPORARY

    允许在使用数据库时创建临时表。

  • EXECUTE

    允许调用函数或过程,包括使用在函数之上实现的任何运算符。
    这是唯一适用于函数和过程的特权类型。

  • USAGE

    对于过程语言,允许使用该语言创建该语言的函数。
    这是唯一适用于过程语言的特权类型。
    对于模式,允许访问模式中包含的对象(假设对象自己的权限要求也得到满足)。
    本质上,这允许被授权者在模式中“查找”对象。
    如果没有此权限,仍然可以查看对象名称,例如通过查询系统目录。
    此外,撤销此权限后,现有会话可能包含先前执行过此查找的语句,因此这不是防止对象访问的完全安全的方法。
    对于序列,允许使用currvalnextval函数。
    对于类型和域,允许在创建表、函数和其他模式对象时使用类型或域。
    (请注意,此特权不控制类型的所有“使用”,例如查询中出现的类型值。
    它只防止创建依赖于类型的对象。
    此特权的主要目的是控制哪些用户可以创建对类型的依赖关系,这可能会防止所有者稍后更改类型。)对于外部数据包装器,允许使用外部数据包装器创建新服务器。
    对于外部服务器,允许使用服务器创建外部表。
    被授予者还可以创建、更改或删除与该服务器关联的自己的用户映射。

  • SET

    允许将服务器配置参数设置为当前会话中的新值。
    (虽然可以对任何参数授予此权限,但除了通常需要超级用户权限才能设置的参数之外,它毫无意义。)

  • ALTER SYSTEM

    允许使用ALTER SYSTEM命令将服务器配置参数配置为新值。


其他命令所需的权限列在相应命令的参考页面上。

PostgreSQL在创建对象时默认将某些类型对象的权限授予PUBLIC
默认情况下,不会授予PUBLIC对表、表列、序列、外部数据包装器、外部服务器、大型对象、模式、表空间或配置参数的权限。
对于其他类型的对象,授予PUBLIC的默认权限如下:CONNECTTEMPORARY(创建数据库的临时表)权限;EXECUTE权限;以及语言和数据类型(包括域)的USAGE权限。
当然,对象所有者可以REVOKE默认和明确授予的权限。
(为了最大的安全性,在创建对象的同一事务中发出REVOKE;然后没有其他用户可以使用该对象的窗口。)此外,可以使用ALTER DEFAULT PRIVILEGES命令覆盖这些默认权限设置。

表5.1显示了ACL(访问控制列表)值中用于这些特权类型的单字母缩写。
您将在下面列出的psql命令的输出中或在查看系统目录的ACL列时看到这些字母。


表5.1 ACL权限缩写

特权缩写适用对象类型
SELECTr(“read”)LARGE OBJECTSEQUENCETABLE(和类似表格的对象),表列
INSERTa(“append”)TABLE,表列
UPDATEw(“write”)LARGE OBJECTSEQUENCETABLE,表列
DELETEdTABLE
TRUNCATEDTABLE
REFERENCESxTABLE,表列
TRIGGERtTABLE
CREATECDATABASESCHEMATABLESPACE
CONNECTcDATABASE
TEMPORARYTDATABASE
EXECUTEXFUNCTION``PROCEDURE
USAGEUDOMAIN``FOREIGN DATA WRAPPER``FOREIGN SERVERLANGUAGESCHEMASEQUENCETYPE
SETsPARAMETER
ALTER SYSTEMAPARAMETER

表5.2使用上面显示的缩写总结了每种类型的SQL对象可用的权限。
它还显示了可用于检查每种对象类型的权限设置的psql命令。

表5.2.访问权限摘要

对象类型所有权限默认PUBLIC权限psql命令
DATABASECTcTc\l
DOMAINUU\dD+
FUNCTIONPROCEDUREXX\df+
FOREIGN DATA WRAPPERU\dew+
FOREIGN SERVERU\des+
LANGUAGEUU\dL+
LARGE OBJECTrw\dl+
PARAMETERsA\dconfig+
SCHEMAUC\dn+
SEQUENCErwU\dp
TABLE(和类似表的对象)arwdDxt\dp
arwx\dp
TABLESPACEC\db+
TYPEUU\dT+

已授予特定对象的权限显示为aclitem条目列表,每个条目的格式为:

grantee=privilege-abbreviation[*].../grantor

每个aclitem列出了特定授予者授予的一个被授予者的所有权限。
特定权限由表5.1中的单字母缩写表示,如果该权限是通过授予选项授予的,则附加 。 例如,calvin=r*w/hobbes指定角色calvin具有特权SELECTr)和授予选项( )以及不可授予的特权UPDATEw),两者均由角色hobbes授予。
如果calvin对不同授予者授予的同一对象也有一些权限,这些权限将显示为单独的aclitem条目。
aclitem中的空被授予者字段代表PUBLIC

例如,假设用户miriam创建mytable并执行:

GRANT SELECT ON mytable TO PUBLIC;
GRANT SELECT, UPDATE, INSERT ON mytable TO admin;
GRANT SELECT (col1), UPDATE (col1) ON mytable TO miriam_rw;

然后psql的\dp命令会显示:

=> \dp mytable

Access privileges

SchemaNameTypeAccess privilegesColumn privilegesPolicies
publicmytabletablemiriam=arwdDxt/miriam+col1: +
=r/miriam +miriam_rw=rw/miriam
admin=arw/miriam

(1 row)


如果给定对象的“访问权限”列为空,则表示该对象具有默认权限(即其在相关系统曲库中的权限条目为空)。
默认权限始终包括所有者的所有权限,并且可以包括PUBLIC的一些权限,具体取决于对象类型,如上所述。
对象上的第一个GRANTREVOKE将实例化默认权限(例如,miriam=arwdDxt/miriam),然后根据指定的请求修改它们。
类似地,条目显示在“列权限”仅适用于具有非默认权限的列。
(注意:为此目的,“默认权限”始终表示对象类型的内置默认权限。
(特权受到ALTER DEFAULT PRIVILEGES命令影响的对象将始终显示包含ALTER影响的显式特权条目。)(

请注意,所有者的隐式授权选项未在访问权限显示中标记。
只有当授权选项已明确授予某人时,才会出现` 。


5.8.行安全策略

除了通过GRANT提供的SQL标准特权系统之外,表还可以具有行安全策略,根据每个用户限制哪些行可以通过正常查询返回或通过数据修改命令插入、更新或删除。
此功能也称为行级安全性
默认情况下,表没有任何策略,因此如果用户根据SQL特权系统对表具有访问权限,则其中的所有行都可以平等地用于查询或更新。

对表启用行安全性(使用ALTER TABLE…启用行级别安全)时,行安全策略必须允许对表进行选择行或修改行的所有正常访问。
(但是,表的所有者通常不受行安全策略的约束。)如果表不存在策略,则使用默认拒绝策略,这意味着没有行可见或可以修改。
适用于整个表的操作,例如TRUNCATEREFERENCES,不受行安全性的约束。

行安全策略可以特定于命令、角色或两者。
可以指定策略应用于ALL命令,或SELECTINSERTUPDATEDELETE
可以为给定策略分配多个角色,并应用正常的角色成员资格和继承规则。

要根据策略指定哪些行是可见的或可修改的,需要一个返回布尔结果的表达式。
在来自用户查询的任何条件或函数之前,将对每一行评估此表达式。
(此规则的唯一例外是leakproof函数,它们保证不会泄漏信息;优化器可以选择在行安全检查之前应用此类函数。)表达式未返回的行true将不会被处理。
可以指定单独的表达式来提供对可见行和允许修改的行的独立控制。
策略表达式作为查询的一部分运行,并具有运行查询的用户的权限,尽管安全定义器函数可用于访问调用用户不可用的数据。

具有BYPASSRLS属性的超级用户和角色在访问表时总是绕过行安全系统。
表所有者通常也会绕过行安全性,尽管表所有者可以选择使用ALTER TABLE…强制行级别安全来受行安全性的约束。

启用和禁用行安全性以及向表添加策略始终是表所有者的特权。

使用CREATE POLICY命令创建策略,使用ALTER POLICY命令更改策略,使用DROP POLICY命令删除策略。
要启用和禁用给定表的行安全性,请使用ALTER TABLE命令。

每个策略都有一个名称,并且可以为一个表定义多个策略。
由于策略是特定于表的,因此表的每个策略都必须具有唯一的名称。
不同的表可能具有相同名称的策略。

当多个策略应用于给定查询时,它们使用OR(对于默认的许可策略)或AND(对于限制性策略)进行组合。
这类似于给定角色拥有它们所属的所有角色的权限的规则。
许可策略与限制性策略将在下面进一步讨论。

作为一个简单的示例,下面是如何在account关系上创建一个策略,以仅允许managers角色的成员访问行,并且仅允许其帐户的行:

CREATE TABLE accounts (manager text, company text, contact_email text);ALTER TABLE accounts ENABLE ROW LEVEL SECURITY;CREATE POLICY account_managers ON accounts TO managersUSING (manager = current_user);

上面的策略隐式提供了一个与其USING子句相同的WITH CHECK子句,因此约束既适用于命令选择的行(因此管理器不能SELECTUPDATEDELETE属于不同管理器的现有行),也适用于命令修改的行(因此不能通过INSERTUPDATE创建属于不同管理器的行)。

如果未指定角色,或者使用了特殊的用户名PUBLIC,则该策略适用于系统上的所有用户。
要允许所有用户仅访问users表中自己的行,可以使用一个简单的策略:

CREATE POLICY user_policy ON usersUSING (user_name = current_user);

这与前面的示例类似。

要对添加到表中的行与可见行使用不同的策略,可以组合多个策略。
这对策略将允许所有用户查看users表中的所有行,但只修改自己的:

CREATE POLICY user_sel_policy ON usersFOR SELECTUSING (true);
CREATE POLICY user_mod_policy ON usersUSING (user_name = current_user);

SELECT命令中,这两个策略使用OR组合,最终效果是可以选择所有行。
在其他命令类型中,只有第二个策略适用,因此效果与之前相同。

也可以使用ALTER TABLE命令禁用行安全性。
禁用行安全性不会删除表中定义的任何策略;它们只是被忽略。
然后表中的所有行都是可见的和可修改的,受标准SQL权限系统的约束。

下面是如何在生产环境中使用此功能的更大示例。
passwd模拟Unix密码文件:

-- Simple passwd-file based example
CREATE TABLE passwd (user_name             text UNIQUE NOT NULL,pwhash                text,uid                   int  PRIMARY KEY,gid                   int  NOT NULL,real_name             text NOT NULL,home_phone            text,extra_info            text,home_dir              text NOT NULL,shell                 text NOT NULL
);CREATE ROLE admin;  -- Administrator
CREATE ROLE bob;    -- Normal user
CREATE ROLE alice;  -- Normal user
-- Populate the table
INSERT INTO passwd VALUES('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash');
INSERT INTO passwd VALUES('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh');
INSERT INTO passwd VALUES('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh');
-- Be sure to enable row-level security on the table
ALTER TABLE passwd ENABLE ROW LEVEL SECURITY;
-- Create policies
-- Administrator can see all rows and add any rows
CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true);
-- Normal users can view all rows
CREATE POLICY all_view ON passwd FOR SELECT USING (true);
-- Normal users can update their own records, but
-- limit which shells a normal user is allowed to set
CREATE POLICY user_mod ON passwd FOR UPDATEUSING (current_user = user_name)WITH CHECK (current_user = user_name ANDshell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh'));
-- Allow admin all normal rights
GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin;
-- Users only get select access on public columns
GRANT SELECT(user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell)ON passwd TO public;
-- Allow users to update certain columns
GRANT UPDATE(pwhash, real_name, home_phone, extra_info, shell)ON passwd TO public;

与任何安全设置一样,测试并确保系统按预期运行非常重要。
使用上面的示例,这表明权限系统正在正常工作。

-- admin can view all rows and fields
postgres=> set role admin;
SET
postgres=> table passwd;
user_namepwhashuidgidreal_namehome_phoneextra_infohome_dirshell
adminxxx00Admin111-222-3333/root/bin/dash
bobxxx11Bob123-456-7890/home/bob/bin/zsh
alicexxx21Alice098-765-4321/home/alice/bin/zsh

(3 rows)


-- Test what Alice is able to do
postgres=> set role alice;
SET
postgres=> table passwd;
ERROR:  permission denied for table passwd
postgres=> select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd;
user_namereal_namehome_phoneextra_infohome_dirshell
adminAdmin111-222-3333/root/bin/dash
bobBob123-456-7890/home/bob/bin/zsh
aliceAlice098-765-4321/home/alice/bin/zsh

(3 rows)


postgres=> update passwd set user_name = 'joe';
ERROR:  permission denied for table passwd
-- Alice is allowed to change her own real_name, but no others
postgres=> update passwd set real_name = 'Alice Doe';
UPDATE 1
postgres=> update passwd set real_name = 'John Doe' where user_name = 'admin';
UPDATE 0
postgres=> update passwd set shell = '/bin/xx';
ERROR:  new row violates WITH CHECK OPTION for "passwd"
postgres=> delete from passwd;
ERROR:  permission denied for table passwd
postgres=> insert into passwd (user_name) values ('xxx');
ERROR:  permission denied for table passwd
-- Alice can change her own password; RLS silently prevents updating other rows
postgres=> update passwd set pwhash = 'abc';
UPDATE 1

到目前为止,构建的所有策略都是许可策略,这意味着当应用多个策略时,它们使用“OR”布尔运算符组合。
虽然许可策略可以构建为只允许在预期情况下访问行,但将许可策略与限制性策略(记录必须传递,哪些使用“AND”布尔运算符组合)组合可能更简单。
在上面示例的基础上,我们添加了一个限制性策略,要求管理员通过本地Unix套接字连接以访问passwd表的记录:

CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO adminUSING (pg_catalog.inet_client_addr() IS NULL);

然后我们可以看到,由于限制性策略,通过网络连接的管理员将看不到任何记录:

=> SELECT current_user;
current_user
admin

(1 row)


=> select inet_client_addr();
inet_client_addr
127.0.0.1

(1 row)


=> TABLE passwd;
user_namepwhashuidgidreal_namehome_phoneextra_infohome_dirshell

(0 rows)


UPDATE 0

引用完整性检查,例如唯一键或主键约束和外键引用,总是绕过行安全性以确保数据完整性得到维护。
在开发模式和行级策略时必须小心,以避免通过这种引用完整性检查“隐蔽通道”泄漏信息。

在某些情况下,确保没有应用行安全性非常重要。
例如,在进行备份时,如果行安全性默默地导致备份中省略某些行,那可能是灾难性的。
在这种情况下,您可以将row_security配置参数设置为off
这本身不会绕过行安全性;如果任何查询的结果会被策略过滤,它会抛出错误。
然后可以调查并修复错误的原因。

在上面的示例中,策略表达式只考虑要访问或更新的行中的当前值。
这是最简单和性能最好的情况;如果可能,最好设计行安全应用程序以这种方式工作。
如果需要查询其他行或其他表来做出策略决策,可以使用策略表达式中的子SELECT或包含SELECTs的函数来完成。
但是要注意,如果不小心,这种访问可能会创建竞争条件,从而允许信息泄漏。
例如,考虑以下表设计:

-- definition of privilege groups
CREATE TABLE groups (group_id int PRIMARY KEY,group_name text NOT NULL);INSERT INTO groups VALUES(1, 'low'),(2, 'medium'),(5, 'high');GRANT ALL ON groups TO alice;  -- alice is the administrator
GRANT SELECT ON groups TO public;
-- definition of users' privilege levels
CREATE TABLE users (user_name text PRIMARY KEY,group_id int NOT NULL REFERENCES groups);INSERT INTO users VALUES('alice', 5),('bob', 2),('mallory', 2);GRANT ALL ON users TO alice;
GRANT SELECT ON users TO public;
-- table holding the information to be protected
CREATE TABLE information (info text,group_id int NOT NULL REFERENCES groups);INSERT INTO information VALUES('barely secret', 1),('slightly secret', 2),('very secret', 5);ALTER TABLE information ENABLE ROW LEVEL SECURITY;
-- a row should be visible to/updatable by users whose security group_id is
-- greater than or equal to the row's group_id
CREATE POLICY fp_s ON information FOR SELECTUSING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
CREATE POLICY fp_u ON information FOR UPDATEUSING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user));
-- we rely only on RLS to protect the information table
GRANT ALL ON information TO public;

现在假设alice希望更改“稍微秘密”的信息,但决定不应该信任mallory来处理该行的新内容,所以她这样做了:

BEGIN;
UPDATE users SET group_id = 1 WHERE user_name = 'mallory';
UPDATE information SET info = 'secret from mallory' WHERE group_id = 2;
COMMIT;

这看起来很安全;没有窗口可以让mallory看到“来自马洛里的秘密”字符串。
但是,这里有一个竞争条件。
如果mallory同时做,比如说,

SELECT * FROM information WHERE group_id = 2 FOR UPDATE;

她的事务处于READ COMMITTED模式,她可以看到“mallory的秘密”。
如果她的事务在alice之后到达information行,就会发生这种情况。
它阻塞等待alice的事务提交,然后通过FOR UPDATE子句获取更新的行内容。
但是,它不会从users那里获取隐式SELECT的更新行,因为子SELECT没有FOR UPDATE;相反,users行是使用查询开始时拍摄的快照读取的。
因此,策略表达式测试mallory权限级别的旧值,并允许她查看更新的行。

有几种方法可以解决这个问题。
一个简单的答案是在子SELECT行安全策略中使用SELECT ... FOR SHARE
但是,这需要授予受影响的用户对被引用表(此处为users)的UPDATE特权,这可能是不可取的。
(但是可以应用另一个行安全策略来阻止他们实际行使该特权;或者子SELECT可以嵌入到安全定义器函数中。)此外,在被引用表上大量并发使用行共享锁可能会带来性能问题,特别是在更新频繁的情况下。
如果被引用表的更新不频繁,另一个实用的解决方案是在更新被引用表时对其进行ACCESS EXCLUSIVE锁,这样就没有并发事务可以检查旧行值。
或者,可以在提交引用表的更新之后,在进行依赖于新的安全情况的更改之前,等待所有并发事务结束。

有关其他详细信息,请参阅创建策略和更改表。


5.9.模式

PostgreSQL数据库集群包含一个或多个命名数据库。
角色和一些其他对象类型在整个集群中共享。
与服务器的客户端连接只能访问单个数据库中的数据,该数据库在连接请求中指定的数据库中。


Note : 集群的用户不一定拥有访问集群中每个数据库的特权。
共享角色名称意味着同一个集群中的两个数据库中不能有不同的角色,比如joe;但是系统可以配置为只允许joe访问一些数据库。

数据库包含一个或多个命名模式,这些模式又包含表。
模式还包含其他类型的命名对象,包括数据类型、函数和运算符。
相同的对象名称可以在不同的模式中使用而不会发生冲突;例如,schema1myschema都可以包含名为mytable的表。
与数据库不同,模式没有严格的分离:用户可以访问他们连接到的数据库中任何模式中的对象,如果他们有权限的话。

可能想要使用模式的原因有几个:

  • 允许多个用户在不相互干扰的情况下使用一个数据库。
  • 将数据库对象组织成逻辑组以使其更易于管理。
  • 第三方应用程序可以放入单独的模式中,这样它们就不会与其他对象的名称发生冲突。

模式类似于操作系统级别的目录,只是模式不能嵌套。


5.9.1创建架构

要创建架构,请使用CREATE ScheMA命令。
为架构指定您选择的名称。
例如:

CREATE SCHEMA myschema;

要在模式中创建或访问对象,请编写一个限定名称,该名称由模式名称和以点分隔的表名称组成:

schema.table

这适用于任何需要表名的地方,包括下面章节中讨论的表修改命令和数据访问命令。
(为了简洁起见,我们将只讨论表,但同样的想法也适用于其他类型的命名对象,如类型和函数。)

实际上,更通用的语法

database.schema.table

也可以使用,但目前这只是为了形式上符合SQL标准。
如果您编写库名,它必须与您连接的数据库相同。

因此,要在新模式中创建表,请使用:

CREATE TABLE myschema.mytable (...
);

如果架构为空(其中的所有对象都已删除),请使用:

DROP SCHEMA myschema;

要删除包含所有包含对象的模式,请使用:

DROP SCHEMA myschema CASCADE;

请参阅第5.14节,了解这背后的一般机制。

通常,您会希望创建由其他人拥有的模式(因为这是将用户的活动限制在定义明确的命名空间的方法之一)。
其语法是:

CREATE SCHEMA schema_name AUTHORIZATION user_name;

您甚至可以省略模式名称,在这种情况下,模式名称将与用户名相同。
请参阅第5.9.6节了解这有何用处。

pg_开头的架构名称保留用于系统目的,用户无法创建。


5.9.2公共模式

在前面的部分中,我们创建了没有指定任何模式名称的表。
默认情况下,这些表(和其他对象)会自动放入名为“public”的模式中。
每个新数据库都包含这样的模式。
因此,以下内容是等效的:

CREATE TABLE products ( ... );

和:

CREATE TABLE public.products ( ... );

5.9.3架构搜索路径

限定的名称写起来很乏味,而且通常最好不要将特定的模式名称连接到应用程序中。
因此,表通常由非限定名称引用,这些名称仅包含表名。
系统通过遵循搜索路径来确定哪个表,这是要查找的模式列表。
搜索路径中的第一个匹配表被认为是想要的。
如果搜索路径中没有匹配,则会报告错误,即使数据库中的其他模式中存在匹配表名。

在不同模式中创建同名对象的能力使得每次都引用相同对象的查询变得复杂。
它还为用户打开了恶意或意外更改其他用户查询行为的可能性。
由于查询中不合格名称的流行以及它们在PostgreSQL内部的使用,将模式添加到search_path有效地信任所有对该模式具有CREATE权限的用户。
当您运行普通查询时,能够在您的搜索路径模式中创建对象的恶意用户可以控制并执行任意SQL函数,就像您执行它们一样。

搜索路径中命名的第一个架构称为当前架构。
除了是搜索到的第一个架构之外,如果CREATE TABLE命令没有指定架构名称,它还是将在其中创建新表的架构。

要显示当前搜索路径,请使用以下命令:

SHOW search_path;

在默认设置中,这将返回:

search_path
“$user”, public

第一个元素指定要搜索与当前用户同名的模式。
如果不存在这样的模式,则忽略该条目。
第二个元素指的是我们已经看到的公共模式。

搜索路径中存在的第一个模式是创建新对象的默认位置。
这就是默认情况下在公共模式中创建对象的原因。
当在没有模式限定(表修改、数据修改或查询命令)的任何其他上下文中引用对象时,搜索路径将被遍历,直到找到匹配的对象。
因此,在默认配置中,任何未限定的访问只能再次引用公共模式。

要将我们的新模式放入路径中,我们使用:

SET search_path TO myschema,public;

(我们在这里省略了$user,因为我们没有立即需要它。)然后我们可以在没有模式限定的情况下访问表:

DROP TABLE mytable;

此外,由于myschema是路径中的第一个元素,默认情况下会在其中创建新对象。

我们还可以写:

SET search_path TO myschema;

那么我们就不能在没有显式限定的情况下访问公共模式。
公共模式没有什么特别之处,除了它默认存在。
它也可以被删除。

有关操纵模式搜索路径的其他方法,请参见第9.26节。

搜索路径对数据类型名称、函数名称和运算符名称的工作方式与对表名称的工作方式相同。
数据类型和函数名称可以以与表名称完全相同的方式进行限定。
如果您需要在表达式中写入限定的运算符名称,有一个特殊规定:您必须编写

OPERATOR(schema.operator)

这是避免句法歧义所必需的。
一个例子是:

SELECT 3 OPERATOR(pg_catalog.+) 4;

在实践中,通常依赖于运算符的搜索路径,以免编写如此丑陋的内容。


5.9.4架构和特权

默认情况下,用户不能访问他们不拥有的模式中的任何对象。
要允许这样做,模式的所有者必须授予模式的USAGE特权。
默认情况下,每个人都对模式public拥有该特权。
要允许用户使用模式中的对象,可能需要授予适合对象的其他特权。

还可以允许用户在其他人的模式中创建对象。
要允许这样做,需要授予模式的CREATE特权。
在从PostgreSQL14或更早版本升级的数据库中,每个人都拥有模式public的特权。
一些使用模式要求撤销该特权:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

(第一个“公共”是模式,第二个“公共”意味着“每个用户”。
在第一种意义上,它是一个标识符,在第二种意义上,它是一个关键词,因此大写不同;回顾第4.1.1节的指南。)


5.9.5系统目录模式

除了public和用户创建的模式之外,每个数据库都包含一个pg_catalog模式,其中包含系统表和所有内置数据类型、函数和运算符。
pg_catalog始终有效地成为搜索路径的一部分。
如果路径中没有显式命名,那么在搜索路径的模式之前会隐式搜索它。
这确保了始终可以找到内置名称。
但是,如果您希望用户定义的名称覆盖内置名称,则可以显式将pg_catalog放在搜索路径的末尾。

由于系统表名称以pg_开头,因此最好避免使用此类名称,以确保在将来某个版本定义了与表名称相同的系统表时不会发生冲突。
(使用默认搜索路径,对表名称的非限定引用将被解析为系统表。)系统表将继续遵循名称以pg_开头的约定,因此只要用户避免使用pg_前缀,它们就不会与非限定用户表名称冲突。


5.9.6 使用模式

模式可用于以多种方式组织数据。
安全模式使用模式可防止不受信任的用户更改其他用户查询的行为。
当数据库不使用安全模式使用模式时,希望安全查询该数据库的用户将在每个会话开始时采取保护措施。
具体来说,他们将通过设置search_path为空字符串或以其他方式从search_path中删除非超级用户可写的模式来开始每个会话。
默认配置很容易支持以下几种使用模式:

  • 将普通用户限制为用户私有模式。
    要实现此模式,首先确保没有模式具有公共CREATE权限。
    然后,对于每个需要创建非临时对象的用户,创建一个与该用户同名的模式,例如CREATE SCHEMA alice AUTHORIZATION alice.(回想一下,默认搜索路径以$user开头,它解析为用户名。
    因此,如果每个用户都有单独的模式,他们默认访问自己的模式。)此模式是安全模式使用模式,除非不受信任的用户是数据库所有者或持有CREATEROLE特权,在这种情况下不存在安全模式使用模式。
    在PostgreSQL15及更高版本中,默认配置支持这种使用模式。
    在以前的版本中,或者使用从以前版本升级的数据库时,您需要从public模式中删除公共CREATE特权(发出REVOKE CREATE ON SCHEMA public FROM PUBLIC)。
    然后考虑为模式pg_catalog中类似对象命名的对象审核public模式。
  • 通过修改postgresql.conf或发出ALTER ROLE ALL SET search_path = "$user"从默认搜索路径中删除公共模式。
    然后,授予在公共模式中创建的权限。
    只有限定名称才会选择公共模式对象。
    虽然限定表引用很好,但调用公共模式中的函数将是不安全或不可靠的。
    如果您在公共模式中创建函数或扩展,请改用第一种模式。
    否则,与第一种模式一样,这是安全的,除非不受信任的用户是数据库所有者或持有CREATEROLE特权。
  • 保留默认搜索路径,并授予在公共模式中创建的权限。
    所有用户都隐式访问公共模式。
    这模拟了模式根本不可用的情况,提供了从非模式感知世界的平滑过渡。
    然而,这从来都不是一个安全模式。
    只有当数据库有单个用户或几个相互信任的用户时,它才是可以接受的。
    在从PostgreSQL 14或更早版本升级的数据库中,这是默认设置。

对于任何模式,要安装共享应用程序(供每个人使用的表、第三方提供的附加功能等),请将它们放入单独的模式中。
请记住授予适当的权限以允许其他用户访问它们。
然后,用户可以通过使用模式名称限定名称来引用这些附加对象,或者他们可以根据自己的选择将附加模式放入搜索路径中。


5.9.7便携性

在SQL标准中,不存在同一模式中的对象由不同用户拥有的概念。
此外,一些实现不允许您创建与其所有者名称不同的模式。
事实上,在仅实现标准中指定的基本模式支持的数据库系统中,模式和用户的概念几乎是等价的。
因此,许多用户认为限定名称实际上由 user_name . table_name 组成。
如果您为每个用户创建每个用户的模式,这就是PostgreSQL的有效行为方式。

此外,SQL标准中没有public模式的概念。
为了最大限度地符合标准,您不应该使用public模式。

当然,一些SQL的数据库系统可能根本没有实现模式,或者通过允许(可能有限的)跨数据库访问来提供命名空间支持。
如果您需要使用这些系统,那么完全不使用模式可以实现最大的可移植性。


5.10.继承

PostgreSQL实现了表继承,这对数据库设计人员来说是一个有用的工具。
(SQL:1999及以后定义了一个类型继承特性,它在许多方面与这里描述的特性不同。)

让我们从一个例子开始:假设我们正在尝试为城市构建一个数据模型。
每个州有许多城市,但只有一个首都。
我们希望能够快速检索任何特定州的首都城市。
这可以通过创建两个表来完成,一个用于州首府,一个用于非首都城市。
但是,当我们想要询问关于一个城市的数据时会发生什么,不管它是否是首都?继承特性可以帮助解决这个问题。
我们定义capitals表,以便它继承自cities

CREATE TABLE cities (name            text,population      float,elevation       int     -- in feet
);CREATE TABLE capitals (state           char(2)
) INHERITS (cities);

在这种情况下,capitals继承其父表cities的所有列。
州首府还有一个额外的列state,显示它们的状态。

在PostgreSQL中,一个表可以从零个或多个其他表继承,一个查询可以引用一个表的所有行,也可以引用一个表的所有行加上它的所有后代表。
后一种行为是默认的。
例如,以下查询查找海拔超过500英尺的所有城市的名称,包括州首府:

SELECT name, elevationFROM citiesWHERE elevation > 500;

给定PostgreSQL教程中的示例数据(参见第2.1节),这将返回:

nameelevation
Las Vegas2174
Mariposa1953
Madison845

另一方面,以下查询查找海拔超过500英尺的所有非州首府城市:

SELECT name, elevationFROM ONLY citiesWHERE elevation > 500;
nameelevation
Las Vegas2174
Mariposa1953

这里ONLY关键字表示查询应该只适用于cities,而不是继承层次结构中低于cities的任何表。
我们已经讨论过的许多命令——SELECTUPDATEDELETE——支持ONLY关键字。

您还可以在表名后面加上` 来显式指定子表:

SELECT name, elevationFROM cities*WHERE elevation > 500;

写` 是不必要的,因为这种行为总是默认的。
但是,仍然支持这种语法,以便与可以更改默认值的旧版本兼容。

在某些情况下,您可能希望知道特定行来自哪个表。
每个表中都有一个名为tableoid的系统列,它可以告诉您原始表:

SELECT c.tableoid, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;

哪个返回:

tableoidnameelevation
139793Las Vegas2174
139793Mariposa1953
139798Madison845

(如果您尝试重现此示例,您可能会得到不同的数字OID。)通过与pg_class进行连接,您可以看到实际的表名:

SELECT p.relname, c.name, c.elevation
FROM cities c, pg_class p
WHERE c.elevation > 500 AND c.tableoid = p.oid;

哪个返回:

relnamenameelevation
citiesLas Vegas2174
citiesMariposa1953
capitalsMadison845

获得相同效果的另一种方法是使用regclass别名类型,它将象征性地打印表OID:

SELECT c.tableoid::regclass, c.name, c.elevation
FROM cities c
WHERE c.elevation > 500;

继承不会自动将数据从INSERTCOPY命令传播到继承层次结构中的其他表。
在我们的示例中,以下INSERT语句将失败:

INSERT INTO cities (name, population, elevation, state)
VALUES ('Albany', NULL, NULL, 'NY');

我们可能希望数据会以某种方式路由到capitals表,但这并没有发生:INSERT总是插入到指定的表中。
在某些情况下,可以使用规则重定向插入(参见第41章)。
然而,这对上述情况没有帮助,因为cities表不包含列state,因此在应用规则之前,该命令将被拒绝。

父表上的所有检查约束和非空约束都由其子表自动继承,除非使用NO INHERIT子句明确指定。
其他类型的约束(唯一、主键和外键约束)不会被继承。

一个表可以从多个父表继承,在这种情况下,它具有父表定义的列的并集。
子表定义中声明的任何列都将添加到这些列中。
如果相同的列名出现在多个父表中,或者出现在父表和子表定义中,则这些列将被“合并”,以便子表中只有一个这样的列。
要合并,列必须具有相同的数据类型,否则会引发错误。
可继承的检查约束和非空约束以类似的方式合并。
因此,例如,如果合并的列来自的任何一个列定义被标记为非空,则合并的列将被标记为非空。
如果检查约束具有相同的名称,则合并将被合并,如果它们的条件不同,则合并将失败。


表继承通常在创建子表时建立,使用CREATE TABLE语句的INHERITS子句。
或者,已经以兼容方式定义的表可以使用ALTER TABLEINHERIT变体添加新的父关系。
为此,新的子表必须已经包含与父表列具有相同名称和类型的列。
它还必须包含与父表相同名称的检查约束和检查表达式。
类似地,可以使用ALTER TABLENO INHERIT变体从子表中删除继承链接。
当继承关系用于表分区时,像这样动态添加和删除继承链接非常有用(参见第5.11节)。

创建稍后将成为新子表的兼容表的一种方便方法是使用CREATE TABLE中的LIKE子句。
这将创建一个与源表具有相同列的新表。
如果源表上定义了任何CHECK约束,则应指定LIKEINCLUDING CONSTRAINTS选项,因为新子表必须具有与父表匹配的约束才能被视为兼容。

当父表的任何子表保留时,不能删除父表。
如果子表的列或检查约束继承自任何父表,则也不能删除或更改它们。
如果您希望删除表及其所有后代,一种简单的方法是使用CASCADE选项删除父表(参见第5.14节)。

ALTER TABLE将传播列数据定义中的任何更改,并检查继承层次结构中的约束。
同样,只有在使用CASCADE选项时才能删除其他表依赖的列。
ALTER TABLE遵循在CREATE TABLE期间适用的重复列合并和拒绝规则。

继承查询仅对父表执行门禁权限检查。
因此,例如,授予cities表的UPDATE权限意味着当通过cities访问capitals表时也允许更新它们中的行。
这保留了数据(也)在父表中的外观。
但是capitals表不能在没有额外授权的情况下直接更新。
以类似的方式,父表的行安全策略(参见第5.8节)在继承查询期间应用于子表中的行。
子表的策略(如果有)仅在查询中明确命名的表时应用;在这种情况下,附加到其父表的任何策略都将被忽略。

外部表(参见第5.12节)也可以是继承层次结构的一部分,作为父表或子表,就像普通表一样。
如果外部表是继承层次结构的一部分,那么整个层次结构也不支持外部表不支持的任何操作。


5.10.1注意事项

请注意,并非所有SQL命令都适用于继承层次结构。
用于数据查询、数据修改或模式修改的命令(例如,SELECTUPDATEDELETEALTER TABLE的大多数变体,但不包括INSERTALTER TABLE ... RENAME)通常默认包含子表,并支持ONLY表示法来排除它们。
执行数据库维护和调整的命令(例如,REINDEXVACUUM)通常仅适用于单个物理表,不支持通过继承层次结构递归。
每个单独命令的相应行为记录在其参考页面中(SQL命令)。

继承特性的一个严重限制是索引(包括唯一约束)和外键约束仅适用于单个表,而不适用于它们的继承子表。
这在外键约束的引用和被引用端都是如此。
因此,在上述示例中:

  • 如果我们将cities.name声明为UNIQUEPRIMARY KEY,这不会阻止capitals表中的行与cities中的行重复。
    默认情况下,这些重复的行将显示在来自cities的查询中。
    事实上,默认情况下,capitals根本没有唯一约束,因此可能包含多个同名行。
    您可以向capitals添加唯一约束,但这不会防止与cities相比的重复。
  • 同样,如果我们指定cities
    name REFERENCES其他一些表,这个约束不会自动传播到capitals
    在这种情况下,您可以通过手动向capitals添加相同的REFERENCES约束来解决这个问题。
  • 指定另一个表的列REFERENCES cities(name)将允许另一个表包含城市名称,但不包含大写名称。

一些未为继承层次结构实现的功能是为声明性分区实现的。
在决定使用遗留继承的分区是否对您的应用程序有用时需要相当小心。


5.11 表分区

PostgreSQL支持基本的表分区。
本节介绍为什么以及如何将分区实现为数据库设计的一部分。


5.11.1概述

分区是指将逻辑上的一个大表拆分为更小的物理块。
分区可以提供几个好处:

  • 在某些情况下,查询性能可以显著提高,特别是当表中大多数高访问行位于单个分区或少量分区中时。
    分区有效地替代了索引的上层树级别,使得索引中高使用部分更有可能适合内存。
  • 当查询或更新访问单个分区的很大一部分时,可以通过使用该分区的顺序扫描而不是使用索引来提高性能,索引需要分散在整个表中的随机访问读取。
  • 如果分区设计中考虑了使用模式,可以通过添加或删除分区来完成批量加载和删除。
    使用DROP TABLE删除单个分区或ALTER TABLE DETACH PARTITION比批量操作快得多。
    这些命令还完全避免了批量DELETE造成的VACUUM开销。
  • 很少使用的数据可以迁移到更便宜、更慢的存储介质。

这些好处通常只有在表非常大的情况下才值得。
表从分区中受益的确切点取决于应用程序,尽管经验法则是表的大小应该超过数据库服务器的物理内存。

PostgreSQL为以下分区形式提供内置支持:

  • 范围分区

    表被划分为由键列或一组列定义的“范围”,分配给不同分区的值范围之间没有重叠。
    例如,可以按日期范围或特定业务对象的标识符范围进行分区。
    每个范围的边界被理解为低端包含,高端排他。
    例如,如果一个分区的范围从110,下一个分区的范围从1020,那么值10属于第二个分区,而不是第一个分区。

  • 列表分区

    通过显式列出每个分区中出现的键值来对表进行分区。

  • 哈希分区

    通过为每个分区指定模数和余数来对表进行分区。每个分区将保存分区键的哈希值除以指定模数将产生指定余数的行。

如果您的应用程序需要使用上面未列出的其他形式的分区,可以使用继承和UNION ALL视图等替代方法。这些方法提供了灵活性,但没有内置声明性分区的一些性能优势。


5.11.2 声明式分区

PostgreSQL允许您声明表被划分为分区。
被划分的表称为分区表。
声明包括如上所述的分区方法,以及用作分区键的列或表达式列表。

分区表本身是一个“虚拟”表,没有自己的存储。
相反,存储属于分区,分区是与分区表关联的普通表。
每个分区存储由其分区边界定义的数据子集。
插入到分区表中的所有行将根据分区键列的值路由到适当的分区之一。
如果行不再满足其原始分区的分区边界,更新行的分区键将导致它被移动到不同的分区。

分区本身可能被定义为分区表,从而导致子分区
尽管所有分区必须具有与其分区父分区相同的列,但分区可能有自己的索引、约束和默认值,与其他分区不同。
有关创建分区表和分区的更多详细信息,请参阅CREATE TABLE。

无法将常规表转换为分区表,反之亦然。
但是,可以添加现有的常规表或分区表作为分区表的分区,或者从分区表中删除分区,将其转换为独立表;这可以简化和加快许多维护过程。
请参阅ALTER TABLE以了解有关ATTACH PARTITIONDETACH PARTITION子命令的更多信息。

分区也可以是外部表,尽管需要相当小心,因为外部表的内容满足分区规则是用户的责任。
还有一些其他限制。
有关详细信息,请参阅创建外部表。


5.11.2.1例子

假设我们正在为一家大型冰淇淋公司构建一个数据库。
该公司每天测量峰值温度以及每个地区的冰淇淋销量。
从概念上讲,我们想要一个如下表:

CREATE TABLE measurement (city_id         int not null,logdate         date not null,peaktemp        int,unitsales       int
);

我们知道大多数查询只会访问上周、上个月或季度的数据,因为该表的主要用途是为管理准备在线报告。
为了减少需要存储的旧数据量,我们决定只保留最近3年的数据。
在每个月初,我们将删除最旧月份的数据。
在这种情况下,我们可以使用分区来帮助我们满足对测量表的所有不同要求。

要在这种情况下使用声明性分区,请使用以下步骤:

1)通过指定PARTITION BY子句将measurement表创建为分区表,其中包括分区方法(本例中为RANGE)和用作分区键的列列表。

CREATE TABLE measurement (city_id         int not null,logdate         date not null,peaktemp        int,unitsales       int
) PARTITION BY RANGE (logdate);

2)创建分区。
每个分区的定义必须指定与父分区的分区方法和分区键相对应的边界。
请注意,指定边界以使新分区的值与一个或多个现有分区中的值重叠将导致错误。

这样创建的分区在各方面都是普通的PostgreSQL表(或者可能是外部表)。
可以分别为每个分区指定表空间和存储参数。

对于我们的示例,每个分区都应该保存一个月的数据,以满足一次删除一个月数据的要求。
所以命令可能如下所示:

CREATE TABLE measurement_y2006m02 PARTITION OF measurementFOR VALUES FROM ('2006-02-01') TO ('2006-03-01');CREATE TABLE measurement_y2006m03 PARTITION OF measurementFOR VALUES FROM ('2006-03-01') TO ('2006-04-01');...
CREATE TABLE measurement_y2007m11 PARTITION OF measurementFOR VALUES FROM ('2007-11-01') TO ('2007-12-01');CREATE TABLE measurement_y2007m12 PARTITION OF measurementFOR VALUES FROM ('2007-12-01') TO ('2008-01-01')TABLESPACE fasttablespace;CREATE TABLE measurement_y2008m01 PARTITION OF measurementFOR VALUES FROM ('2008-01-01') TO ('2008-02-01')WITH (parallel_workers = 4)TABLESPACE fasttablespace;

(回想一下,相邻分区可以共享一个绑定值,因为范围上限被视为独占边界。)


如果您希望实现子分区,请在用于创建单个分区的命令中再次指定PARTITION BY子句,例如:

CREATE TABLE measurement_y2006m02 PARTITION OF measurementFOR VALUES FROM ('2006-02-01') TO ('2006-03-01')PARTITION BY RANGE (peaktemp);

创建measurement_y2006m02的分区后,任何插入到映射到measurement_y2006m02measurement中的数据(或直接插入到measurement_y2006m02中的数据,只要满足其分区约束,这是允许的)将根据peaktemp列进一步重定向到其分区之一。
指定的分区键可能与父分区键重叠,尽管在指定子分区的边界时应该小心,这样它接受的数据集就构成了分区自身边界允许的子集;系统不会尝试检查是否确实如此。

将未映射到现有分区之一的数据插入父表将导致错误;必须手动添加适当的分区。

无需手动创建描述分区边界条件的表约束。
此类约束将自动创建。

3)在分区表上的键列上创建索引,以及您可能需要的任何其他索引。
(键索引不是绝对必要的,但在大多数情况下它很有帮助。)这会自动在每个分区上创建匹配索引,您以后创建或附加的任何分区也将具有这样的索引。
在分区表上声明的索引或唯一约束是“虚拟的”,其方式与分区表相同:实际数据在各个分区表的子索引中。

CREATE INDEX ON measurement (logdate);

4)确保postgresql.conf中没有禁用enable_partition_pruning配置参数。
如果是,查询将不会按预期进行优化。

在上面的示例中,我们将每月创建一个新分区,因此编写一个自动生成所需DDL的脚本可能是明智的。


5.11.2.2分区维护

通常,最初定义表时建立的分区集并不打算保持静态。
通常希望删除保存旧数据的分区,并定期为新数据添加新分区。
分区最重要的优点之一正是它允许通过操纵分区结构几乎立即执行这项痛苦的任务,而不是物理上移动大量数据。

删除旧数据的最简单选择是删除不再需要的分区:

DROP TABLE measurement_y2006m02;

这可以非常快速地删除数百万条记录,因为它不必单独删除每条记录。
但是请注意,上面的命令需要对父表进行ACCESS EXCLUSIVE锁。

另一种通常更可取的选择是从分区表中删除分区,但保留对其本身作为表的访问权限。
这有两种形式:

ALTER TABLE measurement DETACH PARTITION measurement_y2006m02;
ALTER TABLE measurement DETACH PARTITION measurement_y2006m02 CONCURRENTLY;

这些允许在数据被删除之前对其执行进一步的操作。
例如,这通常是使用COPY、pg_dump或类似工具备份数据的有用时间。
这也可能是将数据聚合成更小的格式、执行其他数据操作或运行报告的有用时间。
命令的第一种形式需要父表上的ACCESS EXCLUSIVE锁。
与第二种形式一样添加CONCURRENTLY限定符允许分离操作只需要SHARE UPDATE EXCLUSIVE锁定父表,但有关限制的详细信息,请参阅ALTER TABLE ... DETACH PARTITION

同样,我们可以添加一个新分区来处理新数据。
我们可以在分区表中创建一个空分区,就像上面创建原始分区一样:

CREATE TABLE measurement_y2008m02 PARTITION OF measurementFOR VALUES FROM ('2008-02-01') TO ('2008-03-01')TABLESPACE fasttablespace;

作为替代方案,有时在分区结构之外创建新表,并将其附加为分区更方便。
这允许在新数据出现在分区表中之前加载、检查和转换新数据。
此外,ATTACH PARTITION操作只需要分区表上的SHARE UPDATE EXCLUSIVE锁,而不是CREATE TABLE ... PARTITION OF所需的ACCESS EXCLUSIVE锁,因此它对分区表上的并发操作更友好。
CREATE TABLE ... LIKE选项有助于避免繁琐地重复父表的定义:

CREATE TABLE measurement_y2008m02(LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS)TABLESPACE fasttablespace;ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );\copy measurement_y2008m02 from 'measurement_y2008m02'
-- possibly some other data preparation workALTER TABLE measurement ATTACH PARTITION measurement_y2008m02FOR VALUES FROM ('2008-02-01') TO ('2008-03-01' );

在运行ATTACH PARTITION命令之前,建议在要附加的表上创建一个与预期分区约束相匹配的CHECK约束,如上所示。
这样,系统将能够跳过验证隐式分区约束所需的扫描。
如果没有CHECK约束,将扫描表以验证分区约束,同时保持该分区上的ACCESS EXCLUSIVE锁。
建议在ATTACH PARTITION完成后删除现在多余的CHECK约束。
如果要附加的表本身是一个分区表,那么它的每个子分区都将被递归锁定和扫描,直到遇到合适的CHECK约束或到达叶分区。

类似地,如果分区表有一个DEFAULT分区,建议创建一个CHECK约束,排除要附加的分区的约束。
如果不这样做,则将扫描DEFAULT分区以验证它是否包含应该位于被附加分区中的记录。
此操作将在保持DEFAULT分区上的ACCESS EXCLUSIVE锁的同时执行。
如果DEFAULT分区本身是一个分区表,那么它的每个分区都将以与被附加的表相同的方式进行递归检查,如上所述。

如上所述,可以在分区表上创建索引,以便将它们自动应用于整个层次结构。
这非常方便,因为不仅现有分区会成为索引,将来创建的任何分区也会。
一个限制是在创建这样的分区索引时不可能使用CONCURRENTLY限定符。
为了避免长锁定时间,可以使用CREATE INDEX ON ONLY分区表;这样的索引被标记为无效,分区不会自动应用索引。
分区上的索引可以使用CONCURRENTLY单独创建,然后附加到父级上的索引,使用ALTER INDEX .. ATTACH PARTITION.将所有分区的索引附加到父索引后,父索引将自动标记为有效。
示例:

CREATE INDEX measurement_usls_idx ON ONLY measurement (unitsales);CREATE INDEX CONCURRENTLY measurement_usls_200602_idxON measurement_y2006m02 (unitsales);
ALTER INDEX measurement_usls_idxATTACH PARTITION measurement_usls_200602_idx;
...

这种技术也可以与UNIQUEPRIMARY KEY约束一起使用;创建约束时隐式创建索引。
示例:

ALTER TABLE ONLY measurement ADD UNIQUE (city_id, logdate);ALTER TABLE measurement_y2006m02 ADD UNIQUE (city_id, logdate);
ALTER INDEX measurement_city_id_logdate_keyATTACH PARTITION measurement_y2006m02_city_id_logdate_key;
...

5.11.2.3 限制

以下限制适用于分区表:

  • 要在分区表上创建唯一键或主键约束,分区键不得包含任何表达式或函数调用,并且约束的列必须包含所有分区键列。
    存在此限制是因为构成约束的各个索引只能在其自己的分区内直接强制唯一性;因此,分区结构本身必须保证不同分区中没有重复项。
  • 无法创建跨越整个分区表的排除约束。
    只能将这样的约束单独放在每个叶分区上。
    同样,这种限制源于无法强制执行跨分区限制。
  • INSERT上的BEFORE ROW触发器不能更改哪个分区是新行的最终目标。
  • 不允许在同一分区树中混合临时和永久关系。
    因此,如果分区表是永久的,那么它的分区也必须是永久的,同样,如果分区表是临时的。
    使用临时关系时,分区树的所有成员必须来自同一个会话。

单个分区通过后台继承链接到它们的分区表。
但是,不可能将继承的所有通用特性用于声明分区表或它们的分区,如下所述。
值得注意的是,分区不能有它所在分区的分区表之外的任何父级,表也不能同时从分区表和常规表继承。
这意味着分区表及其分区永远不会与常规表共享继承层次结构。

由于由分区表及其分区组成的分区层次结构仍然是继承层次结构,因此tableoid和所有正常的继承规则都适用于第5.10节中的描述,但有几个例外:

  • 分区不能具有父级中不存在的列。
    使用CREATE TABLE创建分区时无法指定列,也无法事后使用ALTER TABLE向分区添加列。
    只有当表的列与父级完全匹配时,才能将表添加为ALTER TABLE ... ATTACH PARTITION分区。
  • 分区表的CHECKNOT NULL约束始终由其所有分区继承。
    标记为NO INHERITCHECK约束不允许在分区表上创建。
    如果父表中存在相同的约束,则不能删除分区列上的NOT NULL约束。
  • 只要没有分区,就支持使用ONLY仅在分区表上添加或删除约束。
    一旦存在分区,使用ONLY将导致UNIQUEPRIMARY KEY以外的任何约束出错。
    相反,可以添加和删除分区本身的约束(如果它们不存在于父表中)。
  • 由于分区表本身没有任何数据,因此尝试对分区表 ONLY``TRUNCATE将始终返回错误。

5.11.3使用继承进行分区

虽然内置声明性分区适用于大多数常见用例,但在某些情况下,更灵活的方法可能会很有用。
分区可以使用表继承来实现,这允许声明性分区不支持的几个功能,例如:

  • 对于声明性分区,分区必须具有与分区表完全相同的列集,而对于表继承,子表可能具有父表中不存在的额外列。
  • 表继承允许多重继承。
  • 声明式分区仅支持范围、列表和哈希分区,而表继承允许以用户选择的方式划分数据。
    (但是请注意,如果约束排除无法有效地修剪子表,查询性能可能会很差。)

5.11.3.1例子

此示例构建与上面的声明性分区示例等效的分区结构。
使用以下步骤:

1)创建“根”表,所有“子”表都将从中继承。
此表不包含任何数据。
不要在此表上定义任何检查约束,除非您打算将它们平等地应用于所有子表。
在其上定义任何索引或唯一约束也没有意义。
对于我们的示例,根表是最初定义的measurement表:

CREATE TABLE measurement (city_id         int not null,logdate         date not null,peaktemp        int,unitsales       int
);

2)创建几个“子”表,每个表都继承自根表。
通常,这些表不会将任何列添加到从根继承的集合中。
就像声明性分区一样,这些表在各方面都是普通的PostgreSQL表(或外部表)。

CREATE TABLE measurement_y2006m02 () INHERITS (measurement);
CREATE TABLE measurement_y2006m03 () INHERITS (measurement);
...
CREATE TABLE measurement_y2007m11 () INHERITS (measurement);
CREATE TABLE measurement_y2007m12 () INHERITS (measurement);
CREATE TABLE measurement_y2008m01 () INHERITS (measurement);

3)向子表添加不重叠的表约束以定义每个表中允许的键值。

典型的例子是:

CHECK ( x = 1 )
CHECK ( county IN ( 'Oxfordshire', 'Buckinghamshire', 'Warwickshire' ))
CHECK ( outletID >= 100 AND outletID < 200 )

确保约束保证不同子表中允许的键值之间没有重叠。
一个常见的错误是设置范围约束,例如:

CHECK ( outletID BETWEEN 100 AND 200 )
CHECK ( outletID BETWEEN 200 AND 300 )

这是错误的,因为不清楚键值200属于哪个子表。
相反,范围应该以这种样式定义:

CREATE TABLE measurement_y2006m02 (CHECK ( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
) INHERITS (measurement);CREATE TABLE measurement_y2006m03 (CHECK ( logdate >= DATE '2006-03-01' AND logdate < DATE '2006-04-01' )
) INHERITS (measurement);...
CREATE TABLE measurement_y2007m11 (CHECK ( logdate >= DATE '2007-11-01' AND logdate < DATE '2007-12-01' )
) INHERITS (measurement);CREATE TABLE measurement_y2007m12 (CHECK ( logdate >= DATE '2007-12-01' AND logdate < DATE '2008-01-01' )
) INHERITS (measurement);CREATE TABLE measurement_y2008m01 (CHECK ( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
) INHERITS (measurement);

4)对于每个子表,在键列上创建一个索引,以及您可能需要的任何其他索引。

CREATE INDEX measurement_y2006m02_logdate ON measurement_y2006m02 (logdate);
CREATE INDEX measurement_y2006m03_logdate ON measurement_y2006m03 (logdate);
CREATE INDEX measurement_y2007m11_logdate ON measurement_y2007m11 (logdate);
CREATE INDEX measurement_y2007m12_logdate ON measurement_y2007m12 (logdate);
CREATE INDEX measurement_y2008m01_logdate ON measurement_y2008m01 (logdate);

5)我们希望我们的应用程序能够说INSERT INTO measurement ...并将数据重定向到适当的子表中。我们可以通过将合适的触发函数附加到根表来安排这一点。如果数据只添加到最新的子表中,我们可以使用一个非常简单的触发函数:

CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGININSERT INTO measurement_y2008m01 VALUES (NEW.*);RETURN NULL;
END;
$$
LANGUAGE plpgsql;

创建函数后,我们创建一个调用触发器函数的触发器:

CREATE TRIGGER insert_measurement_triggerBEFORE INSERT ON measurementFOR EACH ROW EXECUTE FUNCTION measurement_insert_trigger();

我们必须每个月重新定义触发器函数,以便它始终插入当前子表。
但是,触发器定义不需要更新。

我们可能希望插入数据并让服务器自动定位应该添加行的子表。
我们可以使用更复杂的触发器函数来做到这一点,例如:

CREATE OR REPLACE FUNCTION measurement_insert_trigger()
RETURNS TRIGGER AS $$
BEGINIF ( NEW.logdate >= DATE '2006-02-01' ANDNEW.logdate < DATE '2006-03-01' ) THENINSERT INTO measurement_y2006m02 VALUES (NEW.*);ELSIF ( NEW.logdate >= DATE '2006-03-01' ANDNEW.logdate < DATE '2006-04-01' ) THENINSERT INTO measurement_y2006m03 VALUES (NEW.*);...ELSIF ( NEW.logdate >= DATE '2008-01-01' ANDNEW.logdate < DATE '2008-02-01' ) THENINSERT INTO measurement_y2008m01 VALUES (NEW.*);ELSERAISE EXCEPTION 'Date out of range.  Fix the measurement_insert_trigger() function!';END IF;RETURN NULL;
END;
$$
LANGUAGE plpgsql;

触发器定义和以前一样。
请注意,每个IF测试必须完全匹配其子表的CHECK约束。

虽然此功能比单月情况更复杂,但它不需要经常更新,因为可以在需要之前添加分支。


Note : 在实践中,如果大多数插入都进入该子级,最好先检查最新的子级。
为简单起见,我们以与本示例其他部分相同的顺序显示了触发器的测试。

将插入重定向到适当子表的另一种方法是在根表上设置规则而不是触发器。
例如:

CREATE RULE measurement_insert_y2006m02 AS
ON INSERT TO measurement WHERE( logdate >= DATE '2006-02-01' AND logdate < DATE '2006-03-01' )
DO INSTEADINSERT INTO measurement_y2006m02 VALUES (NEW.*);
...
CREATE RULE measurement_insert_y2008m01 AS
ON INSERT TO measurement WHERE( logdate >= DATE '2008-01-01' AND logdate < DATE '2008-02-01' )
DO INSTEADINSERT INTO measurement_y2008m01 VALUES (NEW.*);

规则的开销比触发器大得多,但是每个查询支付一次开销,而不是每行支付一次开销,因此这种方法可能对批量插入情况有利。
然而,在大多数情况下,触发器方法将提供更好的性能。

请注意,COPY会忽略规则。
如果要使用COPY插入数据,则需要复制到正确的子表中,而不是直接复制到根表中。
COPY会触发触发器,因此如果使用触发器方法,则可以正常使用。

规则方法的另一个缺点是,如果规则集没有覆盖插入日期,则没有简单的方法来强制出错;数据将静默地进入根表。


6)确保postgresql.conf中没有禁用constraint_exclusion配置参数;否则可能会不必要地访问子表。

正如我们所看到的,复杂的表层次结构可能需要大量的DDL。
在上面的示例中,我们将每月创建一个新的子表,因此编写一个自动生成所需DDL的脚本可能是明智的。


5.11.3.2继承分区的维护

要快速删除旧数据,只需删除不再需要的子表:

DROP TABLE measurement_y2006m02;

要从继承层次结构表中删除子表,但保留对其作为表本身的访问权限:

ALTER TABLE measurement_y2006m02 NO INHERIT measurement;

要添加新的子表来处理新数据,请创建一个空的子表,就像上面创建的原始子表一样:

CREATE TABLE measurement_y2008m02 (CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' )
) INHERITS (measurement);

或者,您可能希望在将新的子表添加到表层次结构之前创建并填充它。
这可以允许在对父表的查询可见之前加载、检查和转换数据。

CREATE TABLE measurement_y2008m02(LIKE measurement INCLUDING DEFAULTS INCLUDING CONSTRAINTS);
ALTER TABLE measurement_y2008m02 ADD CONSTRAINT y2008m02CHECK ( logdate >= DATE '2008-02-01' AND logdate < DATE '2008-03-01' );
\copy measurement_y2008m02 from 'measurement_y2008m02'
-- possibly some other data preparation work
ALTER TABLE measurement_y2008m02 INHERIT measurement;

5.11.3.3注意事项

以下警告适用于使用继承实现的分区:

  • 没有自动验证所有CHECK约束是否互斥的方法。
    创建生成子表并创建和/或修改关联对象的代码比手动编写每个对象更安全。
  • 索引和外键约束适用于单个表,而不适用于其继承子表,因此需要注意一些注意事项。
  • 此处显示的方案假定行键列的值永远不会改变,或者至少不会改变到需要移动到另一个分区的程度。
    尝试这样做的UPDATE将由于CHECK约束而失败。
    如果您需要处理这种情况,您可以在子表上放置合适的更新触发器,但这会使结构的管理更加复杂。
  • 如果您使用的是手动VACUUMANALYZE命令,请不要忘记您需要在每个子表上单独运行它们。
    ANALYZE measurement;
    
    只会处理根表。
  • INSERT``ON CONFLICT条款的INSERT语句不太可能按预期工作,因为ON CONFLICT操作仅在特定目标关系(而不是其子关系)发生独特违规的情况下采取。
  • 除非应用程序明确知道分区方案,否则需要触发器或规则将行路由到所需的子表。
    触发器编写起来可能很复杂,并且比声明性分区在内部执行的元组路由慢得多。

5.11.4分区修剪

分区修剪是一种查询优化技术,可提高声明式分区表的性能。
作为一个例子:

SET enable_partition_pruning = on;                 -- the default
SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';

如果不进行分区修剪,上述查询将扫描measurement表的每个分区。
启用分区修剪后,规划器将检查每个分区的定义,并证明该分区不需要扫描,因为它不能包含任何满足查询的WHERE子句的行。
当规划器可以证明这一点时,它会从查询计划中排除(修剪)该分区。

通过使用EXPLAIN命令和enable_partition_pruning配置参数,可以显示已修剪分区的计划和未修剪分区的计划之间的差异。
此类表设置的典型未优化计划是:

SET enable_partition_pruning = off;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';QUERY PLAN |
| -- |
| Aggregate  (cost=188.76..188.77 rows=1 width=8)->  Append  (cost=0.00..181.05 rows=3085 width=0)->  Seq Scan on measurement_y2006m02  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)->  Seq Scan on measurement_y2006m03  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)
...->  Seq Scan on measurement_y2007m11  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)->  Seq Scan on measurement_y2007m12  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)

部分或全部分区可能使用索引扫描而不是全表顺序扫描,但这里的重点是根本不需要扫描旧分区来回答此查询。
当我们启用分区修剪时,我们会得到一个更便宜的计划,它将提供相同的答案:

SET enable_partition_pruning = on;
EXPLAIN SELECT count(*) FROM measurement WHERE logdate >= DATE '2008-01-01';QUERY PLAN |
| -- |
| Aggregate  (cost=37.75..37.76 rows=1 width=8)->  Seq Scan on measurement_y2008m01  (cost=0.00..33.12 rows=617 width=0)Filter: (logdate >= '2008-01-01'::date)

请注意,分区修剪仅由分区键隐式定义的约束驱动,而不是由索引的存在驱动。
因此,没有必要在键列上定义索引。
是否需要为给定分区创建索引取决于您期望扫描分区的查询通常会扫描分区的大部分还是一小部分。
索引在后一种情况下会有帮助,但在前一种情况下不会。

分区修剪不仅可以在给定查询的规划期间执行,也可以在其执行期间执行。
这很有用,因为当子句包含在查询规划时不知道其值的表达式时,它可以允许修剪更多分区,例如,在PREPARE中定义的参数、使用从子查询获得的值或使用嵌套循环连接内侧的参数化值。
执行期间的分区修剪可以在以下任何时间执行:

  • 在查询计划初始化期间。
    可以在此处对执行初始化阶段已知的参数值执行分区修剪。
    在此阶段修剪的分区不会显示在查询的EXPLAINEXPLAIN ANALYZE中。
    可以通过观察EXPLAIN输出中的“子计划已删除”属性来确定在此阶段删除的分区数量。
  • 在实际执行查询计划期间。
    也可以在这里执行分区修剪,以使用仅在实际查询执行期间才知道的值来删除分区。
    这包括来自子查询的值和来自执行时参数的值,例如来自参数化嵌套循环连接的值。
    由于这些参数的值在查询执行期间可能会多次更改,因此每当分区修剪使用的执行参数之一发生更改时,就会执行分区修剪。
    确定分区是否在此阶段被修剪需要仔细检查EXPLAIN ANALYZE中的loops输出。
    对应于不同分区的子计划可能具有不同的值,具体取决于每个分区在执行期间被修剪的次数。
    有些可能显示为(never executed)如果它们每次都被修剪。

可以使用enable_partition_pruning设置禁用分区修剪。


5.11.5分区和约束排除

约束排除是一种类似于分区修剪的查询优化技术。
虽然它主要用于使用遗留继承方法实现的分区,但它也可以用于其他目的,包括声明性分区。

约束排除的工作方式与分区修剪非常相似,只是它使用每个表的CHECK约束——这给了它的名字——而分区修剪使用表的分区边界,这些边界只存在于声明性分区的情况下。
另一个区别是约束排除仅在计划时应用;在执行时不会尝试删除分区。

约束排除使用CHECK约束,这使得它比分区修剪慢,这一事实有时可以用作一个优势:因为约束甚至可以在declaratively-partitioned表上定义,除了它们的内部分区边界之外,约束排除可能能够从查询计划中删除其他分区。

constraint_exclusion的默认(推荐)设置既不是on也不是off,而是称为partition的中间设置,这导致该技术仅应用于可能在继承分区表上工作的查询。
on设置导致规划器检查所有查询中的CHECK约束,即使是不太可能受益的简单查询。

以下警告适用于约束排除:

  • 约束排除仅在查询规划期间应用,与分区修剪不同,分区修剪也可以在查询执行期间应用。
  • 约束排除仅在查询的WHERE子句包含常量(或外部提供的参数)时有效。
    例如,与非不可函数(如CURRENT_TIMESTAMP)的比较无法优化,因为规划器无法知道函数的值在运行时可能属于哪个子表。
  • 保持分区约束简单,否则规划器可能无法证明可能不需要访问子表。
    使用简单的相等条件进行列表分区,或使用简单的范围测试进行范围分区,如前面的示例所示。
    一个很好的经验法则是分区约束应该只包含使用B-tree可索引运算符将分区列与常量进行比较,因为分区键中只允许使用B-tree可索引列。
  • 在约束排除期间,会检查父表所有子表上的所有约束,因此大量子表可能会大大增加查询规划时间。
    因此,基于遗留继承的分区可以很好地处理多达一百个子表;不要试图使用成千上万的子表。

5.11.6声明式分区的最佳实践

应该仔细选择如何对表进行分区,因为糟糕的设计可能会对查询规划和执行的性能产生负面影响。

最关键的设计决策之一是对数据进行分区的一个或多个列。
通常,最好的选择是按列或一组列进行分区,这些列最常出现在分区表上执行的WHERE查询子句中。
WHERE子句可用于修剪不需要的分区。
但是,您可能会因PRIMARY KEYUNIQUE约束的要求而被迫做出其他决定。
在规划分区策略时,删除不需要的数据也是一个要考虑的因素。
整个分区可以相当快地分离,因此以这样一种方式设计分区策略可能是有益的,即一次删除的所有数据都位于单个分区中。

选择表应该划分的目标分区数量也是一个关键的决定。
没有足够的分区可能意味着索引仍然太大,数据局部性仍然很差,这可能导致缓存命中率低。
但是,将表划分为太多分区也可能导致问题。
太多分区可能意味着查询规划时间更长,查询规划和执行期间内存消耗更高,如下文进一步描述。
在选择如何对表进行分区时,考虑未来可能发生的变化也很重要。
例如,如果您选择每个客户有一个分区,并且您目前拥有少量大客户,请考虑如果几年后您发现自己拥有大量小客户的影响。
在这种情况下,最好选择通过HASH进行分区并选择合理数量的分区,而不是尝试通过LIST进行分区并希望客户数量不会增加到超出实际数据分区的数量。

子分区对于进一步划分预期会变得比其他分区更大的分区很有用。
另一种选择是使用分区键中有多列的范围分区。
其中任何一种都很容易导致分区数量过多,因此建议限制。

在查询规划和执行期间考虑分区的开销是很重要的。
查询规划器通常能够相当好地处理多达几千个分区的分区层次结构,前提是典型的查询允许查询规划器修剪除少量分区之外的所有分区。
规划时间变得更长,当规划器执行分区修剪后剩下更多分区时,内存消耗也会变得更高。
担心拥有大量分区的另一个原因是服务器的内存消耗可能会随着时间的推移而显着增长,特别是如果许多会话触及大量分区。
那是因为每个分区都需要将其元数据加载到触及它的每个会话的本地内存中。

对于数据仓库类型的工作负载,使用更多的分区比使用联机事务处理类型的工作负载更有意义。
通常,在数据仓库中,查询规划时间不太重要,因为大部分流转时长都花在查询执行期间。
对于这两种类型的工作负载中的任何一种,尽早做出正确的决定都很重要,因为重新分区大量数据可能会非常缓慢。
对预期工作负载的模拟通常有利于优化分区策略。
永远不要假设更多的分区比更少的分区更好,反之亦然。


5.12 外部数据

PostgreSQL实现了SQL/MED规范的一部分,允许您使用常规SQL查询访问驻留在PostgreSQL之外的数据。
这些数据称为外部数据。
(请注意,这种用法不要与外键混淆,外键是数据库中的一种约束。)

外部数据包装器的帮助下访问外部数据。
外部数据包装器是一个库,可以与外部数据源通信,隐藏连接到数据源并从中获取数据的详细信息。
有一些外部数据包装器可用作contrib模块;见附录F。
其他类型的外部数据包装器可能可以作为第三方产品找到。
如果现有的外部数据包装器都不适合您的需求,您可以编写自己的;见第59章。

要访问外部数据,您需要创建一个外部服务器对象,该对象定义如何根据其支持的外部数据包装器使用的选项集连接到特定的外部数据源。
然后您需要创建一个或多个外部表,这些表定义了远程数据的结构。
外部表可以像普通表一样用于查询,但外部表在PostgreSQL服务器中没有存储空间。
每当使用它时,PostgreSQL都会要求外部数据包装器从外部源获取数据,或者在更新命令的情况下将数据传输到外部源。

访问远程数据可能需要对外部数据源进行身份验证,此信息可以由用户映射提供,该映射可以根据当前PostgreSQL角色提供用户名和密码等附加数据。

有关其他信息,请参阅CREATE FOREIGN DATA WRAPPER、CREATE SERVER、CREATE USER MAPPING、CREATE FOREIGN TABLE和IMPORT FOREIGN ScheMA。


5.13 其他数据库对象

表是关系数据库结构中的中心对象,因为它们保存着您的数据。
但它们不是数据库中唯一存在的对象。
可以创建许多其他类型的对象,以使数据的使用和管理更加高效或方便。
本章没有讨论它们,但我们在这里给你一个列表,以便你知道什么是可能的:

  • 浏览量
  • 函数、过程和运算符
  • 数据类型和域
  • 触发和重写规则

关于这些专题的详细资料见第五部分。


5.14 依赖跟踪

当您创建涉及许多具有外键约束的表、视图、触发器、函数等的复杂数据库结构时,您会隐式地创建对象之间的依赖关系网络。
例如,具有外键约束的表取决于它引用的表。

为了确保整个数据库结构的完整性,PostgreSQL确保您不能删除其他对象仍然依赖的对象。
例如,尝试删除我们在第5.4.5节中考虑的产品表,订单表取决于它,将导致如下错误消息:

DROP TABLE products;ERROR:  cannot drop table products because other objects depend on it
DETAIL:  constraint orders_product_no_fkey on table orders depends on table products
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

错误消息包含一个有用的提示:如果您不想麻烦单独删除所有依赖对象,您可以运行:

DROP TABLE products CASCADE;

所有依赖对象都将被递归删除,依赖它们的任何对象也将被递归删除。
在这种情况下,它不会删除订单表,只会删除外键约束。
它停在那里,因为没有任何东西依赖于外键约束。
(如果你想检查DROP ... CASCADE会做什么,运行没有CASCADEDROP并读取DETAIL输出。)

在PostgreSQL中几乎所有的DROP命令都支持指定CASCADE,当然,可能的依赖关系的性质随对象的类型而变化,您也可以编写RESTRICT而不是CASCADE来获得默认行为,即防止丢弃任何其他对象所依赖的对象。


Note : 根据SQL标准,DROP命令中需要指定RESTRICTCASCADE
没有数据库系统实际执行该规则,但默认行为是RESTRICT还是CASCADE因系统而异。

如果DROP命令列出多个对象,则仅当指定组之外存在依赖项时才需要CASCADE
例如,当说DROP TABLE tab1, tab2时,存在从tab2引用tab1的外键并不意味着需要CASCADE才能成功。

对于主体定义为字符串文字的用户定义函数或过程,PostgreSQL跟踪与函数外部可见属性相关的依赖关系,例如其参数和结果类型,但不跟踪只能通过检查函数主体才能知道的依赖关系。
例如,考虑这种情况:

CREATE TYPE rainbow AS ENUM ('red', 'orange', 'yellow','green', 'blue', 'purple');CREATE TABLE my_colors (color rainbow, note text);CREATE FUNCTION get_color_note (rainbow) RETURNS text AS'SELECT note FROM my_colors WHERE color = $1'LANGUAGE SQL;

(见第38.5节对SQL函数的解释。)PostgreSQL会意识到get_color_note函数依赖于rainbow类型:删除类型会强制删除函数,因为它的参数类型将不再被定义。
但是PostgreSQL不会考虑get_color_note依赖于my_colors表,因此如果表被删除,函数也不会删除。
虽然这种方法有缺点,但也有好处。
如果表丢失,该函数在某种意义上仍然有效,尽管执行它会导致错误;创建一个同名的新表将允许函数再次工作。

另一方面,对于主体以SQL标准风格编写的SQL语言函数或过程,主体在函数定义时被解析,解析器识别的所有依赖项都被存储。
因此,如果我们将上面的函数编写为

CREATE FUNCTION get_color_note (rainbow) RETURNS text
BEGIN ATOMICSELECT note FROM my_colors WHERE color = $1;
END;

然后函数对my_colors表的依赖关系将被DROP知道并强制执行。


第6章 数据操作

上一章讨论了如何创建表和其他结构来保存您的数据。
现在是时候用数据填充表了。
本章介绍了如何插入、更新和删除表数据。
这之后的章节将最终解释如何从数据库中提取您丢失已久的数据。


6.1 插入数据

创建表时,它不包含任何数据。
在数据库发挥作用之前,首先要做的是插入数据。
数据一次插入一行。
您也可以在单个命令中插入多行,但不可能插入不是完整行的东西。
即使您只知道一些列值,也必须创建完整的行。

要创建新行,请使用INSERT命令。
该命令需要表名和列值。
例如,考虑第5章中的产品表:

CREATE TABLE products (product_no integer,name text,price numeric
);

插入行的示例命令是:

INSERT INTO products VALUES (1, 'Cheese', 9.99);

数据值按列在表中出现的顺序列出,以逗号分隔。
通常,数据值将是文字(常量),但也允许使用标量表达式。

上述语法的缺点是您需要知道表中列的顺序。
为避免这种情况,您还可以显式列出列。
例如,以下两个命令与上面的命令具有相同的效果:

INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', 9.99);
INSERT INTO products (name, price, product_no) VALUES ('Cheese', 9.99, 1);

许多用户认为始终列出列名是一种很好的做法。

如果您没有所有列的值,则可以省略其中一些。
在这种情况下,列将填充其默认值。
例如:

INSERT INTO products (product_no, name) VALUES (1, 'Cheese');
INSERT INTO products VALUES (1, 'Cheese');

第二种形式是PostgreSQL扩展。
它用给定数量的值填充左侧的列,其余的将被默认。

为清楚起见,您还可以为单个列或整行显式请求默认值:

INSERT INTO products (product_no, name, price) VALUES (1, 'Cheese', DEFAULT);
INSERT INTO products DEFAULT VALUES;

您可以在单个命令中插入多行:

INSERT INTO products (product_no, name, price) VALUES(1, 'Cheese', 9.99),(2, 'Bread', 1.99),(3, 'Milk', 2.99);

也可以插入查询的结果(可能是没有行、一行或多行):

INSERT INTO products (product_no, name, price)SELECT product_no, name, price FROM new_productsWHERE release_date = 'today';

这为计算要插入的行提供了SQL查询机制(第7章)的全部功能。


Tips : 当同时插入大量数据时,可以考虑使用COPY命令,它不如INSERT命令灵活,但效率更高。
有关提高批量加载性能的更多信息,请参阅第14.4节。


6.2.更新数据

对数据库中已有数据的修改称为更新。
您可以更新单个行、表中的所有行或所有行的子集。
每列都可以单独更新;其他列不受影响。

要更新现有行,请使用UPDATE命令。
这需要三个信息:

  1. 要更新的表和列的名称
  2. 列的新值
  3. 要更新哪一行

回想一下第5章,SQL通常不会为行提供唯一标识符。
因此,并不总是可以直接指定要更新的行。
相反,您需要指定要更新的行必须满足哪些条件。
只有当您在表中有一个主键(与您是否声明它无关)时,您才能通过选择与主键匹配的条件来可靠地处理单个行。
图形数据库访问工具依赖于这一事实来允许您单独更新行。

例如,此命令将所有价格为5的产品更新为价格为10:

UPDATE products SET price = 10 WHERE price = 5;

这可能会导致更新零、一行或多行。
尝试不匹配任何行的更新不是错误。

让我们详细看看这个命令。
首先是关键字UPDATE,然后是表名。
像往常一样,表名可以是模式限定的,否则会在路径中查找。
接下来是关键字SET,后跟列名、等号和新的列值。
新列值可以是任何标量表达式,而不仅仅是一个常量。
例如,如果您想将所有产品的价格提高10%,您可以使用:

UPDATE products SET price = price * 1.10;

如您所见,新值的表达式可以引用行中的现有值。
我们还遗漏了WHERE子句。
如果省略它,则意味着表中的所有行都被更新。
如果存在,则仅更新那些与WHERE条件匹配的行。
请注意,SET子句中的等号是赋值,而WHERE子句中的等号是比较,但这不会产生任何歧义。
当然,WHERE条件不必是相等测试。
许多其他运算符都可以使用(参见第9章)。
但是表达式需要评估为布尔结果。

您可以通过在SET子句中列出多个赋值来更新UPDATE命令中的多个列。
例如:

UPDATE mytable SET a = 5, b = 3, c = 1 WHERE a > 0;


6.3.删除数据

到目前为止,我们已经解释了如何向表中添加数据以及如何更改数据。
剩下的就是讨论如何删除不再需要的数据。
正如添加数据只能在整行中进行一样,您只能从表中删除整行。
在上一节中,我们解释了SQL不提供直接处理单个行的方法。
因此,删除行只能通过指定要删除的行必须匹配的条件来完成。
如果表中有一个主键,那么您可以指定确切的行。
但是您也可以删除与条件匹配的行组,或者您可以一次删除表中的所有行。

您可以使用DELETE命令删除行;语法与UPDATE命令非常相似。
例如,要从产品表中删除价格为10的所有行,请使用:

DELETE FROM products WHERE price = 10;

如果你简单地写:

DELETE FROM products;

然后表中的所有行都将被删除!警告程序员。


6.4.从修改的行返回数据

有时,在操作修改后的行时从这些行中获取数据很有用。
INSERTUPDATEDELETE命令都有一个可选的RETURNING子句来支持这一点。
使用RETURNING可以避免执行额外的数据库查询来收集数据,并且在难以可靠地识别修改后的行时特别有价值。

允许的内容与RETURNING``SELECT命令的输出列表相同(参见第7.3节)。
它可以包含命令目标表的列名,或使用这些列的值表达式。
一个常见的简写是`RETURNING ,它按顺序选择目标表的所有列。

INSERT中,可用于RETURNING的数据是插入时的行。
这在琐碎的插入中不太有用,因为它只会重复客户端提供的数据。
但是当依赖计算的默认值时,它会非常方便。
例如,当使用serial列提供唯一标识符时,RETURNING可以返回分配给新行的ID:

CREATE TABLE users (firstname text, lastname text, id serial primary key);INSERT INTO users (firstname, lastname) VALUES ('Joe', 'Cool') RETURNING id;

对于INSERT ... SELECTRETURNING子句也非常有用。

UPDATE中,可用于RETURNING的数据是修改行的新内容。
例如:

UPDATE products SET price = price * 1.10WHERE price <= 99.99RETURNING name, price AS new_price;

DELETE中,可用于RETURNING的数据是已删除行的内容。
例如:

DELETE FROM productsWHERE obsoletion_date = 'today'RETURNING *;

如果目标表上有触发器(第39章),则可用于RETURNING的数据是触发器修改的行。
因此,检查触发器计算的列是RETURNING的另一个常见用例。


第7章 查询

前面的章节解释了如何创建表、如何用数据填充它们以及如何操作这些数据。
现在我们最后讨论如何从数据库中检索数据。


7.1 概述

从数据库中检索数据的过程或命令称为查询
SQLSELECT命令用于指定查询。
SELECT命令的一般语法是

[WITH with_queries] SELECT select_list FROM table_expression [sort_specification]

以下部分描述了选择列表、表表达式和排序规范的详细信息。
WITH查询是最后处理的,因为它们是一个高级功能。

一种简单的查询具有以下形式:

SELECT * FROM table1;

假设有一个名为table1的表,此命令将从table1中检索所有行和所有用户定义的列。
(检索方法取决于客户端应用程序。
例如,psql程序将在屏幕上显示一个ASCII-art表,而客户端库将提供从查询结果中提取单个值的函数。)选择列表规范 表示表表达式碰巧提供的所有列。 选择列表还可以选择可用列的子集或使用列进行计算。 例如,如果table1有名为abc`(可能还有其他)的列,您可以进行以下查询:

SELECT a, b + c FROM table1;

(假设bc是数值数据类型)。
有关详细信息,请参阅第7.3节。

FROM table1是一种简单的表表达式:它只读取一个表。
通常,表表达式可以是基表、连接和子查询的复杂结构。
但是您也可以完全省略表表达式,并将SELECT命令用作计算器:

SELECT 3 * 4;

如果选择列表中的表达式返回不同的结果,这将更有用。
例如,您可以这样调用函数:

SELECT random();

7.2.表格表达式

一个表表达式计算一个表。
表表达式包含一个FROM子句,可以选择后跟WHEREGROUP BYHAVING
普通表表达式只是指磁盘上的表,即所谓的基表,但更复杂的表达式可以用于以各种方式修改或组合基表。


表表达式中可选的WHEREGROUP BYHAVING子句指定对FROM子句中派生的表执行的连续转换的管道。
所有这些转换都会生成一个虚拟表,该表提供传递到选择列表以计算查询的输出行的行。


7.2.1FROM条款

FROM子句从逗号分隔的表引用列表中给出的一个或多个其他表派生表。

FROM table_reference [, table_reference [, ...]]

表引用可以是表名(可能是模式限定的),也可以是派生表(如子查询、JOIN构造)或它们的复杂组合。
如果FROM子句中列出了多个表引用,则表是交叉连接的(即形成它们行的笛卡尔积;见下文)。
FROM列表的结果是一个中间虚拟表,然后可以通过WHEREGROUP BYHAVING子句进行转换,最后是整个表表达式的结果。

当表引用命名为表继承层次结构的父表时,表引用不仅会生成该表的行,还会生成其所有后代表的行,除非关键字ONLY位于表名之前。
但是,引用仅生成出现在命名表中的列——子表中添加的任何列都将被忽略。

您可以在表名后面写 ,而不是ONLY`写表名,以明确指定包含后代表。
没有真正的理由再使用这种语法,因为搜索后代表现在始终是默认行为。
但是,它支持与旧版本兼容。


7.2.1.1 联接表

联接表是根据特定联接类型的规则从另外两个(实数或派生)表派生的表。
内联接、外联接和交叉联接可用。
联接表的一般语法是

T1 join_type T2 [ join_condition ]

所有类型的连接都可以链接在一起,也可以嵌套: T1 和 T2 都可以连接表。
JOIN周围可以使用括号来控制连接顺序。
如果没有括号,JOIN从左到右嵌套。

Join Types

  • Cross join
T1 CROSS JOIN T2

对于 T1 和 T2 行的每个可能组合(即笛卡尔积),连接表将包含由 T1 中的所有列和 T2 中的所有列组成的行。
如果表分别有N行和M行,则连接表将有N*M行。
FROM T1 CROSS JOIN T2 等价于 FROM T1 INNER JOIN T2 ON TRUE(见下文)。
它也等价于FROM T1 , T2


注:当出现两个以上的表时,后一个等价不完全成立,因为JOIN比逗号绑定得更紧。
例如FROM T1 CROSS JOIN T2 INNER JOIN T3 ON conditionFROM T1, T2 INNER JOIN T3 ON condition不同,因为 condition 可以在第一种情况下引用 T1 ,但不能在第二种情况下引用。


  • Qualified joins
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 ON boolean_expression
T1 { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2 USING ( join column list )
T1 NATURAL { [INNER] | { LEFT | RIGHT | FULL } [OUTER] } JOIN T2

单词INNEROUTER在所有形式中都是可选的。
INNER是默认值;LEFTRIGHTFULL表示外部连接。
ONUSING子句中指定连接条件,或通过单词NATURAL隐式指定。
连接条件确定两个源表中的哪些行被认为是“匹配”,如下所述。


合格连接的可能类型有:

  • INNER JOIN
    对于T1的每一行R1,连接表对于T2中满足R1连接条件的每一行都有一行。
  • LEFT OUTER JOIN 首先,执行内连接。
    然后,对于T1中不满足与T2中任何行的连接条件的每一行,在T2的列中添加一个具有空值的连接行。
    因此,连接表在T1中的每一行总是至少有一行。
  • RIGHT OUTER JOIN 首先,执行内连接。
    然后,对于T2中不满足T1中任何行的连接条件的每一行,在T1的列中添加一个带有空值的连接行。
    这与左连接相反:结果表将始终为T2中的每一行添加一行。
  • FULL OUTER JOIN 首先,执行内连接。
    然后,对于T1中不满足T2中任何行的连接条件的每一行,在T2的列中添加一个具有空值的连接行。
    此外,对于T2中不满足T1中任何行的连接条件的每一行,在T1的列中添加一个具有空值的连接行。

其中ON子句是最常见的连接条件:它采用与WHERE中使用的类型相同的布尔值表达式。
如果ON表达式的计算结果为true,则 T1和T2T2 使用USING子句,您可以利用连接双方对连接列使用相同名称的特定情况。
它采用一个以逗号分隔的共享列名列表,并形成一个连接条件,其中包括每个列名的相等比较。
例如,使用 USING (a, b)连接T1 和 T2 会产生连接条件 ON T1.a = T2.a AND T1.b = T2.b
此外,JOIN USING的输出抑制了冗余列:不需要打印两个匹配的列,因为它们必须具有相等的值。
JOIN ON从 T1 生成所有列,然后从 T2 生成所有列时,JOIN USING为每个列出的列对生成一个输出列(按列出的顺序),然后是 T1 的任何剩余列,然后是 T2 的任何剩余列。
最后,NATURALUSING的简写形式:它形成一个USING列表,其中包含两个输入表中出现的所有列名。
USING一样,这些列在输出表中只出现一次。
如果没有通用列名,NATURAL JOIN的行为类似于JOIN ... ON TRUE,产生跨产品连接。
USING对连接关系中的列更改相当安全,因为只有列出的列被合并。
NATURAL风险要大得多,因为对任一关系的任何模式更改都会导致出现新的匹配列名,这也将导致连接合并该新列。

为了把这些放在一起,假设我们有表t1

numname
1a
2b
3c

t2

numvalue
1xxx
3yyy
5zzz

然后我们得到各种连接的以下结果:

=> SELECT * FROM t1 CROSS JOIN t2;
numnamenumvalue
1a1xxx
1a3yyy
1a5zzz
2b1xxx
2b3yyy
2b5zzz
3c1xxx
3c3yyy
3c5zzz

(9 rows)


SELECT * FROM t1 INNER JOIN t2 ON t1.num = t2.num;
numnamenumvalue
1a1xxx
3c3yyy

(2 rows)


SELECT * FROM t1 INNER JOIN t2 USING (num);
numnamevalue
1axxx
3cyyy

(2 rows)


SELECT * FROM t1 NATURAL INNER JOIN t2;
numnamevalue
1axxx
3cyyy

(2 rows)


SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num;
numnamenumvalue
1a1xxx
2b
3c3yyy

(3 rows)


SELECT * FROM t1 LEFT JOIN t2 USING (num);
numnamevalue
1axxx
2b
3cyyy

(3 rows)


SELECT * FROM t1 RIGHT JOIN t2 ON t1.num = t2.num;
numnamenumvalue
1a1xxx
3c3yyy
5zzz

(3 rows)


SELECT * FROM t1 FULL JOIN t2 ON t1.num = t2.num;
numnamenumvalue
1a1xxx
2b
3c3yyy
5zzz

(4 rows)


使用ON指定的连接条件也可以包含与连接不直接相关的条件。
这可能对某些查询很有用,但需要仔细考虑。
例如:

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num AND t2.value = 'xxx';
numnamenumvalue
1a1xxx
2b
3c

(3 rows)


请注意,将限制放在WHERE中会产生不同的结果:

=> SELECT * FROM t1 LEFT JOIN t2 ON t1.num = t2.num WHERE t2.value = 'xxx';
numnamenumvalue
1a1xxx

(1 row)


这是因为放置在ON子句中的限制在连接之前处理,而放置在WHERE中的限制在连接之后处理。
这与内部连接无关,但与外部连接关系很大。


7.2.1.2表和列别名

可以为表和复杂表引用提供临时名称,以便在查询的其余部分中用于对派生表的引用。
这称为表别名。

要创建表别名,请写入

FROM table_reference AS alias

或者

FROM table_reference alias

关键字AS是可选的噪声。
alias 可以是任何标识符。


表别名的典型应用是将短标识符分配给长表名,以保持连接子句的可读性。
例如:

SELECT * FROM some_very_long_table_name s JOIN another_fairly_long_name a ON s.id = a.num;

就当前查询而言,别名成为表引用的新名称-不允许在查询的其他地方使用原始名称引用表。
因此,这是无效的:

SELECT * FROM my_table AS m WHERE my_table.a > 5;    -- wrong

表别名主要是为了方便符号,但在将表连接到自身时必须使用它们,例如:

SELECT * FROM people AS mother JOIN people AS child ON mother.id = child.mother_id;

此外,如果表引用是子查询,则需要别名(参见第7.2.1.3节)。

括号用于解决歧义。
在以下示例中,第一条语句将别名b分配给my_table的第二个实例,但第二条语句将别名分配给连接的结果:

SELECT * FROM my_table AS a CROSS JOIN my_table AS b ...
SELECT * FROM (my_table AS a CROSS JOIN my_table) AS b ...

表混淆现象的另一种形式为表的列以及表本身提供了临时名称:

FROM table_reference [AS] alias ( column1 [, column2 [, ...]] )

如果指定的列别名少于实际表的列,则不会重命名其余列。
此语法对于自连接或子查询特别有用。

当别名应用于JOIN的输出时,别名会隐藏JOIN中的原始名称。
例如:

SELECT a.* FROM my_table AS a JOIN your_table AS b ON ...

SQL有效,但:

SELECT a.* FROM (my_table AS a JOIN your_table AS b ON ...) AS c

无效;表别名a在别名c之外不可见。


7.2.1.3子查询

指定派生表的子查询必须用括号括起来,并且必须分配表别名(如第7.2.1.2节)。
例如:

FROM (SELECT * FROM table1) AS alias_name

这个例子等价于FROM table1 AS alias_name
当子查询涉及分组或聚合时,会出现更有趣的情况,不能简化为普通连接。

子查询也可以是VALUES列表:

FROM (VALUES ('anne', 'smith'), ('bob', 'jones'), ('joe', 'blow'))AS names(first, last)

同样,需要表别名。
VALUES列表的列分配别名是可选的,但这是一个很好的做法。
有关详细信息,请参见第7.7节。


7.2.1.4表函数

表函数是生成一组行的函数,这些行由基本数据类型(标量类型)或复合数据类型(表行)组成。
它们在查询的FROM子句中像表、视图或子查询一样使用。
表函数返回的列可以像表、视图或子查询的列一样包含在SELECTJOINWHERE子句中。


表函数也可以使用ROWS FROM语法组合,结果以并行列的形式返回;在这种情况下,结果行数是最大函数结果的行数,较小的结果用空值填充以匹配。

function_call [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]
ROWS FROM( function_call [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]

如果指定了WITH ORDINALITY,则会在函数结果列中添加bigint类型的附加列。
此列对函数结果集的行进行编号,从1开始。
(这是UNNEST ... WITH ORDINALITY的SQL标准语法的推广。)默认情况下,序数列称为ordinality,但可以使用AS子句为其分配不同的列名。

可以使用任意数量的数组参数调用特殊表函数UNNEST,它返回相应数量的列,就好像UNNEST(第9.19节)已被单独调用每个参数并使用ROWS FROM构造组合。

UNNEST( array_expression [, ... ] ) [WITH ORDINALITY] [[AS] table_alias [(column_alias [, ... ])]]

如果未指定 table_alias ,则函数名用作表名;在ROWS FROM()构造的情况下,使用第一个函数的名称。

如果未提供列别名,则对于返回基本数据类型的函数,列名也与函数名相同。
对于返回复合类型的函数,结果列获取该类型的各个属性的名称。

一些例子:

CREATE TABLE foo (fooid int, foosubid int, fooname text);CREATE FUNCTION getfoo(int) RETURNS SETOF foo AS $$SELECT * FROM foo WHERE fooid = $1;
$$ LANGUAGE SQL;SELECT * FROM getfoo(1) AS t1;SELECT * FROM fooWHERE foosubid IN (SELECT foosubidFROM getfoo(foo.fooid) zWHERE z.fooid = foo.fooid);CREATE VIEW vw_getfoo AS SELECT * FROM getfoo(1);SELECT * FROM vw_getfoo;

在某些情况下,定义可以根据调用方式返回不同列集的表函数很有用。
为了支持这一点,表函数可以声明为返回没有OUT参数的伪类型record
当在查询中使用这样的函数时,必须在查询本身中指定预期的行结构,这样系统才能知道如何解析和规划查询。
这种语法如下所示:

function_call [AS] alias (column_definition [, ... ])
function_call AS [alias] (column_definition [, ... ])
ROWS FROM( ... function_call AS (column_definition [, ... ]) [, ... ] )

当不使用ROWS FROM()语法时, column_definition 列表替换列别名列表,否则可以附加到FROM项;列定义中的名称用作列别名。
使用ROWS FROM()语法时,可以将 column_definition 列表单独附加到每个成员函数;或者如果只有一个成员函数并且没有WITH ORDINALITY子句,则可以编写 column_definition 列表来代替下面的列别名列表ROWS FROM()

考虑这个例子:

SELECT *FROM dblink('dbname=mydb', 'SELECT proname, prosrc FROM pg_proc')AS t1(proname name, prosrc text)WHERE proname LIKE 'bytea%';

这个dblink函数(dblink模块的一部分)执行一个远程查询。
它被声明为返回record,因为它可以用于任何类型的查询。
实际的列集必须在调用查询中指定,以便解析器知道,例如,` 应该扩展到什么。

此示例使用ROWS FROM

SELECT *
FROM ROWS FROM(json_to_recordset('[{"a":40,"b":"foo"},{"a":"100","b":"bar"}]')AS (a INTEGER, b TEXT),generate_series(1, 3)) AS x (p, q, s)
ORDER BY p;
pqs
40foo1
100bar2
3

它将两个函数连接到一个FROM目标中。
json_to_recordset()被指示返回两列,第一个integer和第二个text
直接使用generate_series()的结果。
ORDER BY子句将列值排序为整数。


7.2.1.5LATERAL子查询

出现在FROM中的子查询可以在关键字LATERAL之前。
这允许它们引用前面的FROM项提供的列。
(如果没有LATERAL,每个子查询都是独立评估的,因此不能交叉引用任何其他FROM项。)

出现在FROM中的表函数也可以在关键字LATERAL之前,但对于函数,关键字是可选的;在任何情况下,函数的参数都可以包含对前面FROM项提供的列的引用。

一个LATERAL项目可以出现在FROM列表的顶层,也可以出现在JOIN树中。
在后一种情况下,它也可以引用位于JOIN右侧的任何项目。

FROM项包含LATERAL交叉引用时,计算过程如下:对于提供交叉引用列的FROM项的每一行,或提供列的多个FROM项的行集,使用该行或行集的列值计算LATERAL项。
结果行与计算它们的行照常连接。
对于列源表中的每一行或行集重复此操作。

一个微不足道的例子LATERAL

SELECT * FROM foo, LATERAL (SELECT * FROM bar WHERE bar.id = foo.bar_id) ss;

这不是特别有用,因为它与更传统的结果完全相同

SELECT * FROM foo, bar WHERE bar.id = foo.bar_id;

LATERAL主要是在计算要连接的行时需要交叉引用的列。
一个常见的应用程序是为集合返回函数提供参数值。
例如,假设vertices(polygon)返回多边形的顶点集,我们可以识别存储在表中的多边形的紧密顶点:

SELECT p1.id, p2.id, v1, v2
FROM polygons p1, polygons p2,LATERAL vertices(p1.poly) v1,LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;

这个查询也可以写

SELECT p1.id, p2.id, v1, v2
FROM polygons p1 CROSS JOIN LATERAL vertices(p1.poly) v1,polygons p2 CROSS JOIN LATERAL vertices(p2.poly) v2
WHERE (v1 <-> v2) < 10 AND p1.id != p2.id;

如前所述,LATERAL关键字在本例中是不必要的,但为了清楚起见,我们使用它。)

LEFT JOINLATERAL子查询通常特别方便,这样即使LATERAL子查询没有为源行生成任何行,源行也会出现在结果中。
例如,如果get_product_names()返回制造商生产的产品名称,但我们表中的一些制造商目前不生产任何产品,我们可以找出哪些是这样的:

SELECT m.name
FROM manufacturers m LEFT JOIN LATERAL get_product_names(m.id) pname ON true
WHERE pname IS NULL;

7.2.2. The WHERE Clause

The syntax of the WHERE clause is

WHERE search_condition

其中 search_condition 是返回boolean类型值的任何值表达式(参见第4.2节)。

完成FROM的处理后,将根据搜索条件检查派生虚拟表的每一行。如果条件的结果为真,则该行将保留在输出表中,否则(即,如果结果为假或空)将被丢弃。搜索条件通常引用在FROM中生成的表的至少一列;这不是必需的,但否则WHERE子句将相当无用。


Note

内连接的连接条件可以写在WHEREJOIN中。
例如,这些表表达式是等效的:

FROM a, b WHERE a.id = b.id AND b.val > 5

和:

FROM a INNER JOIN b ON (a.id = b.id) WHERE b.val > 5

或者甚至:

FROM a NATURAL JOIN b WHERE b.val > 5

使用哪一种主要是风格问题。
FROM子句中的JOIN语法可能无法移植到其他SQL数据库管理系统,即使它是在SQL标准中。
对于外连接,没有选择:它们必须在FROM子句中完成。
外连接的ONUSING子句不等同于WHERE条件,因为它会导致添加行(对于不匹配的输入行)以及删除最终结果中的行。

以下是WHERE的一些示例:

SELECT ... FROM fdt WHERE c1 > 5SELECT ... FROM fdt WHERE c1 IN (1, 2, 3)SELECT ... FROM fdt WHERE c1 IN (SELECT c1 FROM t2)SELECT ... FROM fdt WHERE c1 IN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10)SELECT ... FROM fdt WHERE c1 BETWEEN (SELECT c3 FROM t2 WHERE c2 = fdt.c1 + 10) AND 100SELECT ... FROM fdt WHERE EXISTS (SELECT c1 FROM t2 WHERE c2 > fdt.c1)

fdt是在FROM中派生的表。
不满足WHERE搜索条件的行将从fdt中删除。
请注意使用标量子查询作为值表达式。
就像任何其他查询一样,子查询可以使用复杂的表表达式。
还请注意子查询中如何引用fdt
仅当c1也是子查询派生输入表中列的名称时,才需要将c1限定为fdt.c1
但是限定列名即使在不需要时也会增加清晰度。
此示例显示了外部查询的列命名范围如何扩展到其内部查询。


7.2.3 GROUP BYHAVING条款

传递WHERE过滤器后,派生的输入表可能需要使用GROUP BY子句进行分组,并使用HAVING消除组行。

SELECT select_listFROM ...[WHERE ...]GROUP BY grouping_column_reference [, grouping_column_reference]...

使用GROUP BY子句将表中所有列中具有相同值的行组合在一起。
列的列出顺序无关紧要。
其效果是将具有公共值的每组行组合到一个组行中,该组行表示组中的所有行。
这样做是为了消除应用于这些组的输出和/或计算聚合中的冗余。
例如:

=> SELECT * FROM test1;
xy
a3
c2
b5
a1

(4 rows)


SELECT x FROM test1 GROUP BY x;
x
a
b
c

(3 rows)


在第二个查询中,我们不能写入SELECT * FROM test1 GROUP BY x,因为列y没有单个值可以与每个组相关联。

通常,如果对表进行分组,则不能引用GROUP BY中未列出的列,除非在聚合表达式中。
聚合表达式的一个示例是:

=> SELECT x, sum(y) FROM test1 GROUP BY x;x | sum
--|--a | 4b | 5c | 2(3 rows)
***
这里的`sum`是一个聚合函数,它计算整个组的单个值。
有关可用聚合函数的更多信息,请参见[9.21](https://www.postgresql.org/docs/15/functions-aggregate.html)。***
Tips : 没有聚合表达式的分组可以有效地计算列中不同值的集合。
这也可以使用`DISTINCT`来实现(参见[7.3.3](https://www.postgresql.org/docs/15/queries-select-lists.html#QUERIES-DISTINCT))。这是另一个例子:它计算每个产品的总销售额(而不是所有产品的总销售额):```sql
SELECT product_id, p.name, (sum(s.units) * p.price) AS salesFROM products p LEFT JOIN sales s USING (product_id)GROUP BY product_id, p.name, p.price;

在此示例中,列product_idp.namep.price必须位于GROUP BY子句中,因为它们在查询选择列表中引用(但见下文)。
s.units不必位于GROUP BY列表中,因为它仅用于表示产品销售情况的聚合表达式(sum(...))。
对于每个产品,查询将返回一个关于产品所有销售情况的汇总行。

如果产品表是这样设置的,比如说product_id是主键,那么在上面的例子中按product_id分组就足够了,因为名称和价格在功能上依赖于产品ID,所以对于每个产品ID组返回哪个名称和价格值没有歧义。

严格SQL,GROUP BY只能按源表的列分组,但PostgreSQL对此进行了扩展,还允许GROUP BY按选择列表中的列分组。
还允许按值表达式而不是简单的列名分组。

如果使用GROUP BY对表进行分组,但只对某些组感兴趣,则可以使用HAVING子句(非常类似于WHERE)从结果中删除组。
语法为:

SELECT select_list FROM ... [WHERE ...] GROUP BY ... HAVING boolean_expression

HAVING中的表达式既可以引用已分组的表达式,也可以引用未分组的表达式(这必然涉及聚合函数)。

示例:

=> SELECT x, sum(y) FROM test1 GROUP BY x HAVING sum(y) > 3;x | sum
--|--a | 4b | 5(2 rows)
***
```sql 
SELECT x, sum(y) FROM test1 GROUP BY x HAVING x < 'c';x | sum
--|--a | 4b | 5(2 rows)
***
再举一个更现实的例子:```sql
SELECT product_id, p.name, (sum(s.units) * (p.price - p.cost)) AS profitFROM products p LEFT JOIN sales s USING (product_id)WHERE s.date > CURRENT_DATE - INTERVAL '4 weeks'GROUP BY product_id, p.name, p.price, p.costHAVING sum(p.price * s.units) > 5000;

在上面的示例中,WHERE子句按未分组的列选择行(表达式仅适用于最近四周的销售额),而HAVING子句将输出限制为总销售额超过5000的组。
请注意,查询的所有部分中的聚合表达式不一定需要相同。

如果查询包含聚合函数调用,但没有GROUP BY子句,则仍然会发生分组:结果是单个组行(或者如果单个行随后被HAVING消除,则可能根本没有行)。
如果它包含HAVING子句,即使没有任何聚合函数调用或GROUP BY子句也是如此。


7.2.4 GROUPING SETSCUBEROLLUP

使用分组集的概念可以进行比上述更复杂的分组操作。
FROMWHERE子句选择的数据由每个指定的分组集单独分组,为每个组计算聚合,就像简单的GROUP BY一样,然后返回结果。
例如:

=> SELECT * FROM items_sold;
brandsizesales
FooL10
FooM20
BarM15
BarL5

(4 rows)


SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
brandsizesum
Foo30
Bar20
L15
M35
50

(5 rows)


每个GROUPING SETS子列表可以指定零个或多个列或表达式,并且可以像直接在GROUP BY中一样进行解释。
空分组集意味着所有行都被聚合到单个组(即使没有输入行也会输出),如上所述,对于没有GROUP BY子句的聚合函数。

对于没有出现这些列的分组集,对分组列或表达式的引用将替换为结果行中的空值。
要区分特定输出行的分组结果,请参见表9.62。

提供了用于指定两种常见类型的分组集的速记符号。
形式的子句

ROLLUP ( e1, e2, e3, ... )

表示给定的表达式列表和列表的所有前缀,包括空列表;因此它相当于

GROUPING SETS (( e1, e2, e3, ... ),...( e1, e2 ),( e1 ),( )
)

这通常用于分析分层数据;例如,按部门、部门和公司范围的总工资。

形式的一个从句

CUBE ( e1, e2, ... )

表示给定列表及其所有可能的子集(即功率集)。
因此

CUBE ( a, b, c )

相当于

GROUPING SETS (( a, b, c ),( a, b    ),( a,    c ),( a       ),(    b, c ),(    b    ),(       c ),(         )
)

一个CUBEROLLUP的单个元素可以是单独的表达式,也可以是括号中元素的子列表。
在后一种情况下,为了生成单独的分组集,子列表被视为单个单元。
例如:

CUBE ( (a, b), (c, d) )

相当于

GROUPING SETS (( a, b, c, d ),( a, b       ),(       c, d ),(            )
)

ROLLUP ( a, (b, c), d )

相当于

GROUPING SETS (( a, b, c, d ),( a, b, c    ),( a          ),(            )
)

可以直接在GROUP BY子句中使用CUBEROLLUP构造,也可以嵌套在GROUPING SETS中。
如果一个GROUPING SETS嵌套在另一个子句中,则效果与内部子句的所有元素直接写入外部子句相同。

如果在单个GROUP BY子句中指定了多个分组项,则分组集的最终列表是各个项的向量积。
例如:

GROUP BY a, CUBE (b, c), GROUPING SETS ((d), (e))

相当于

GROUP BY GROUPING SETS ((a, b, c, d), (a, b, c, e),(a, b, d),    (a, b, e),(a, c, d),    (a, c, e),(a, d),       (a, e)
)

将多个分组项指定在一起时,最后一组分组集可能包含重复项。
例如:

GROUP BY ROLLUP (a, b), ROLLUP (a, c)

相当于

GROUP BY GROUPING SETS ((a, b, c),(a, b),(a, b),(a, c),(a),(a),(a, c),(a),()
)

如果这些重复项不受欢迎,可以直接在GROUP BY上使用DISTINCT子句删除它们。因此:

GROUP BY DISTINCT ROLLUP (a, b), ROLLUP (a, c)

等价于

GROUP BY GROUPING SETS ((a, b, c),(a, b),(a, c),(a),()
)

这与使用SELECT DISTINCT不同,因为输出行可能仍然包含重复项。
如果任何未分组的列包含NULL,它将无法与对同一列进行分组时使用的NULL区分开来。


Note : 构造(a, b)通常在表达式中被识别为行构造函数。
GROUP BY中,这不适用于表达式的顶层,并且(a, b)被解析为如上所述的表达式列表。
如果出于某种原因,您在分组表达式中需要行构造函数,请使用ROW(a, b)


7.2.5窗口函数处理

如果查询包含任何窗口函数(参见第3.5节、第9.22节和第4.2.8节),则在执行任何分组、聚合和HAVING过滤后对这些函数进行评估。
也就是说,如果查询使用任何聚合、GROUP BYHAVING,则窗口函数看到的行是组行,而不是来自FROM/WHERE的原始表行。

当使用多个窗口函数时,保证在其窗口定义中具有语法等效的PARTITION BYORDER BY子句的所有窗口函数在数据的一次传递中被评估。
因此,即使ORDER BY不能唯一确定排序,它们也会看到相同的排序顺序。
但是,不保证具有不同PARTITION BYORDER BY规范的函数的评估。
(在这种情况下,通常需要在窗口函数评估之间进行排序步骤,并且排序不能保证保留其ORDER BY认为等效的行的排序。)

目前,窗口函数总是需要预先排序的数据,因此查询输出将根据一个或另一个窗口函数的PARTITION BY/ORDER BY子句排序。
但是,不建议依赖于此。
如果您想确保结果以特定方式排序,请使用显式的顶级ORDER BY子句。


7.3.选择列表

如上一节所示,SELECT命令中的table表达式通过可能组合表、视图、消除行、分组等方式构造中间虚拟表,该表最终由选择列表传递给处理,选择列表决定中间表的哪些列实际输出。


7.3.1选择-列表项目

最简单的选择列表是` ,它发出表表达式产生的所有列。
否则,选择列表是以逗号分隔的值表达式列表(如第4.2节所定义)。
例如,它可以是列名列表:

SELECT a, b, c FROM ...

列名abc要么是FROM子句中引用的表列的实际名称,要么是第7.2.1.2节中解释的别名。
select列表中可用的名称空间与WHERE子句中相同,除非使用分组,在这种情况下,它与HAVING子句中相同。

如果多个表具有同名的列,则还必须给出表名,如下所示:

SELECT tbl1.a, tbl2.a, tbl1.b FROM ...

在处理多个表时,请求特定表的所有列也很有用:

SELECT tbl1.*, tbl2.a FROM ...

有关 table_name .*符号`. 第8.16.5节

如果在选择列表中使用任意值表达式,它会在概念上向返回的表中添加一个新的虚拟列。
值表达式对每个结果行计算一次,该行的值替换任何列引用。
但是选择列表中的表达式不必引用FROM的表表达式中的任何列;例如,它们可以是常量算术表达式。


7.3.2列标签

可以为select列表中的条目指定名称以供后续处理,例如用于ORDER BY子句或由客户端应用程序显示。
例如:

SELECT a AS value, b + c AS sum FROM ...

如果没有使用AS指定输出列名,系统将指定一个默认列名。
对于简单的列引用,这是被引用列的名称。
对于函数调用,这是函数的名称。
对于复杂的表达式,系统将生成一个通用名称。

关键字AS通常是可选的,但在所需列名与PostgreSQL关键字匹配的某些情况下,您必须写入AS或双引号列名以避免歧义。
(附录C显示了哪些关键字需要将AS用作列标签。)例如,FROM就是这样一个关键字,因此这不起作用:

SELECT a from, b + c AS sum FROM ...

但它们中的任何一个都可以:

SELECT a AS from, b + c AS sum FROM ...
SELECT a "from", b + c AS sum FROM ...

为了最大限度地防止将来可能添加的关键字,建议您始终将输出列名写入AS或双引号。


Note : 此处输出列的命名与FROM中的命名不同(参见第7.2.1.2节)。
可以重命名同一列两次,但选择列表中分配的名称将被传递。


7.3.3.DISTINCT

选择列表处理完成后,结果表可以选择消除重复行。
DISTINCT关键字直接写在SELECT后面以指定:

SELECT DISTINCT select_list ...

(可以使用关键字ALL代替DISTINCT来指定保留所有行的默认行为。)

显然,如果两行在至少一个列值上不同,则两行被认为是不同的。
在此比较中,空值被认为是相等的。

或者,任意表达式可以确定哪些行被认为是不同的:

SELECT DISTINCT ON (expression [, expression ...]) select_list ...

这里 expression 是针对所有行计算的任意值表达式。
所有表达式相等的一组行被认为是重复的,并且只有该组的第一行保留在输出中。
请注意,除非查询在足够多的列上排序,以保证到达DISTINCT过滤器的行的唯一顺序,否则一组的“第一行”是不可预测的。
DISTINCT ON处理发生在ORDER BY排序之后。)

DISTINCT ON子句并不是SQL标准的一部分,由于其结果的潜在不确定性,有时会被认为是不好的风格。
通过明智地使用GROUP BY和子查询FROM,可以避免这种结构,但它通常是最方便的选择。


7.4.组合查询(UNIONINTERSECTEXCEPT

可以使用集合操作联合、交集和差异组合两个查询的结果。
语法是

query1 UNION [ALL] query2
query1 INTERSECT [ALL] query2
query1 EXCEPT [ALL] query2

其中 query1 和 query2 是可以使用到目前为止讨论的任何功能的查询。

UNION有效地将 query2 的结果附加到 query1 的结果(尽管不能保证这是实际返回行的顺序)。
此外,它从结果中消除重复行,与DISTINCT相同,除非使用UNION ALL

INTERSECT返回 query1 结果和query2结果中的所有 query2 。
除非使用INTERSECT ALL,否则会删除重复的行。

EXCEPT返回在 query1 的结果中但不在 query2 的结果中的所有行。
(这有时称为两个查询之间的差异。)同样,除非使用EXCEPT ALL,否则重复项将被消除。

为了计算两个查询的并集、交集或差异,两个查询必须“并集兼容”,这意味着它们返回相同数量的列,并且相应的列具有兼容的数据类型,如第10.5节所述。

集合操作可以组合,例如

query1 UNION query2 EXCEPT query3

这相当于

(query1 UNION query2) EXCEPT query3

如图所示,您可以使用括号来控制计算顺序。
如果没有括号,UNIONEXCEPT将从左到右关联,但INTERSECT比这两个运算符绑定得更紧。
因此

query1 UNION query2 INTERSECT query3

意味着

query1 UNION (query2 INTERSECT query3)

您还可以用括号将单个 query 括起来。
如果 query 需要使用以下部分中讨论的任何子句,例如LIMIT,这一点很重要。
如果没有括号,您将获得语法错误,否则子句将被理解为应用于集合操作的输出,而不是其输入之一。
例如,

SELECT a FROM b UNION SELECT x FROM y LIMIT 10

被接受,但这意味着

(SELECT a FROM b UNION SELECT x FROM y) LIMIT 10

不是

SELECT a FROM b UNION (SELECT x FROM y LIMIT 10)

7.5.排序行(ORDER BY

在查询生成输出表后(在处理选择列表后),可以选择对其进行排序。
如果未选择排序,则将行以未指定的顺序返回。
在这种情况下,实际顺序将取决于扫描和连接计划类型以及磁盘上的顺序,但不能依赖它。
只有在显式选择排序步骤时,才能保证特定的输出顺序。

ORDER BY子句指定排序顺序:

SELECT select_listFROM table_expressionORDER BY sort_expression1 [ASC | DESC] [NULLS { FIRST | LAST }][, sort_expression2 [ASC | DESC] [NULLS { FIRST | LAST }] ...]

排序表达式可以是在查询的选择列表中有效的任何表达式。
一个例子是:

SELECT a, b FROM table1 ORDER BY a + b, c;

当指定了多个表达式时,后面的值用于根据前面的值对相等的行进行排序。
每个表达式后面可以跟一个可选的ASCDESC关键字,以将排序方向设置为升序或降序。
ASC顺序是默认的。
升序将较小的值放在首位,其中“较小”是根据<运算符定义的。
类似地,降序由>运算符确定。
[6]

可以使用NULLS FIRSTNULLS LAST选项来确定在排序顺序中空值出现在非空值之前还是之后。
默认情况下,空值排序时就好像大于任何非空值一样;也就是说,NULLS FIRSTDESC顺序的默认值,否则为NULLS LAST

请注意,每个排序列的排序选项是独立考虑的。
例如ORDER BY x, y DESC表示ORDER BY x ASC, y DESC,这与ORDER BY x DESC, y DESC不同。

一个 sort_expression 也可以是列标签或输出列的编号,如:

SELECT a + b AS sum, c FROM table1 ORDER BY sum;
SELECT a, max(b) FROM table1 GROUP BY a ORDER BY 1;

两者都按第一个输出列排序。
请注意,输出列名必须独立,也就是说,它不能在表达式中使用-例如,这是不正确的:

SELECT a + b AS sum, c FROM table1 ORDER BY sum + c;          -- wrong

设置此限制是为了减少歧义。
如果ORDER BY项是一个简单的名称,可以匹配输出列名或表表达式中的列,则仍然存在歧义。
在这种情况下使用输出列。
如果您使用AS重命名输出列以匹配其他表列的名称,这只会导致混淆。

ORDER BY可以应用于UNIONINTERSECTEXCEPT组合的结果,但在这种情况下,只允许按输出列名或数字排序,不允许按表达式排序。

[6]实际上,PostgreSQL使用表达式数据类型的默认B树运算符类来确定ASCDESC的排序顺序。
通常,数据类型将被设置为<>运算符对应于这种排序顺序,但是用户定义数据类型的设计者可以选择做一些不同的事情。


7.6.LIMITOFFSET

LIMITOFFSET允许您检索查询其余部分生成的行的一部分:

SELECT select_listFROM table_expression[ ORDER BY ... ][ LIMIT { number | ALL } ] [ OFFSET number ]

如果给出了限制计数,则返回的行数不会超过这个数(但如果查询本身产生的行数较少,则可能更少)。
LIMIT ALL与省略LIMIT相同,带有NULL参数的LIMIT也是如此。

OFFSET表示在开始返回行之前跳过许多行。
OFFSET 0与省略OFFSET子句相同,带有NULL参数的OFFSET也是如此。

如果同时出现OFFSETLIMIT,则在开始计算返回的LIMIT行之前跳过OFFSET行。

使用LIMIT时,使用ORDER BY子句将结果行限制为唯一的顺序非常重要。
否则,您将获得查询行的不可预测子集。
您可能会要求第十行到第二十行,但第十行到第二十行的顺序是什么?排序未知,除非您指定了ORDER BY

查询优化器在生成查询计划时会考虑LIMIT,因此您很可能会得到不同的计划(产生不同的行顺序),这取决于您为LIMITOFFSET提供的内容。
因此,使用不同的LIMIT/OFFSET值来选择查询结果的不同子集将给出不一致的结果除非您使用ORDER BY强制执行可预测的结果排序。
这不是bug;这是SQL不承诺以任何特定顺序传递查询结果的固有结果,除非ORDER BY用于约束顺序。

OFFSET跳过的行仍然需要在服务器内部计算;因此,大型OFFSET可能效率低下。


7.7.VALUES清单

VALUES提供了一种生成“常量表”的方法,该方法可以在查询中使用,而无需在磁盘上实际创建和填充表。
语法是

VALUES ( expression [, ...] ) [, ...]

每个带括号的表达式列表在表中生成一行。
列表必须具有相同数量的元素(即表中的列数),并且每个列表中的相应条目必须具有兼容的数据类型。
分配给结果每列的实际数据类型使用与UNION相同的规则确定(参见第10.5节)。

举个例子:

VALUES (1, 'one'), (2, 'two'), (3, 'three');

将返回一个包含两列三行的表。
它实际上相当于:

SELECT 1 AS column1, 'one' AS column2
UNION ALL
SELECT 2, 'two'
UNION ALL
SELECT 3, 'three';

默认情况下,PostgreSQL为VALUES表的列分配名称column1column2等。
列名不是由SQL标准指定的,不同的数据库系统的做法不同,因此通常最好使用表别名列表覆盖默认名称,如下所示:

=> SELECT * FROM (VALUES (1, 'one'), (2, 'two'), (3, 'three')) AS t (num,letter);
numletter
1one
2two
3three

(3 rows)


在语法上,VALUES表达式列表被视为等同于:

SELECT select_list FROM table_expression

并且可以出现在SELECT可以出现的任何地方。
例如,您可以将其用作UNION的一部分,或附加 sort_specification (ORDER BYLIMIT和/或OFFSET)。
VALUES最常用作INSERT命令中的数据源,其次最常用于子查询。

有关详细信息,请参阅值。


7.8.WITH查询(通用表表达式)

WITH提供了一种编写用于大型查询的辅助语句的方法。
这些语句通常称为Common Table Expressions或CTE,可以被认为是定义仅为一个查询存在的临时表。
WITH子句中的每个辅助语句可以是SELECTINSERTUPDATEDELETEWITH子句本身附加到可以是SELECTINSERTUPDATEDELETEMERGE的主语句。


7.8.1WITH中的SELECT

WITH中,SELECT的基本价值是将复杂的查询分解为更简单的部分。

WITH regional_sales AS (SELECT region, SUM(amount) AS total_salesFROM ordersGROUP BY region
), top_regions AS (SELECT regionFROM regional_salesWHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,product,SUM(quantity) AS product_units,SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;

它只在最高销售区域显示每个产品的销售总额。
WITH子句定义了两个名为regional_salestop_regions的辅助语句,其中regional_sales的输出用于top_regionstop_regions的输出用于主SELECT查询。
这个例子可以在没有WITH的情况下编写,但是我们需要两个级别的嵌套子SELECT
这样更容易遵循。


7.8.2递归查询

可选的RECURSIVE修饰符将WITH从一个简单的语法方便变成了一个功能,可以完成标准SQL中无法完成的事情。
使用RECURSIVEWITH查询可以引用它自己的输出。
一个非常简单的例子是这个查询,它将1到100的整数相加:

WITH RECURSIVE t(n) AS (VALUES (1)UNION ALLSELECT n+1 FROM t WHERE n < 100
)
SELECT sum(n) FROM t;

递归WITH查询的一般形式始终是非递归项,然后是UNION(或UNION ALL),然后是递归项,其中只有递归项可以包含对查询自身输出的引用。
这样的查询执行如下:

递归查询评估

  1. 计算非递归术语。
    对于UNION(但不是UNION ALL),丢弃重复行。
    在递归查询的结果中包含所有剩余行,并将它们放在临时工作表中。

  2. 只要工作台不是空的,就重复以下步骤:

    1. 计算递归项,将工作表的当前内容替换为递归自引用。
      对于UNION(但不是UNION ALL),丢弃重复行和重复任何先前结果行的行。
      在递归查询的结果中包含所有剩余行,并将它们放置在临时中间表中。

    2. 将工作表的内容替换为中间表的内容,然后清空中间表。


Note : 虽然RECURSIVE允许递归指定查询,但在内部此类查询是迭代评估的。

在上面的例子中,工作表在每个步骤中只有一行,它在连续的步骤中接受从1到100的值。
在第100步,由于WHERE子句,没有输出,因此查询终止。

递归查询通常用于处理分层或树形结构的数据。
一个有用的例子是此查询,用于查找产品的所有直接和间接子部分,仅给定一个显示直接包含的表:

WITH RECURSIVE included_parts(sub_part, part, quantity) AS (SELECT sub_part, part, quantity FROM parts WHERE part = 'our_product'UNION ALLSELECT p.sub_part, p.part, p.quantity * pr.quantityFROM included_parts pr, parts pWHERE p.part = pr.sub_part
)
SELECT sub_part, SUM(quantity) as total_quantity
FROM included_parts
GROUP BY sub_part

7.8.2.1搜索顺序

使用递归查询计算树遍历时,您可能希望以深度优先或广度优先的顺序对结果进行排序。
这可以通过计算排序列和其他数据列一起,并在最后对结果进行排序来完成。
请注意,这实际上并不控制查询评估访问行的顺序;这与SQLimplementation-dependent一样。
这种方法只是提供了一种方便的方法来对结果进行排序。

为了创建深度优先顺序,我们为每个结果行计算一个我们迄今为止访问过的行数组。
例如,考虑以下使用link字段搜索表tree的查询:

WITH RECURSIVE search_tree(id, link, data) AS (SELECT t.id, t.link, t.dataFROM tree tUNION ALLSELECT t.id, t.link, t.dataFROM tree t, search_tree stWHERE t.id = st.link
)
SELECT * FROM search_tree;

要添加深度优先排序信息,您可以这样写:

WITH RECURSIVE search_tree(id, link, data, path) AS (SELECT t.id, t.link, t.data, ARRAY[t.id]FROM tree tUNION ALLSELECT t.id, t.link, t.data, path || t.idFROM tree t, search_tree stWHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY path;

在需要使用多个字段来标识一行的一般情况下,使用行数组。
例如,如果我们需要跟踪字段f1f2

WITH RECURSIVE search_tree(id, link, data, path) AS (SELECT t.id, t.link, t.data, ARRAY[ROW(t.f1, t.f2)]FROM tree tUNION ALLSELECT t.id, t.link, t.data, path || ROW(t.f1, t.f2)FROM tree t, search_tree stWHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY path;

Tips : 在只需要跟踪一个字段的常见情况下省略ROW()语法。
这允许使用简单数组而不是复合类型数组,从而提高效率。

要创建广度优先顺序,您可以添加一个跟踪搜索深度的列,例如:

WITH RECURSIVE search_tree(id, link, data, depth) AS (SELECT t.id, t.link, t.data, 0FROM tree tUNION ALLSELECT t.id, t.link, t.data, depth + 1FROM tree t, search_tree stWHERE t.id = st.link
)
SELECT * FROM search_tree ORDER BY depth;

要获得稳定的排序,请将数据列添加为辅助排序列。


Tips : 递归查询评估算法以广度优先的搜索顺序生成其输出。
然而,这是一个实现细节,依赖它可能是不合理的。
每个级别中行的顺序肯定是未定义的,因此在任何情况下都可能需要一些明确的顺序。

有内置语法可以计算深度或广度优先排序列。
例如:

WITH RECURSIVE search_tree(id, link, data) AS (SELECT t.id, t.link, t.dataFROM tree tUNION ALLSELECT t.id, t.link, t.dataFROM tree t, search_tree stWHERE t.id = st.link
) SEARCH DEPTH FIRST BY id SET ordercol
SELECT * FROM search_tree ORDER BY ordercol;WITH RECURSIVE search_tree(id, link, data) AS (SELECT t.id, t.link, t.dataFROM tree tUNION ALLSELECT t.id, t.link, t.dataFROM tree t, search_tree stWHERE t.id = st.link
) SEARCH BREADTH FIRST BY id SET ordercol
SELECT * FROM search_tree ORDER BY ordercol;

此语法在内部扩展为类似于上述手写形式。
SEARCH子句指定是否需要深度优先或广度优先搜索、要跟踪排序的列列表以及包含可用于排序的结果数据的列名。
该列将隐式添加到CTE的输出行中。


7.8.2.2循环检测

在处理递归查询时,必须确保查询的递归部分最终不会返回元组,否则查询将无限期循环。
有时,使用UNION而不是UNION ALL可以通过丢弃与先前输出行重复的行来实现这一点。
然而,通常一个循环不涉及完全重复的输出行:可能只需要检查一个或几个字段来查看之前是否到达过同一点。
处理这种情况的标准方法是计算已经访问过的值的数组。
例如,再次考虑以下使用link字段搜索表graph的查询:

WITH RECURSIVE search_graph(id, link, data, depth) AS (SELECT g.id, g.link, g.data, 0FROM graph gUNION ALLSELECT g.id, g.link, g.data, sg.depth + 1FROM graph g, search_graph sgWHERE g.id = sg.link
)
SELECT * FROM search_graph;

如果link关系包含循环,则此查询将循环。
因为我们需要“深度”输出,因此仅将UNION ALL更改为UNION不会消除循环。
相反,我们需要识别在遵循特定链接路径时是否再次到达同一行。
我们为容易循环的查询添加两列is_cyclepath

WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS (SELECT g.id, g.link, g.data, 0,false,ARRAY[g.id]FROM graph gUNION ALLSELECT g.id, g.link, g.data, sg.depth + 1,g.id = ANY(path),path || g.idFROM graph g, search_graph sgWHERE g.id = sg.link AND NOT is_cycle
)
SELECT * FROM search_graph;

除了防止循环之外,数组值本身通常很有用,因为它代表了到达任何特定行所采用的“路径”。

在需要检查多个字段以识别循环的一般情况下,使用行数组。
例如,如果我们需要比较字段f1f2

WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS (SELECT g.id, g.link, g.data, 0,false,ARRAY[ROW(g.f1, g.f2)]FROM graph gUNION ALLSELECT g.id, g.link, g.data, sg.depth + 1,ROW(g.f1, g.f2) = ANY(path),path || ROW(g.f1, g.f2)FROM graph g, search_graph sgWHERE g.id = sg.link AND NOT is_cycle
)
SELECT * FROM search_graph;

Tip : 在只需要检查一个字段来识别循环的常见情况下省略ROW()语法。这允许使用简单数组而不是复合类型数组,从而提高效率。


有内置语法来简化周期检测。
上面的查询也可以这样写:

WITH RECURSIVE search_graph(id, link, data, depth) AS (SELECT g.id, g.link, g.data, 1FROM graph gUNION ALLSELECT g.id, g.link, g.data, sg.depth + 1FROM graph g, search_graph sgWHERE g.id = sg.link
) CYCLE id SET is_cycle USING path
SELECT * FROM search_graph;

它将在内部重写为上述形式。
CYCLE子句首先指定要跟踪以进行循环检测的列列表,然后指定显示是否检测到循环的列名,最后指定将跟踪路径的另一列的名称。
循环和路径列将隐式添加到CTE的输出行中。


Tips : 循环路径列的计算方式与上一节中显示的深度优先排序列相同。
查询可以同时具有SEARCHCYCLE子句,但是深度优先搜索规范和循环检测规范会产生冗余计算,因此只使用CYCLE子句和按路径列排序会更有效。
如果需要广度优先排序,那么指定SEARCHCYCLE会很有用。

当您不确定查询是否会循环时,测试查询的一个有用技巧是在父查询中放置LIMIT
例如,此查询将在没有LIMIT的情况下永远循环:

WITH RECURSIVE t(n) AS (SELECT 1UNION ALLSELECT n+1 FROM t
)
SELECT n FROM t LIMIT 100;

这是因为PostgreSQL的实现只计算父查询实际获取的WITH查询的行数。
不建议在生产环境中使用此技巧,因为其他系统的工作方式可能不同。
此外,如果您让外部查询对递归查询的结果进行排序或将它们连接到其他表,它通常不会起作用,因为在这种情况下,外部查询通常会尝试获取所有WITH查询的输出。


7.8.3公用表表达式物化

一个有用的属性是WITH查询通常只在每次执行父查询时计算一次,即使它们被父查询或兄弟WITH查询多次引用。
因此,在多个地方需要的昂贵计算可以放在WITH查询中以避免冗余工作。
另一个可能的应用是防止对具有副作用的函数进行不必要的多次评估。
然而,这枚硬币的另一面是优化器无法将限制从父查询向下推送到乘引用WITH查询中,因为这可能会影响WITH查询的输出的所有用途,而它应该只影响一个。
乘引用WITH查询将被评估为写入,而不会抑制父查询之后可能丢弃的行。
(但是,如上所述,如果对查询的引用只需要有限数量的行,评估可能会提前停止。)

但是,如果WITH查询是非递归的并且没有副作用(也就是说,它是一个不包含易失性函数的SELECT),那么它可以折叠到父查询中,允许对两个查询级别进行联合优化。
默认情况下,如果父查询仅引用一次WITH查询,则会发生这种情况,但如果它多次引用WITH查询,则不会发生这种情况。
您可以通过指定MATERIALIZED来强制单独计算WITH查询,或者通过指定NOT MATERIALIZED来强制将其合并到父查询中。
后一种选择有重复计算WITH查询的风险,但如果每次使用WITH查询只需要WITH查询完整输出的一小部分,它仍然可以节省净成本。

这些规则的一个简单例子是

WITH w AS (SELECT * FROM big_table
)
SELECT * FROM w WHERE key = 123;

WITH查询将被折叠,生成与

SELECT * FROM big_table WHERE key = 123;

特别是,如果key上有索引,它可能仅用于获取key = 123的行。
另一方面,在

WITH w AS (SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

将物化WITH查询,生成big_table的临时副本,然后将其与自身连接-没有任何索引的好处

WITH w AS NOT MATERIALIZED (SELECT * FROM big_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref
WHERE w2.key = 123;

以便父查询的限制可以直接应用于big_table的扫描。

一个不希望NOT MATERIALIZED例子是

WITH w AS (SELECT key, very_expensive_function(val) as f FROM some_table
)
SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;

这里,WITH查询的物化确保每个表行只计算一次very_expensive_function,而不是两次。

上面的例子只显示了WITHSELECT一起使用,但是它可以以同样的方式附加到INSERTUPDATEDELETEMERGE
在每种情况下,它都有效地提供了可以在主命令中引用的临时表。


7.8.4数据修改WITH

您可以在WITH中使用大多数数据修改语句(INSERTUPDATEDELETE,但不能MERGE)。
这允许您在同一个查询中执行多个不同的操作。
一个例子是:

WITH moved_rows AS (DELETE FROM productsWHERE"date" >= '2010-10-01' AND"date" < '2010-11-01'RETURNING *
)
INSERT INTO products_log
SELECT * FROM moved_rows;

此查询有效地将行从products移动到products_log
WITH中的DELETEproducts中删除指定的行,通过其RETURNING子句返回其内容;然后主查询读取该输出并将其插入products_log

上面例子的一个要点是WITH子句附加到INSERT,而不是INSERT中的子SELECT
这是必要的,因为数据修改语句只允许在附加到顶级语句的WITH子句中使用。
但是,正常的WITH可见性规则适用,因此可以从子SELECT引用WITH语句的输出。

如上例所示,WITH中的数据修改语句通常具有RETURNING子句(参见第6.4节)。
RETURNING子句的输出,而不是数据修改语句的目标表,构成了查询的其余部分可以引用的临时表。
如果WITH中的数据修改语句缺少RETURNING子句,则它不构成临时表,也不能在查询的其余部分中引用。
这样的语句仍然会被执行。
一个not-particularly-useful的例子是:

WITH t AS (DELETE FROM foo
)
DELETE FROM bar;

此示例将删除表foobar中的所有行。
报告给客户端的受影响行数仅包括从bar中删除的行。

不允许在数据修改语句中使用递归自引用。
在某些情况下,可以通过引用递归WITH的输出来解决此限制,例如:

WITH RECURSIVE included_parts(sub_part, part) AS (SELECT sub_part, part FROM parts WHERE part = 'our_product'UNION ALLSELECT p.sub_part, p.partFROM included_parts pr, parts pWHERE p.part = pr.sub_part
)
DELETE FROM partsWHERE part IN (SELECT part FROM included_parts);

This query would remove all direct and indirect subparts of a product.

与主查询是否读取所有(或任何)输出无关,WITH中的数据修改语句只执行一次,并且始终完成。
请注意,这与WITH中的SELECT规则不同:如上一节所述,SELECT的执行仅在主查询需要其输出时进行。

WITH中,子语句彼此之间并与主查询同时执行。
因此,当在WITH中使用数据修改语句时,指定更新实际发生的顺序是不可预测的。
所有语句都使用相同的快照执行(见第13章),因此它们不能“看到”彼此对目标表的影响。
这减轻了行更新实际顺序不可预测的影响,并意味着RETURNING数据是在不同的WITH子语句和主查询之间传达更改的唯一方法。
这方面的一个例子是

WITH t AS (UPDATE products SET price = price * 1.05RETURNING *
)
SELECT * FROM products;

外部SELECT将返回UPDATE操作之前的原始价格,而在

WITH t AS (UPDATE products SET price = price * 1.05RETURNING *
)
SELECT * FROM t;

外部SELECT将返回更新的数据。

不支持在单个语句中两次尝试更新同一行。
只进行了一次修改,但不容易(有时也不可能)可靠地预测是哪一次。
这也适用于删除同一语句中已经更新的行:只执行更新。
因此,您通常应该避免在单个语句中两次尝试修改单行。
特别是避免编写可能影响主语句或兄弟子语句更改的相同行的WITH子语句。
这种语句的效果是不可预测的。

目前,在WITH中用作数据修改语句目标的任何表都不能有条件规则,也不能有ALSO规则,也不能有扩展为多个语句的INSTEAD规则。


第8章 数据类型

PostgreSQL有一组丰富的本机数据类型可供用户使用。
用户可以使用CREATE TYPE命令向PostgreSQL添加新类型。

表8.1显示了所有内置的通用数据类型,“别名”列中列出的大多数替代名称都是PostgreSQL出于历史原因在内部使用的名称,此外,还有一些内部使用或不推荐使用的类型可用,但这里没有列出。


表8.1 数据类型

名称别名描述
bigintint8有符号八字节整数
bigserialserial8自动递增八字节整数
bit[(n)]固定长度位串
bit varying[(n)]varbit[(n)]可变长度位串
booleanbool逻辑布尔(真/假)
box平面上的矩形框
bytea二进制数据(“字节数组”)
character[(n)]char[(n)]固定长度字符串
character varying[(n)]varchar[(n)]可变长度字符串
cidrIPv4或IPv6网络地址
circle平面
date日历日期(年、月、日)
double precisionfloat8双精度浮点数(
inetIPv4或IPv6主机地址
integerintint4有符号四字节整数
interval [ fields ] [(p)]时间跨度
json文本JSON数据
jsonb二进制JSON数据,分解
line平面上的无限线
lseg平面上的线段
macaddrMAC(媒体访问控制)地址
macaddr8MAC(媒体访问控制)地址(EUI-64格式)
money货币金额
numeric [(p, s)]decimal [(p, s)]可选择精度的精确数字
path平面上的几何路径
pg_lsnPostgreSQL日志序列号
pg_snapshot用户级事务ID快照
point几何点
polygon封闭几何路径平面上
realfloat4单精度浮点数(4字节)
smallintint2有符号两字节整数
smallserialserial2自动递增两字节整数
serialserial4自动递增四字节整数
text可变长度字符串
time [(p)] [ without time zone ]time of day(无时区)
time [(p)] with time zonetimetztime of day,包括时区
timestamp [(p)] [ without time zone ]日期和时间(无时区)
timestamp [(p)] with time zonetimestamptz日期和时间,包括时区
tsquery文本搜索查询
tsvector文本搜索文档
txid_snapshot用户级事务ID快照(已弃用;参见pg_snapshot
uuid通用唯一标识符
xmlXML数据

兼容性

以下类型(或其拼写)由SQL指定:bigintbitbit varyingbooleancharcharacter varyingcharactervarchardatedouble precisionintegerintervalnumericdecimalrealsmallinttime(带或不带时区)、timestamp(带或不带时区)、xml

每种数据类型都有由其输入和输出函数决定的外部表示。
许多内置类型都有明显的外部格式。
但是,有几种类型要么是PostgreSQL独有的,例如几何路径,要么有几种可能的格式,例如日期和时间类型。
一些输入和输出函数是不可反转的,即输出函数的结果与原始输入相比可能会失去准确性。


8.1 数字类型

数字类型由两字节、四字节和八字节整数、四字节和八字节浮点数以及selectable-precision小数组成。
表8.2列出了可用的类型。


表8.2.数字类型

名称存储大小描述范围
smallint2字节小范围整数-32768到+32767
integer4字节整数的典型选择-2147483648到+2147483647
bigint8字节大范围整数-9223372036854775808到+9223372036854775807
decimal变量用户指定精度,精确小数点前最多131072位;小数点后最多16383位
numeric变量用户指定精度,精确小数点前最多131072位;小数点后最多16383位
real4字节可变精度,不精确6位十进制数字精度
double precision8字节可变精度,不精确15位十进制数字精度
smallserial2字节小型自动递增整数
serial
bigserial自动递增整数1到9223372036854775807

数值类型的常量语法在第4.1.2节中描述。
数值类型有一整套相应的算术运算符和函数。
有关详细信息,请参阅第9章。
以下部分详细描述了类型。


8.1.1整数类型

类型smallintintegerbigint存储各种范围的整数,即没有小数部分的数字。
尝试存储允许范围之外的值将导致错误。

通常选择integer类型,因为它在范围、存储大小和性能之间提供了最佳平衡。
smallint类型通常仅在磁盘空间较高时使用。
bigint类型设计用于integer类型范围不足时。

SQL仅指定整数类型integer(或int)、smallintbigint
类型名称int2int4int8是扩展,其他一些SQL数据库系统也使用它们。


8.1.2任意精度数字

类型numeric可以存储具有大量数字的数字。特别推荐用于存储货币金额和其他需要精确的数量。使用numeric进行计算会尽可能产生精确的结果,例如加法、减法、乘法。但是,与整数类型或下一节中描述的浮点类型相比,numeric的计算非常慢。

我们在下面使用以下术语:numeric精度是整个数字中有效数字的总数,即小数点两边的位数。numeric刻度是小数点右边小数部分的小数位数。所以数字23.5141的精度为6,刻度为4。整数可以认为刻度为零。

可以配置numeric列的最大精度和最大刻度。要声明numeric类型的列,请使用以下语法:

NUMERIC(precision, scale)

精度必须是正数,而刻度可以是正数或负数(见下文)。或者:

NUMERIC(precision)

选择比例为0。指定:

NUMERIC

如果没有任何精度或刻度,则创建一个“无约束数字”列,其中可以存储任何长度的数值,直至实现限制。这种列不会将输入值强制为任何特定刻度,而具有声明刻度的numeric列将强制输入值为该刻度。(SQL标准要求默认刻度为0,即强制为整数精度。我们觉得这有点没用。如果您担心可移植性,请始终明确指定精度和刻度。)


Note : 可以在numeric类型声明中明确指定的最大精度为1000。不受约束的numeric列受表8.2中描述的限制。

如果要存储的值的小数位数大于该列声明的小数位数,系统会将该值四舍五入到指定的小数位数。然后,如果小数点左侧的位数超过声明的精度减去声明的小数位数,则会引发错误。例如,声明为

NUMERIC(3, 1)

将值四舍五入到小数点后1位,并且可以存储-99.9和99.9之间的值,包括。

从PostgreSQL15开始,允许声明一个小数点为负的numeric列。然后值将四舍五入到小数点的左侧。精度仍然表示非四舍五入位数的最大值。因此,声明为

NUMERIC(2, -3)

将值四舍五入到最接近的千,并且可以存储-99000到99000之间的值,包括。它还允许声明大于声明精度的刻度。这样的列只能保存小数值,它要求小数点右侧的零位数至少是声明的刻度减去声明的精度。例如,声明为

NUMERIC(3, 5)

将值四舍五入到小数点后5位,并且可以存储-0.00999和0.00999之间的值,包括。


Note : PostgreSQL允许numeric类型声明中的小数位数为-1000到1000范围内的任何值。但是,SQL标准要求小数位数在0到*precision*范围内。在该范围之外使用小数位数可能无法移植到其他数据库系统。

数值是物理存储的,没有任何额外的前导零或尾随零。因此,列的声明精度和小数位数是最大值,而不是固定分配。(从这个意义上说,numeric类型更类似于varchar(*n*)而不是char(*n*)。)实际存储要求是每组四个十进制数字两个字节,外加三到八个字节的开销。


除了普通数值之外,numeric类型还有几个特殊值:

Infinity
-Infinity
NaN

它们改编自IEEE 754标准,分别表示“无穷大”、“负无穷大”和“非-a-number”。在SQL命令中将这些值作为常量写入时,必须在它们周围加上引号,例如UPDATE table SET x = '-Infinity'。在输入时,这些字符串以不区分大小写的方式识别。无穷大值也可以拼写为inf-inf

无穷大值的行为符合数学期望。例如,Infinity加上任何有限值等于InfinityInfinity加上Infinity也是如此;但是Infinity减去Infinity会产生NaN(不是数字),因为它没有明确定义的解释。请注意,无穷大只能存储在不受约束的numeric列中,因为它理论上超过了任何有限精度限制。

NaN(不是数字)值表示未定义的计算结果。
一般来说,任何有NaN输入的操作都会产生另一个NaN
唯一的例外是,如果NaN被任何有限或无限数值替换,该操作的其他输入将获得相同的输出;然后,该产出值也用于NaN
(这个原理的一个例子是,NaN提高到零次方会产生1。)


Note : 在"not-a-number"概念的大多数实现中,NaN不被认为等于任何其他数值(包括NaN)。
为了允许在基于树的索引中对numeric进行排序和使用,PostgreSQL将NaN值视为相等,并且大于所有非NaN值。

类型decimalnumeric是等效的。
这两种类型都是SQL标准的一部分。

舍入值时,numeric类型舍入与零相关联,而(在大多数机器上)realdouble precision类型舍入与最接近的偶数相关联。
例如:

SELECT x,round(x::numeric) AS num_round,round(x::double precision) AS dbl_round
FROM generate_series(-3.5, 3.5, 1) as x;

xnum_rounddbl_round
-3.5-4-4
-2.5-3-2
-1.5-2-2
-0.5-1-0
0.510
1.522
2.532
3.544

(8 rows)


8.1.3浮点类型

数据类型realdouble precision是不精确的可变精度数字类型。
在当前支持的所有平台上,这些类型都是IEEE标准754的二进制浮点算术(分别为单精度和双精度)的实现,只要底层处理器、操作系统和编译器支持它。

不精确意味着某些值不能完全转换为内部格式并存储为近似值,因此存储和检索值可能会显示轻微差异。
管理这些错误以及它们如何通过计算传播是数学和计算机科学整个分支的主题,这里将不讨论,除了以下几点:

  • 如果您需要精确的存储和计算(例如货币金额),请改用numeric类型。
  • 如果您想对任何重要的东西使用这些类型进行复杂的计算,特别是如果您依赖于边界情况下的某些行为(无穷大、下限溢位),您应该仔细评估实现。
  • 比较两个浮点值是否相等可能并不总是按预期工作。

在当前支持的所有平台上,real类型的范围约为1E-37到1E+37,精度至少为6位十进制数字。
double precision类型的范围约为1E-307到1E+308,精度至少为15位。
太大或太小的值将导致错误。
如果输入数字的精度太高,可能会进行四舍五入。
数字太接近零,不能表示为与零不同,将导致下限溢位错误。

默认情况下,浮点值以最短精确的十进制表示形式以文本形式输出;产生的十进制值比以相同二进制精度表示的任何其他值更接近真实存储的二进制值。
(然而,产出值目前永远不会正好介于两个可表示值之间,以避免输入例程不正确尊重round-to-nearest-even规则的普遍bug。)此值将对float8值最多使用17位有效十进制数字,对float4值最多使用9位有效十进制数字。


Note : 这种最短精确的输出格式比历史四舍五入格式的生成速度要快得多。


为了与旧版本的PostgreSQL生成的输出兼容,并降低输出精度,可以使用extra_float_digits参数来选择四舍五入的十进制输出。将值设置为0会恢复之前将值四舍五入为6(对于float4)或15(对于float8)有效十进制数字的默认值。设置负值会进一步减少位数;例如-2会将输出分别四舍五入为4或13位。

任何大于0的extra_float_digits值都会选择最短精确格式。


Note: 需要精确值的应用程序历来必须将extra_float_digits设置为3才能获得它们。为了实现版本之间的最大兼容性,他们应该继续这样做。


除了普通数值之外,浮点类型还有几个特殊值:

Infinity
-Infinity
NaN

它们分别表示IEEE 754的特殊值“infinity”、“负无穷大”和“not-a-number”。在SQL命令中将这些值作为常量写入时,必须在它们周围加上引号,例如UPDATE table SET x = '-Infinity'。在输入时,这些字符串以不区分大小写的方式识别。无穷大值也可以拼写为inf-inf


Note : IEEE 754指定NaN不应与任何其他浮点值(包括NaN)进行比较。为了允许在基于树的索引中对浮点值进行排序和使用,PostgreSQL将NaN值视为相等,并且大于所有非NaN值。


PostgreSQL还支持用于指定非精确数字类型的SQL标准符号floatfloat(*p*)。这里,*p指定二进制数字的最小可接受精度。PostgreSQL接受float(1)float(24)作为选择real类型,而float(25)float(53)选择double precision。超出允许范围的p*值会产生错误。未指定精度的float被视为表示double precision


8.1.4 串行类型


Note : 本节介绍一种特定于PostgreSQL的创建自动递增列的方法。另一种方法是使用CREATE TABLE中描述的SQL标准标识列功能。


据类型smallserialserialbigserial不是真正的类型,而仅仅是创建唯一标识符列的符号便利(类似于其他一些数据库支持的AUTO_INCREMENT属性)。在当前实现中,指定:

CREATE TABLE tablename (colname SERIAL
);

相当于指定:

CREATE SEQUENCE tablename_colname_seq AS integer;
CREATE TABLE tablename (colname integer NOT NULL DEFAULT nextval('tablename_colname_seq')
);
ALTER SEQUENCE tablename_colname_seq OWNED BY tablename.colname;

因此,我们创建了一个整数列,并安排从序列生成器分配其默认值。
应用NOT NULL约束以确保无法插入空值。
(在大多数情况下,您还希望附加一个UNIQUEPRIMARY KEY约束以防止意外插入重复值,但这不是自动的。)最后,序列被标记为列的“拥有者”,因此如果列或表被删除,它将被删除。


Note : 因为smallserialserialbigserial是使用序列实现的,所以即使没有删除任何行,列中出现的值序列中也可能存在“漏洞”或间隙。
即使包含该值的行从未成功插入表列,从序列分配的值仍然“用完”。
例如,如果插入事务回滚,这可能会发生。
有关详细信息,请参见第9.17节中的nextval()

要将序列的下一个值插入serial列,请指定应为serial列分配其默认值。
这可以通过从INSERT的列列表中排除该列来完成,也可以通过使用DEFAULT关键字来完成。

类型名称serialserial4是等效的:它们都创建integer列。
类型名称bigserialserial8的工作方式相同,只是它们创建了一个bigint列。
如果您预计在表的生命周期内使用超过2个31个标识符,则应使用bigserial
类型名称smallserialserial2的工作方式相同,只是它们创建了一个smallint列。

删除拥有的列时,为serial列创建的序列会自动删除。
您可以在不删除列的情况下删除序列,但这将强制删除列默认表达式。


8.2. Monetary Types

货币类型存储具有固定分数精度的货币数量;见money表8.3。
分数精度由数据库的lc_monetary设置决定。
表中显示的范围假定有两个小数。
可以接受多种格式的输入,包括整数和浮点文字,以及典型的货币格式,如'$1,000.00'
输出通常采用后一种形式,但取决于语言环境。

Table 8.3. Monetary Types

NameStorage SizeDescriptionRange
money8 bytescurrency amount-92233720368547758.08 to +92233720368547758.07

由于此数据类型的输出对区域设置敏感,因此将money数据加载到具有不同lc_monetary设置的数据库中可能不起作用。
为避免问题,在将转储恢复到新数据库之前,请确保lc_monetary与转储的数据库具有相同或等效的值。

可以将numericintbigint数据类型的值转换为money
realdouble precision数据类型的转换可以先转换为numeric,例如:

SELECT '12.34'::float8::numeric::money;

但是,不建议这样做。由于可能出现舍入错误,浮点数不应用于处理货币。


可以将money值转换为numeric而不会损失精度。
转换为其他类型可能会失去精度,并且还必须分两个阶段完成:

SELECT '52093.89'::money::numeric::float8;

money值除以整数值,并将小数部分截断为零。
要获得四舍五入的结果,请除以浮点值,或将money值在除法前转换为numeric,然后再转换为money
(后者更可取,以避免精度损失的风险。)当一个money值除以另一个money值时,结果是double precision(即纯数字,而不是货币);货币单位在除法中相互抵消。


8.3.字符类型

表8.4.字符类型

名称描述
character varying(n)varchar(n)可变长度与限制
character(n)char(n)固定长度,空白填充
text可变无限长度

表8.4显示了PostgreSQL中可用的通用字符类型。

SQL定义两种主要字符类型:character varying(n)character(n),其中n是正整数。
这两种类型都可以存储长度不超过n个字符(不是字节)的字符串。
尝试将较长的字符串存储到这些类型的列中将会导致错误,除非多余的字符都是空格,在这种情况下,字符串将被截断到最大长度。
(SQL标准要求这种有点奇怪的例外。)如果要存储的字符串短于声明的长度,character类型的值将被填充空格;character varying类型的值将简单地存储较短的字符串。

如果将值显式转换为character varying(n)character(n),则超长值将被截断为n个字符而不会引发错误。
(这也是SQL标准所要求的。)

符号varchar(n)char(n)分别是character varying(n)character(n)的别名。
如果指定,长度必须大于零,并且不能超过10485760。
没有长度说明符的character相当于character(1)
如果使用character varying而没有长度说明符,则该类型接受任何大小的字符串。
后者是PostgreSQL扩展。

此外,PostgreSQL提供了text类型,它存储任意长度的字符串。
虽然text类型不在SQL标准中,但其他几个SQL数据库管理系统也有。

类型character的值在物理上用空格填充到指定的宽度n,并以这种方式存储和显示。
但是,尾随空格在语义上是无关紧要的,并且在比较character类型的两个值时被忽略。
在空格重要的排序规则中,这种行为可能会产生意想不到的结果;例如SELECT 'a '::CHAR(2) collate "C" < E'a\n'::CHAR(2)返回true,即使C语言环境会认为空格大于换行符。
character值转换为其他字符串类型之一时,尾随空格会被删除。
请注意,尾随空格在character varyingtext值中是语义重要的,当使用模式匹配时,即LIKE和正则表达式。

在任何这些数据类型中可以存储的字符由数据库字符集决定,在创建数据库时选择该字符集。
无论具体的字符集如何,代码为零(有时称为NUL)的字符都不能存储。
有关详细信息,请参阅第24.3节。

短字符串(最多126字节)的存储要求是1字节加上实际字符串,其中包括character的情况下的空间填充。
较长的字符串有4个字节的开销,而不是1。
长字符串由系统自动压缩,因此磁盘上的物理要求可能更少。
很长的值也存储在后台表中,这样它们就不会干扰对较短列值的快速访问。
在任何情况下,可以存储的最长可能的字符串大约是1 GB。
(数据类型声明中允许的最大值n小于这个值。
改变这一点没有用,因为对于多字节字符编码,字符和字节的数量可能会完全不同。
如果您希望存储没有特定上限的长字符串,请使用没有长度说明符的textcharacter varying,而不是构成任意长度限制。)


Tips : 这三种类型之间没有性能差异,除了使用空白填充类型时增加存储空间,以及存储到长度受限列时检查长度的一些额外CPU周期。
虽然character(n)在其他一些数据库系统中具有性能优势,但在PostgreSQL中没有这种优势;事实上,character(n)通常是三种类型中最慢的,因为它有额外的存储成本。
在大多数情况下,应该使用textcharacter varying

有关字符串文字语法的信息,请参阅第4.1.2.1节,有关可用运算符和函数的信息,请参阅第9章。

示例8.1 使用字符类型

CREATE TABLE test1 (a character(4));
INSERT INTO test1 VALUES ('ok');
SELECT a, char_length(a) FROM test1; -- (1)
achar_length
ok2

CREATE TABLE test2 (b varchar(5));
INSERT INTO test2 VALUES ('ok');
INSERT INTO test2 VALUES ('good      ');
INSERT INTO test2 VALUES ('too long');
ERROR:  value too long for type character varying(5)
INSERT INTO test2 VALUES ('too long'::varchar(5)); -- explicit truncation
SELECT b, char_length(b) FROM test2;
bchar_length
ok2
good5
too l5

(1) | 该char_length函数将在第9.4节中讨论。


在PostgreSQL中还有另外两种固定长度的字符类型,如表8.5所示。
它们不打算用于一般用途,仅用于内部系统目录。
name类型用于存储标识符。
其长度目前定义为64字节(63个可用字符加上终止符),但应使用C源代码中的常量NAMEDATALEN引用。
长度在编译时设置(因此可针对特殊用途进行调整);默认的最大长度可能会在未来版本中更改。
类型"char"(注意引号)不同于char(1),因为它只使用一个字节的存储空间,因此只能存储一个ASCII字符。
它在系统目录中用作简单的枚举类型。


表8.5.特殊字符类型

名称存储大小说明
"char"1字节单字节内部类型
name64字节内部类型用于对象名称

8.4.二进制数据类型

这种bytea数据类型允许存储二进制字符串;见表8.6。


表8.6.二进制数据类型

名称存储大小描述
bytea1或4字节加上实际的二进制字符串可变长度的二进制字符串

二进制字符串是八位字节(或字节)的序列。
二进制字符串与字符串有两种区别。
首先,二进制字符串特别允许存储值为零的八位字节和其他“不可打印”的八位字节(通常是十进制范围32到126之外的八位字节)。
字符串不允许零八位字节,也不允许根据数据库选择的字符集编码无效的任何其他八位字节值和八位字节值序列。
其次,对二进制字符串的操作处理实际字节,而字符串的处理取决于语言环境设置。
简而言之,二进制字符串适合存储程序员认为是“原始字节”的数据,而字符串适合存储文本。

对于输入和输出,bytea类型支持两种格式:“十六进制”格式和PostgreSQL的历史“转义”格式。
这两种格式总是在输入时被接受。
输出格式取决于配置参数bytea_output;默认为十六进制。
(请注意,十六进制格式是在PostgreSQL9.0中引入的;早期版本和一些工具不理解它。)

该SQL标准定义了不同的二进制字符串类型,称为BLOBBINARY LARGE OBJECT,输入格式与bytea不同,但提供的函数和运算符大多相同。


8.4.1-byte-byte-byte-byte-by-Hex格式-字节bytea

“十六进制”格式将二进制数据编码为每字节2个十六进制数字,最重要的半字节优先。
整个字符串前面是序列\x(以将其与转义格式区分开来)。
在某些情况下,初始反斜杠可能需要通过将其加倍来转义(参见第4.1.2.1节)。
对于输入,十六进制数字可以是大写或小写,数字对之间允许空格(但不能在数字对内,也不能在起始\x序列中)。
十六进制格式与广泛的外部应用程序和协议兼容,并且它的转换速度往往比转义格式快,因此首选使用它。

示例:

SET bytea_output = 'hex';SELECT '\xDEADBEEF'::bytea;
bytea
\xdeadbeef

8.4.2 bytea转义格式

这种“转义”格式是bytea类型的传统PostgreSQL格式。
它采用将二进制字符串表示为ASCII字符序列的方法,同时将那些不能表示为ASCII字符的字节转换为特殊的转义序列。
如果从应用程序的角度来看,将字节表示为字符是有意义的,那么这种表示可能很方便。
但是在实践中,它通常是令人困惑的,因为它模糊了二进制字符串和字符串之间的区别,而且选择的特定转义机制也有点笨拙。
因此,大多数新应用程序可能应该避免这种格式。

以转义格式输入bytea值时,某些值的八位字节必须转义,而所有八位字节值都可以转义。
通常,要转义八位字节,请将其转换为三位八进制值,并在其前面加上反斜杠。
反斜杠本身(八位字节十进制值92)也可以用双反斜杠表示。
表8.7显示了必须转义的字符,并给出了适用的替代转义序列。


表8.7bytea文字转义八位字节

十进制八位字节值描述转义输入表示示例十六进制表示
0零八位字节'\000''\000'::bytea\x00
39单引号'''''\047'''''::bytea\x27
92反斜杠'\\''\134''\\'::bytea\x5c
0到31和127到255"不可打印"八位字节'\ xxx' (八进制值)'\001'::bytea\x01

转义不可打印八位字节的要求因语言环境设置而异。
在某些情况下,您可以不转义它们。

如表8.7所示,单引号必须加倍的原因是,对于SQL命令中的任何字符串文字都是如此。
通用字符串文字解析器使用最外面的单引号,并将任何一对单引号简化为一个数据字符。
bytea输入函数看到的只是一个单引号,它将其视为普通数据字符。
然而,bytea输入函数将反斜杠视为特殊行为,表8.7中显示的其他行为由该函数实现。

在某些情况下,与上面显示的相比,反斜杠必须加倍,因为通用字符串文字解析器也会将反斜杠对减少为一个数据字符;参见第4.1.2.1节。

Bytea八位字节默认以hex格式输出。
如果您将bytea_output更改为escape,“不可打印”八位字节将转换为其等效的三位八进制值,并在其前面加上一个反斜杠。
大多数“可打印”八位字节在客户端字符集中按其标准表示形式输出,例如:

SET bytea_output = 'escape';SELECT 'abc \153\154\155 \052\251\124'::bytea;
bytea
abc klm *\251T
***
十进制值为92的八位字节(反斜杠)在输出中加倍。
详细信息见[表8.8](https://www.postgresql.org/docs/15/datatype-binary.html#DATATYPE-BINARY-RESESC)。**表8.8`bytea`输出转义八位字节**| 十进制八位字节值 | 描述 | 转义输出表示 | 示例 | 输出结果 |
| -- | --- | -- | -- | -- |
| 92 | 反斜杠 | `\\` | `'\134'::bytea` | `\\` |
| 0到31和127到255 | "不可打印"八位字节 | `\ xxx` (八进制值) | `'\001'::bytea` | `\001` |
| 32到126 | "可打印"八位字节 | 客户端字符集表示 | `'\176'::bytea` | `~` |根据您使用的PostgreSQL前端,您可能需要在转义和取消转义`bytea`字符串方面做额外的工作。
例如,如果您的界面自动转换换行符和回车符,您可能还需要转义换行符和回车符。***
## 8.5.日期/时间类型PostgreSQL支持全套SQL日期和时间类型,如[表8.9](https://www.postgresql.org/docs/15/datatype-datetime.html#DATATYPE-DATETIME-TABLE)所示。
[第9.9节](https://www.postgresql.org/docs/15/functions-datetime.html)描述了对这些数据类型的可用操作。
日期根据公历计算,甚至在引入公历之前的年份(有关详细信息,请参见[第B.6节](https://www.postgresql.org/docs/15/datetime-units-history.html))。***
表8.9 日期/时间类型| 名称 | 存储大小 | 描述 | 低值 | 高值 | 分辨率 |
| -- | -- | -- | -- | -- | -- |
| `timestamp [(p)] [ without time zone ]` | 8字节 | 日期和时间(无时区) | 4713 BC | 294276 AD | 1微秒 |
| `timestamp [(p)] with time zone` | 8字节 | 日期和时间,带时区 | 4713 BC | 294276 AD | 1微秒 |
| `date` | 4字节 | 日期(无时间) | 4713 BC | 5874897 AD | 1天 |
| `time [(p)] [ without time zone ]` | 8字节 | 时间(无日期) | 00:00:00 | 24:00:00 | 1微秒 |
| `time [(p)] with time zone` | 12字节 | 时间(无日期),带时区 | 00:00:00+1559 | 24:00:00 | 1微秒 |
| `interval [ fields  ] [(p)]` | 16字节 | 时间间隔 | -178000000年 | 178000000年 | 1微秒 |
***
Note : SQL标准要求只写`timestamp`等价于`timestamp without time zone`,PostgreSQL尊重这种行为。
`timestamptz`被接受为`timestamp with time zone`的缩写;这是一个PostgreSQL扩展。`time`、`timestamp`和`interval`接受可选的精度值 p ,它指定秒数字段中保留的小数位数。
默认情况下,精度没有明确的限制。p 的允许范围是从0到6。该`interval`类型有一个附加选项,即通过写入以下短语之一来限制存储字段集:```sql
YEAR
MONTH
DAY
HOUR
MINUTE
SECOND
YEAR TO MONTH
DAY TO HOUR
DAY TO MINUTE
DAY TO SECOND
HOUR TO MINUTE
HOUR TO SECOND
MINUTE TO SECOND

请注意,如果同时指定了 fields 和 p ,则 fields 必须包含SECOND,因为精度仅适用于秒。

类型time with time zone是由SQL标准定义的,但是该定义具有导致有用性可疑的属性。
在大多数情况下,datetimetimestamp without time zonetimestamp with time zone的组合应该提供任何应用程序所需的完整的日期/时间功能。


8.5.1日期/时间输入

日期和时间输入几乎可以接受任何合理的格式,包括ISO 8601、SQL兼容、传统POSTGRES等。
对于某些格式,日期输入中的日、月、年排序不明确,并且支持指定这些字段的预期排序。
将DateStyle参数设置为MDY以选择月-日-年解释,DMY选择日-月-年解释,或YMD选择年-月-日解释。

PostgreSQL在处理日期/时间输入方面比SQL标准要求的更灵活。
请参阅附录B,了解日期/时间输入的确切解析规则和识别的文本字段,包括月份、星期几和时区。

请记住,任何日期或时间文字输入都需要用单引号括起来,如文本字符串。
有关详细信息,请参阅第4.1.2.7节。
SQL需要以下语法

type[(p)] 'value'

其中 p 是可选的精度规范,给出秒数字段中的小数位数。
可以为timetimestampinterval类型指定精度,范围可以从0到6。
如果在常量规范中未指定精度,则默认为文字值的精度(但不超过6位)。


8.5.1.1日期

表8.10显示了date类型的一些可能输入。


表8.10 日期输入

示例描述
1999-01-08ISO 8601;任何模式下的1月8日(推荐格式)
1999年1月8日在任何datestyle输入模式下明确
1/8/19991月8日在MDY模式下;8月1日在DMY模式下
1/18/19991月18日在MDY模式下;在其他模式下被拒绝
01/02/032003年1月2日在MDY模式下;2003年2月1日在DMY模式下;2001年2月3日在YMD模式下
1999-Jan-081月8日在任何模式下
1999年1月8日1999年1月8日在任何模式下
08-Jan-19991月8日在任何模式下
99-Jan-081月8日在YMD模式下,否则错误
08-Jan-991月8日,YMD
1月8日,除了错误在YMD模式
19990108ISO 8601; 1999年1月8日在任何模式
990108ISO 8601;1999年1月8日在任何模式
1999.008一年和一年中的一天
J2451187朱利安日期
一月8,99 BC一年99 BC

8.5.1.2时报

一天中的时间类型是time [(p)] without time zonetime [(p)] with time zone
单独time相当于time without time zone

这些类型的有效输入包括一天中的一个时间,后跟一个可选的时区。
(见表8.11和表8.12。)如果在输入中为time without time zone指定了时区,它将被静默忽略。
您也可以指定日期,但它将被忽略,除非您使用涉及夏令时规则的时区名称,例如America/New_York
在这种情况下,需要指定日期,以确定是适用标准时间还是夏令时。
适当的时区偏移记录在time with time zone值中,并按存储状态输出;它不会调整到活动时区。


表8.11 时间输入

示例描述
04:05:06.789ISO 8601
04:05:06ISO 8601
04:05ISO 8601
040506ISO 8601
04:05 AM与04:05相同;AM不影响值
04:05 PM与16:05相同;输入小时必须<=12
04:05:06.789-8ISO 8601,时区为UTC偏移
04:05:06-08:00ISO 8601,时区为UTC偏移
04:05-08:00ISO 8601,时区为UTC偏移
040506-08ISO 8601,时区为UTC偏移
040506+0730ISO 8601,小数小时时区为UTC偏移
040506+07:30:00UTC偏移指定为秒(ISO 8601中不允许)
04:05:06 PST
2003-04-12 04:05:06 America/New_York时区由全名指定

表8.12 时区输入

示例描述
PST缩写(用于太平洋标准时间)
America/New_York全时区名称
PST8PDTPOSIX风格的时区规范
-8:00:00UTC偏移用于PST
-8:00UTC偏移用于PST(ISO 8601扩展格式)
-800UTC偏移用于PST(ISO 8601基本格式)
-8UTC偏移用于PST(ISO 8601基本格式)
zuluUTC的军事缩写
zzulu的缩写形式(也在ISO 8601中)

有关如何指定时区的更多信息,请参阅第8.5.3节。


8.5.1.3时间戳

时间戳类型的有效输入包括日期和时间的串联,后跟可选的时区,后跟可选的ADBC
(或者,AD/BC可以出现在时区之前,但这不是首选顺序。)因此:

1999-01-08 04:05:06

和:

1999-01-08 04:05:06 -8:00

是符合ISO 8601标准的逻辑值。
此外,常用格式:

January 8 04:05:06 1999 PST

支持。

该SQL标准区分timestamp without time zonetimestamp with time zone文字,通过存在“+”或“-”符号和时间后的时区偏移。
因此,根据标准,

TIMESTAMP '2004-10-19 10:23:54'

是一个timestamp without time zone,而

TIMESTAMP '2004-10-19 10:23:54+02'

是一个timestamp with time zone
PostgreSQL在确定其类型之前从不检查文字字符串的内容,因此会将上述两者视为timestamp without time zone
要确保文字被视为timestamp with time zone,请给它正确的显式类型:

TIMESTAMP WITH TIME ZONE '2004-10-19 10:23:54+02'

在已确定为timestamp without time zone的文字中,PostgreSQL将静默忽略任何时区指示。
也就是说,结果值来自输入值中的日期/时间字段,并且不会针对时区进行调整。

对于timestamp with time zone,内部存储的值始终为UTC(通用协调时间,传统上称为格林威治时间GMT)。
指定了显式时区的输入值将使用该时区的适当偏移量转换为UTC。
如果输入字符串中没有说明时区,则假定它位于系统的TimeZone参数指示的时区,并使用timezone的偏移量转换为UTC。

当输出timestamp with time zone值的时间戳时,它总是从UTC转换为当前timezone,并在该时区显示为本地时间。
要查看另一个时区的时间,请更改timezone或使用AT TIME ZONE构造(参见第9.9.4节)。

timestamp without time zonetimestamp with time zone之间的转换通常假定timestamp without time zone值应作为timezone本地时间。
可以使用AT TIME ZONE为转换指定不同的时区。


8.5.1.4特殊价值

PostgreSQL为方便起见支持几个特殊的日期/时间输入值,如表8.13所示。
infinity-infinity在系统内部专门表示,将保持不变显示;但其他的只是符号简写,读取时会转换为普通的日期/时间值。
(特别是,now和相关字符串在读取时会立即转换为特定的时间值。)在SQL命令中用作常量时,所有这些值都需要用单引号括起来。


表8.13 特殊日期/时间输入

输入字符串有效类型描述
epochdatetimestamp1970-01-0100:00:00+00(Unix系统时间零)
infinitydatetimestamp晚于所有其他时间戳
-infinitydatetimestamp早于所有其他时间戳
nowdatetimetimestamp当前事务的开始时间
todaydatetimestamp午夜(00:00)今天
tomorrowdatetimestamp午夜(00:00)明天
yesterdaydatetimestamp午夜(00:00)昨天
allballstime00:00:00.00 UTC

还可以使用以下SQL兼容函数来获取相应数据类型的当前时间值:CURRENT_DATECURRENT_TIMECURRENT_TIMESTAMPLOCALTIMELOCALTIMESTAMP
(参见第9.9.5节。)请注意,这些是SQL函数,在数据输入字符串中无法识别。


Caution : 虽然输入字符串nowtodaytomorrowyesterday可以在交互式SQL命令中使用,但当命令被保存以供以后执行时,它们可能会有令人惊讶的行为,例如在准备好的语句、视图和函数定义中。
字符串可以转换为特定的时间值,在它变得陈旧后很长时间内继续使用。
在这种情况下改用SQL函数之一。
例如,CURRENT_DATE + 1'tomorrow'::date更安全。


8.5.2日期/时间输出

日期/时间类型的输出格式可以设置为四种样式之一ISO 8601、SQL(Ingres)、传统POSTGRES(Unix日期格式)或德语。
默认为ISO格式。
(SQL标准要求使用ISO 8601格式。
“SQL”输出格式的名称是历史意外。)表8.14显示了每种输出样式的示例。
datetime类型的输出通常仅根据给定示例的日期或时间部分。
然而,POSTGRES样式以ISO格式输出仅日期值。


表8.14 日期/时间输出样式

样式规范说明示例
ISOISO 8601,SQL标准1997-12-17 07:37:16-08
SQL传统样式12/17/1997 07:37:16.00 PST
Postgres原始样式Wed Dec 17 07:37:16 1997 PST
German区域样式17.12.1997 07:37:16.00 PST

Note : ISO 8601指定使用大写字母T来分隔日期和时间。
PostgreSQL在输入时接受这种格式,但在输出时使用空格而不是T,如上所示。
这是为了易读性,也是为了与RFC 3339以及其他一些数据库系统保持一致。

在SQL和POSTGRES样式中,如果指定了DMY字段顺序,则日期出现在月份之前,否则月份出现在日期之前。
(有关此设置如何影响输入值的解释,请参见第8.5.1节。)表8.15显示了示例。


表8.15日期顺序约定

datestyle设置输入排序示例输出
SQL, DMYday / month / year17/12/1997 15:37:16.00 CET
SQL, MDYmonth / day / year12/17/1997 07:37:16.00 PST
Postgres, DMYday / month / yearWed 17 Dec 07:37:16 1997 PST

在国际标准化组织样式中,时区始终显示为与UTC的有符号数字偏移,格林威治以东的区域使用正号。
如果偏移是小时的整数,则偏移将显示为 hh (仅限小时),如果是分钟的整数,则显示为 hh : mm ,否则显示为 hh : mm : ss 。
(第三种情况不适用于任何现代时区标准,但在使用标准化时区之前的时间戳时可以出现。)在其他日期样式中,如果时区在当前区域中常用,则时区显示为字母缩写。
否则,它显示为ISO 8601基本格式( hh 或 hhmm )的有符号数字偏移量。

用户可以使用SET datestyle命令、postgresql.conf配置文件中的DateStyle参数或服务器或客户端上的PGDATESTYLE环境变量来选择日期/时间样式。

格式化功能to_char(参见第9.8节)也可以作为一种更灵活的方式来格式化日期/时间输出。


8.5.3时区

时区和时区惯例受政治决策的影响,而不仅仅是地球几何学。
世界各地的时区在20世纪变得有些标准化,但仍然容易发生任意变化,特别是在夏令时规则方面。
PostgreSQL使用广泛使用的IANA(Olson)时区数据库来获取有关历史时区规则的信息。
对于未来的某些时候,假设给定时区的最新已知规则将在未来无限期地继续得到遵守。

PostgreSQL努力与典型用法的SQL标准定义兼容。
然而,SQL标准有奇怪的日期和时间类型和功能组合。
两个明显的问题是:

  • 虽然date类型不能有关联的时区,但time类型可以。
    现实世界中的时区没有什么意义,除非与日期和时间相关联,因为偏移量可以在一年中因夏令时边界而异。
  • 默认时区指定为与UTC的恒定数值偏移量。
    因此,在跨DST边界进行日期/时间算术时,不可能适应夏令时。

为了解决这些困难,我们建议在使用时区时使用同时包含日期和时间的日期/时间类型。
我们不建议使用time with time zone类型(尽管PostgreSQL支持遗留应用程序并符合SQL标准)。
PostgreSQL为仅包含日期或时间的任何类型假定您的本地时区。

所有时区感知日期和时间都存储在UTC内部。
在显示给客户端之前,它们会在TimeZone配置参数指定的区域中转换为本地时间。

PostgreSQL允许您以三种不同的形式指定时区:

  • 一个完整的时区名称,例如America/New_York
    识别的时区名称在pg_timezone_names视图中列出(参见第54.32节)。
    PostgreSQL为此使用广泛使用的IANA时区数据,因此相同的时区名称也被其他软件识别。
  • 时区缩写,例如PST
    这种规范仅定义了与UTC的特定偏移量,而全时区名称也可能暗示一组夏令时转换规则。
    可识别的缩写列在pg_timezone_abbrevs视图中(见第54.31节)。
    您不能将配置参数时区或log_timezone设置为时区缩写,但您可以在日期/时间输入值中使用缩写,并使用AT TIME ZONE运算符。
  • 除了时区名称和缩写之外,PostgreSQL将接受POSIX风格的时区规范,如第B.5节所述。
    此选项通常不如使用命名时区,但如果没有合适的IANA时区条目可用,则可能需要此选项。

简而言之,这就是缩写和全名之间的区别:缩写代表与UTC的特定偏移量,而许多全名暗示了当地夏令时规则,因此有两种可能的UTC偏移量。
例如,2014-06-04 12:00 America/New_York代表纽约当地时间中午,该特定日期为东部夏令时(UTC-4)。
因此2014-06-04 12:00 EDT指定同一时间即时。
但是2014-06-04 12:00 EST指定东部标准时间中午(UTC-5),无论该日期名义上是否夏令时有效。

更复杂的是,一些司法管辖区使用相同的时区缩写来表示不同时间的不同UTC偏移;例如,在莫斯科,MSK在某些年份意味着UTC+3,在其他年份意味着UTC+4。
PostgreSQL根据指定日期的任何含义(或最近的含义)来解释这些缩写;但是,与上面的EST示例一样,这不一定与该日期的当地民事时间相同。

在所有情况下,时区名称和缩写都不区分大小写。
(这与8.2之前的PostgreSQL版本不同,后者在某些情况下区分大小写,但在其他情况下不区分大小写。)

时区名称和缩写都没有硬连线到服务器中;它们是从存储在安装目录的.../share/timezone/.../share/timezonesets/下的配置文件中获得的(参见第B.4节)。

可以在文件postgresql.conf中设置TimeZone配置参数,也可以使用第20章中描述的任何其他标准方式。
还有一些特殊的设置方式:

  • SQL命令SET TIME ZONE设置会话的时区。
    这是SET TIMEZONE TO的另一种拼写,语法SQL兼容。
  • 连接时,libpq客户端使用PGTZ环境变量向服务器发送SET TIME ZONE命令。

8.5.4间隔输入

interval值可以使用以下详细语法编写:

[@] quantity unit [quantity unit...] [direction]

其中 quantity 是一个数字(可能有符号); unit 是microsecondmillisecondsecondminutehourdayweekmonthyeardecadecenturymillennium,或这些单位的缩写或复数; direction 可以是ago或空。
at符号(@)是可选噪声。
不同单位的数量用适当的符号计算隐式添加。
ago否定所有字段。
如果IntervalStyle设置为postgres_verbose,则此语法也用于区间输出。

天、小时、分钟和秒的数量可以在没有明确单位标记的情况下指定。
例如,'1 12:59:10'的读数与'1 day 12 hours 59 min 10 sec'相同。
此外,可以用破折号指定年份和月份的组合;例如'200-10'的读数与'200 years 10 months'
(这些较短的形式实际上是SQL标准允许的唯一形式,并且在IntervalStyle设置为sql_standard.)

间隔值也可以写成ISO 8601时间间隔,使用标准部分4.4.3.2的“带指示符的格式”或部分4.4.3.3的“替代格式”。
带指示符的格式如下所示:

P quantity unit [ quantity unit ...] [ T [ quantity unit ...]]

字符串必须以P开头,并且可以包括引入时间单位的T
可用的单位缩写在表8.16中给出。
单位可以省略,并且可以以任何顺序指定,但是小于一天的单位必须出现在T之后。
特别是,M的含义取决于它是在T之前还是之后。


表8.16 ISO 8601间隔单位缩写

缩写含义
Y
M月(在日期部分)
W
D
H小时
M分钟(在时间部分)
S

In the alternative format:

P [ years-months-days ] [ T hours:minutes:seconds ]

the string must begin with P, and a T separates the date and time parts of the interval. The values are given as numbers similar to ISO 8601 dates.

When writing an interval constant with a fields specification, or when assigning a string to an interval column that was defined with a fields specification, the interpretation of unmarked quantities depends on the fields . For example INTERVAL '1' YEAR is read as 1 year, whereas INTERVAL '1' means 1 second. Also, field values “to the right” of the least significant field allowed by the fields specification are silently discarded. For example, writing INTERVAL '1 day 2:03:04' HOUR TO MINUTE results in dropping the seconds field, but not the day field.

根据SQL标准,区间值的所有字段必须具有相同的符号,因此前导负号适用于所有字段;例如,区间文字中的负号'-1 2:03:04'适用于天和小时/分钟/秒部分。
PostgreSQL允许字段具有不同的符号,并且传统上将文本表示中的每个字段视为独立签名,因此在本例中小时/分钟/秒部分被认为是正的。
如果IntervalStyle设置为sql_standard则认为前导符号适用于所有字段(但仅当没有出现额外符号时)。
否则使用传统的PostgreSQL解释。
为了避免歧义,如果任何字段为负,建议在每个字段上附加一个显式符号。

在内部,interval值被存储为三个整数字段:月、天和微秒。
这些字段保持分开,因为一个月中的天数会发生变化,而如果涉及夏令时转换,一天可以有23或25小时。
使用其他单位的间隔输入字符串被规范化为这种格式,然后以标准化的方式重建以进行输出,例如:

SELECT '2 years 15 months 100 weeks 99 hours 123456789 milliseconds'::interval;
interval
3 years 3 mons 700 days 133:17:36.789
***
在这里,被理解为“7天”的周被分开,而较小和较大的时间单位被合并和标准化。输入字段值可以有小数部分,例如`'1.5 weeks'`或`'01:02:03.45'`。
但是,由于`interval`内部只存储整数字段,小数值必须转换为更小的单位。
大于月的单位的小数部分四舍五入为整数个月,例如`'1.5 years'`变为`'1 year 6 mons'`。
周数和天数的小数部分计算为整数天数和微秒数,假设每月30天,每天24小时,例如,`'1.75 months'`变为`1 mon 22 days 12:00:00`。
只有秒将在输出时显示为小数。[表8.17](https://www.postgresql.org/docs/15/datatype-datetime.html#DATATYPE-INTERVAL-INPUT-EXAMPLES)显示了有效`interval`输入的一些示例。***
表8.17 间隔输入| 示例 | 说明 |
| -- | -- |
| `1-2` | SQL标准格式:1年2个月 |
| `3 4:05:06` | SQL标准格式:3天4小时5分6秒 |
| `1 year 2 months 3 days 4 hours 5 minutes 6 seconds` | 传统Postgres格式:1年2个月3天4小时5分6秒 |
| `P1Y2M3DT4H5M6S` | ISO 8601“带指示符的格式”:与上述含义相同 |
| `P0001-02-03T04:05:06` | ISO 8601“替代格式”:与上述含义相同 |
***
### 8.5.5间隔输出如前所述,PostgreSQL将`interval`值存储为月、日和微秒。
对于输出,月字段通过除以12转换为年和月。
天字段按原样显示。
微秒字段转换为小时、分钟、秒和小数秒。
因此,月、分钟和秒将永远不会分别显示为超过范围0-11、0-59和0-59,而显示的年、天和小时字段可能非常大。
(如果需要将大的天或小时值转置到下一个更高的字段中,可以使用[`justify_days`](https://www.postgresql.org/docs/15/functions-datetime.html#FUNCTION-JUSTIFY-DAYS)和[`justify_hours`](https://www.postgresql.org/docs/15/functions-datetime.html#FUNCTION-JUSTIFY-HOURS)函数。)可以使用命令`SET intervalstyle`将间隔类型的输出格式设置为`sql_standard`、`postgres`、`postgres_verbose`或`iso_8601`四种样式之一。
默认为`postgres`格式。
[表8.18](https://www.postgresql.org/docs/15/datatype-datetime.html#INTERVAL-STYLE-OUTPUT-TABLE)显示了每种输出样式的示例。如果间隔值满足标准的限制(仅年月或仅日间时间,不混合正负分量),则`sql_standard`样式生成符合SQL标准的间隔文字字符串规范的输出。
否则,输出看起来像一个标准的年月文字字符串,后跟一个日间时间文字字符串,并添加显式符号以消除混合符号间隔的歧义。当[DateStyle](https://www.postgresql.org/docs/15/runtime-config-client.html#GUC-DATESTYLE)参数设置为`ISO`时,`postgres`样式的输出与8.4之前的PostgreSQL版本的输出相匹配。当`DateStyle`参数设置为非`ISO`输出时,`postgres_verbose`样式的输出与8.4之前的PostgreSQL版本的输出匹配。`iso_8601`样式的输出与ISO 8601标准第4.4.3.2节中描述的“带指示符的格式”相匹配。***
表8.18 间隔输出样式示例| 样式规范 | 年-月间隔 | 日-时间隔 | 混合间隔 |
| --- | --- | -- | --- |
| `sql_standard` | 1-2 | 3 4:05:06 | -1-2+3-4:05:06 |
| `postgres` | 1年2个月 | 3天04:05:06 | -1年-2个月+3天-04:05:06 |
| `postgres_verbose` | @1年2个月 | @3天4小时5分钟6秒 | @1年2个月-3天4小时5分钟6秒前 |
| `iso_8601` | P1Y2M | P3DT4H5M6S | P-1Y-2M3D T-4H-5M-6S |
***
## 8.6 布尔类型PostgreSQL提供了标准的SQL类型`boolean`;见[表8.19](https://www.postgresql.org/docs/15/datatype-boolean.html#DATATYPE-BOOLEAN-TABLE)。
`boolean`类型可以有几种状态:“真”、“假”和第三种状态“未知”,由SQL空值表示。***
表8.19 布尔数据类型| 名称 | 存储大小 | 描述 |
| --- | -- | -- |
| `boolean` | 1字节 | 状态为真或假 |布尔常量可以在SQL查询中用SQL关键字`TRUE`、`FALSE`和`NULL`。类型`boolean`的数据类型输入函数接受以下字符串表示形式的"true"状态:| `true` |
| -- |
| `yes` |
| `on` |
| `1` |***
以及“false”状态的这些表示:| `false` |
| -- |
| `no` |
| `off` |
| `0` |这些字符串的唯一前缀也被接受,例如`t`或`n`。
前导或尾随空格被忽略,大小写无关紧要。类型`boolean`的数据类型输出函数总是发出`t`或`f`,如[示例8.2](https://www.postgresql.org/docs/15/datatype-boolean.html#DATATYPE-BOOLEAN-EXAMPLE)所示。**示例8.2.使用`boolean`类型**```sql
CREATE TABLE test1 (a boolean, b text);
INSERT INTO test1 VALUES (TRUE, 'sic est');
INSERT INTO test1 VALUES (FALSE, 'non est');
SELECT * FROM test1;

ab
tsic est
fnon est

SELECT * FROM test1 WHERE a;

***a | b
--|---t | sic est

关键字TRUEFALSE是SQL查询中编写布尔常量的首选(SQL兼容)方法。
但是您也可以通过遵循第4.1.2.7节中描述的通用字符串文字常量语法来使用字符串表示,例如'yes'::boolean

请注意,解析器会自动理解TRUEFALSEboolean类型,但NULL并非如此,因为它可以有任何类型。
因此在某些上下文中,您可能必须将NULL显式转换为boolean,例如NULL::boolean
相反,在解析器可以推断文字必须是boolean类型的上下文中,可以从字符串文字布尔值中省略转换。


8.7.枚举类型

枚举(enum)类型是包含一组静态有序值的数据类型。
它们等效于许多编程语言支持的enum类型。
枚举类型的一个示例可能是星期几,也可能是一段数据的一组状态值。


8.7.1列举类型的声明

枚举类型是使用CREATE TYPE命令创建的,例如:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');

创建后,枚举类型可以在表和函数定义中使用,就像任何其他类型一样:

CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
CREATE TABLE person (name text,current_mood mood
);
INSERT INTO person VALUES ('Moe', 'happy');
SELECT * FROM person WHERE current_mood = 'happy';

namecurrent_mood
Moehappy

(1 row)


8.7.2点餐

枚举类型中值的顺序是创建该类型时列出值的顺序。
枚举支持所有标准比较运算符和相关的聚合函数。
例如:

INSERT INTO person VALUES ('Larry', 'sad');
INSERT INTO person VALUES ('Curly', 'ok');
SELECT * FROM person WHERE current_mood > 'sad';

namecurrent_mood
Moehappy
Curlyok

(2 rows)


SELECT * FROM person WHERE current_mood > 'sad' ORDER BY current_mood;

namecurrent_mood
Curlyok
Moehappy

(2 rows)


SELECT name
FROM person
WHERE current_mood = (SELECT MIN(current_mood) FROM person);

name
Larry

(1 row)


8.7.3类型安全

每个枚举数据类型都是单独的,不能与其他枚举类型进行比较。
请参阅此示例:

CREATE TYPE happiness AS ENUM ('happy', 'very happy', 'ecstatic');
CREATE TABLE holidays (num_weeks integer,happiness happiness
);
INSERT INTO holidays(num_weeks,happiness) VALUES (4, 'happy');
INSERT INTO holidays(num_weeks,happiness) VALUES (6, 'very happy');
INSERT INTO holidays(num_weeks,happiness) VALUES (8, 'ecstatic');
INSERT INTO holidays(num_weeks,happiness) VALUES (2, 'sad');
ERROR:  invalid input value for enum happiness: "sad"
SELECT person.name, holidays.num_weeks FROM person, holidaysWHERE person.current_mood = holidays.happiness;
ERROR:  operator does not exist: mood = happiness

如果您真的需要做这样的事情,您可以编写自定义运算符或向查询添加显式强制转换:

SELECT person.name, holidays.num_weeks FROM person, holidaysWHERE person.current_mood::text = holidays.happiness::text;

namenum_weeks
Moe4

(1 row)


8.7.4实施细节

枚举标签区分大小写,因此'happy''HAPPY'不同。
标签中的空白也很重要。

尽管枚举类型主要用于静态值集,但支持向现有枚举类型添加新值和重命名值(参见ALTER TYPE)。
现有值不能从枚举类型中删除,也不能更改这些值的排序顺序,除非删除并重新创建枚举类型。

枚举值在磁盘上占用四个字节。
枚举值的文本标签的长度受到编译到PostgreSQL中的NAMEDATALEN设置的限制;在标准版本中,这意味着最多63个字节。

从内部枚举值到文本标签的转换保存在系统曲库pg_enum中。
直接查询此曲库会很有用。


8.8.几何类型

几何数据类型表示二维空间对象。
表8.20显示了PostgreSQL中可用的几何类型。


表8.20 几何类型

名称存储大小描述表示
point16字节平面上的点(x, y)
line24字节无限线{A,B,C}
lseg32字节有限线段((x1,y1),(x2,y2))
box32字节矩形框((x1,y1),(x2,y2))
path16+16n字节封闭路径(类似于多边形)((x1,y1),…)
path16+16n字节开放路径[(x1,y1),…]
polygon40+16n字节多边形(类似于封闭路径)((x1,y1),…)
circle24字节<(x,y),r>(中心点和半径)

在所有这些类型中,单个坐标都存储为double precisionfloat8)数字。

一组丰富的函数和运算符可用于执行各种几何操作,如缩放、平移、旋转和确定交叉点。
它们将在第9.11节中解释。


8.8.1点

点是几何类型的基本二维构建块。
point类型的值使用以下任一语法指定:

( x , y )x , y

其中*xy*是各自的坐标,作为浮点数。

点使用第一种语法输出。


8.8.2. Lines

线由线性方程*Ax+By+C=0表示,其中AB*不都是零。line类型的值以以下形式输入和输出:

{ A, B, C }

或者,可以使用以下任何形式进行输入:

[(x1 , y1 ) , ( x2 , y2)]
( ( x1 , y1 ) , ( x2 , y2 ) )( x1 , y1 ) , ( x2 , y2 )x1 , y1  ,  x2 , y2

其中(x1,y1)(x2,y2) 是直线上的两个不同点。


8.8.3. Line Segments

线段由作为线段端点的点对表示。使用以下任何语法指定lseg类型的值:

[(x1 , y1 ) , ( x2 , y2)]
( ( x1 , y1 ) , ( x2 , y2 ) )( x1 , y1 ) , ( x2 , y2 )x1 , y1  ,  x2 , y2

其中(x1,y1)(x2,y2) 是线段的端点。


8.8.4. Boxes

框由框的相对角的点对表示。使用以下任何语法指定box类型的值:

( ( x1 , y1 ) , ( x2 , y2 ) )( x1 , y1 ) , ( x2 , y2 )x1 , y1  ,  x2 , y2

其中 ( x1 , y1 )( x2 , y2 ) 是盒子的任意两个相对角。

使用第二种语法输出框。

任何两个相反的角都可以在输入时提供,但值将根据需要重新排序以按该顺序存储右上角和左下角。


8.8.5. Paths

路径由连接点列表表示。
路径可以是开放的,其中列表中的第一个和最后一个点被认为没有连接,或者关闭,其中第一个和最后一个点被认为是连接的。

使用以下任一语法指定类型path的值:

[(x1 , y1 ) , ... , ( xn , yn)]
( ( x1 , y1 ) , ... , ( xn , yn ) )( x1 , y1 ) , ... , ( xn , yn )( x1 , y1   , ...,  xn , yn )x1 , y1   , ...,  xn , yn

其中点是构成路径的线段的端点。
方括号([])表示开放路径,括号(())表示封闭路径。
当省略最外面的括号时,如在第三到第五语法中,假定封闭路径。

根据情况使用第一种或第二种语法输出路径。


8.8.6多边形

多边形由点列表(多边形的顶点)表示。
多边形与封闭路径非常相似;本质的语义区别是多边形被认为包括其中的区域,而路径不是。

多边形和路径之间的一个重要实现区别是多边形的存储表示包括其最小的边界框。
这加快了某些搜索操作,尽管计算边界框在构建新多边形时增加了开销。

使用以下任何语法指定polygon类型的值:

( ( x1 , y1 ) , ... , ( xn , yn ) )( x1 , y1 ) , ... , ( xn , yn )( x1 , y1   , ...,  xn , yn )x1 , y1   , ...,  xn , yn

其中,点是构成多边形边界的线段的端点。

多边形使用第一种语法输出。


8.8.7圆圈

圆由中心点和半径表示。
使用以下任何语法指定circle类型的值:

< ( x , y ) , r >
( ( x , y ) , r )( x , y ) , rx , y   , r

其中( x , y )是中心点, r 是圆的半径。

圆圈使用第一种语法输出。


8.9.网络地址类型

PostgreSQL提供数据类型来存储IPv4、IPv6和MAC地址,如表8.21所示。
最好使用这些类型而不是纯文本类型来存储网络地址,因为这些类型提供输入错误检查和专门的运算符和函数(参见第9.12节)。


表8.21 网络地址类型

名称存储大小描述
cidr7或19字节IPv4和IPv6网络
inet7或19字节IPv4和IPv6主机和网络
macaddr6字节MAC地址
macaddr88字节MAC地址(EUI-64格式)

在对inetcidr数据类型进行排序时,IPv4地址将始终在IPv6地址之前进行排序,包括封装或映射到IPv6地址的IPv4地址,例如 ::10.2.3.4::ffff:10.4.3.2


8.9.1inet

在一个字段中,inet类型保存IPv4或IPv6主机地址,以及可选的子网。
子网由主机地址中存在的网络地址位数(“网络掩码”)表示。
如果网络掩码为32,地址为IPv4,则该值不表示子网,仅表示单个主机。
在IPv6中,地址长度为128位,因此128位指定唯一的主机地址。
请注意,如果您只想接受网络,则应使用cidr类型而不是inet

这种类型的输入格式是 address/y ,其中 address 是IPv4或IPv6地址, y 是网络掩码中的位数。
如果省略 /y 部分,则IPv4的网络掩码为32,IPv6的网络掩码为128,因此该值仅代表单个主机。
在显示中,如果网络掩码指定单个主机,则 /y 部分被抑制。


8.9.2.cidr

cidrcidr包含IPv4或IPv6网络规范。
输入和输出格式遵循无类Internet域路由约定。
指定网络的格式是 address/y ,其中 address 是表示为IPv4或IPv6地址的网络最低地址, y 是网络掩码中的位数。
如果省略 y ,则使用旧的类网络编号系统的假设计算,除非它至少足够大,足以包含输入中写入的所有八位字节。
指定位设置在指定网络掩码右侧的网络地址是错误的。

表8.22显示了一些例子。

表8.22cidr类型输入示例

cidr输入cidr输出abbrev(cidr)
192.168.100.128/25192.168.100.128/25192.168.100.128/25
192.168/24192.168.0.0/24192.168.0/24
192.168/25192.168.0.0/25168.1/24
192.168.1168.0/24192.192.168.0.0/25
192.168128.1/16192.192.168.1.0/24
128.1128.0/16192.168.0.0/24
128128.1.2128.1.0.0/16
128.0.0.0/16128.1.2128.1.2.0/24/24
10.1.210.1.2.0/2410.1.2/24
10.110.1.0.0/1610.1/16
1010.0.0.0/810/8
10.1.2.3/3210.1.2.3/3210.1.2.3/32
2001:4f8:3:ba::/642001:4f8:3:ba::/642001:4f8:3:ba/64
2001:4f8:3:ba:​2e0:81ff:fe22:d1f1/1282001:4f8:3:ba:​2e0:81ff:fe22:d1f1/1282001:4f8:3:ba:​2e0:81ff:fe22:d1f1/128
::ffff:1.2.3.0/120::ffff:1.2.3.0/120::ffff:1.2.3/120
::ffff:1.2.3.0/128::ffff:1.2.3.0/128::ffff:1.2.3.0/128

8.9.3inetcidr

inetcidr数据类型之间的本质区别是inet接受网络掩码右边非零位的值,而cidr不接受。
例如,192.168.0.1/24inet有效,但对cidr


Tips : 如果您不喜欢inetcidr值的输出格式,请尝试函数hosttextabbrev


8.9.4 macaddr

使用macaddr类型存储MAC地址,例如已知的以太网卡硬件地址(尽管MAC地址也用于其他目的)。

  • '08:00:2b:01:02:03'
  • '08-00-2b-01-02-03'
  • '08002b:010203'
  • '08002b-010203'
  • '0800.2b01.0203'
  • '0800-2b01-0203'
  • '08002b010203'

这些例子都指定了相同的地址。
数字af接受大写和小写。
输出总是显示的第一种形式。

IEEE标准802-2001将所示的第二种形式(带连字符)指定为MAC地址的规范形式,并将第一种形式(带冒号)指定为位反转、MSB优先表示法,因此08-00-2b-01-02-03=10:00:D4:80:40:C0。
这种约定如今被广泛忽略,它只适用于过时的网络协议(如令牌环)。
PostgreSQL没有提供位反转;所有接受的格式都使用规范的LSB顺序。

其余五种输入格式不属于任何标准。


8.9.5.macaddr8

Macaddr8macaddr8类型以EUI-64格式存储MAC地址,例如已知的以太网卡硬件地址(尽管MAC地址也用于其他目的)。
这种类型可以接受6字节和8字节长度的MAC地址,并以8字节长度的格式存储它们。
以6字节格式给出的MAC地址将以8字节长度的格式存储,第4和第5字节分别设置为FF和FE。
请注意,IPv6使用修改后的EUI-64格式,其中第7位应在从EUI-48转换后设置为1。
提供了功能macaddr8_set7bit来进行此更改。
一般来说,任何由十六进制数字对组成的输入(在字节边界上),可选地由':''-''.'之一分隔。
十六进制位数必须为16(8字节)或12(6字节)。
前导和尾随空格将被忽略。
以下是可接受的输入格式示例:

  • '08:00:2b:01:02:03:04:05'
  • '08-00-2b-01-02-03-04-05'
  • '08002b:0102030405'
  • '08002b-0102030405'
  • '0800.2b01.0203.0405'
  • '0800-2b01-0203-0405'
  • '08002b01:02030405'
  • '08002b0102030405'

这些例子都指定了相同的地址。
数字af接受大写和小写。
输出总是显示的第一种形式。

上面显示的最后六种输入格式不是任何标准的一部分。

要将EUI-48格式的传统48位MAC地址转换为修改后的EUI-64格式以作为IPv6地址的主机部分,请使用如下所示的macaddr8_set7bit

SELECT macaddr8_set7bit('08:00:2b:01:02:03');
macaddr8_set7bit
0a:00:2b:ff:fe:01:02:03

(1 row)


8.10 位字符串类型

位串是1和0的字符串,它们可用于存储或可视化位掩码。
有两种SQL类型:bit(n)bit varying(n),其中n是正整数。

bit类型数据必须与长度n完全匹配;尝试存储较短或较长的位串是错误的。
bit varying数据的长度可变,最大长度为n;较长的字符串将被拒绝。
写入没有长度的bit相当于bit(1),而没有长度规范的bit varying意味着无限长度。


Note : 如果将位串值显式转换为bit(n),它将在右侧被截断或零填充,使其正好为n位,而不会引发错误。
类似地,如果将位串值显式转换为bit varying(n),如果它超过n位,它将在右侧被截断。

有关位串常量语法的信息,请参阅第4.1.2.5节。
位逻辑运算符和字符串操作函数可用;见第9.6节。

示例8.3 使用位字符串类型

CREATE TABLE test (a BIT(3), b BIT VARYING(5));
INSERT INTO test VALUES (B'101', B'00');
INSERT INTO test VALUES (B'10', B'101');ERROR:  bit string length 2 does not match type bit(3)INSERT INTO test VALUES (B'10'::bit(3), B'101');
SELECT * FROM test;

ab
10100
100101

位串值每组8位需要1个字节,加上5或8个字节的开销,具体取决于字符串的长度(但长值可能会被压缩或移出行,如字符串第8.3节所述)。


8.10 位字符串类型

位串是1和0的字符串,它们可用于存储或可视化位掩码。
有两种SQL类型:bit(n)bit varying(n),其中n是正整数。

bit类型数据必须与长度n完全匹配;尝试存储较短或较长的位串是错误的。
bit varying数据的长度可变,最大长度为n;较长的字符串将被拒绝。
写入没有长度的bit相当于bit(1),而没有长度规范的bit varying意味着无限长度。


Note : 如果将位串值显式转换为bit(n),它将在右侧被截断或零填充,使其正好为n位,而不会引发错误。
类似地,如果将位串值显式转换为bit varying(n),如果它超过n位,它将在右侧被截断。

有关位串常量语法的信息,请参阅第4.1.2.5节。
位逻辑运算符和字符串操作函数可用;见第9.6节。

示例8.3 使用位字符串类型

CREATE TABLE test (a BIT(3), b BIT VARYING(5));
INSERT INTO test VALUES (B'101', B'00');
INSERT INTO test VALUES (B'10', B'101');ERROR:  bit string length 2 does not match type bit(3)INSERT INTO test VALUES (B'10'::bit(3), B'101');
SELECT * FROM test;

ab
10100
100101

位串值每组8位需要1个字节,加上5或8个字节的开销,具体取决于字符串的长度(但长值可能会被压缩或移出行,如字符串第8.3节所述)。


8.11 文本搜索类型

PostgreSQL提供了两种旨在支持全文搜索的数据类型,这是通过搜索自然语言文档的集合来定位最匹配查询的活动。
tsvector类型表示针对文本搜索优化的形式的文档;tsquery类型类似地表示文本查询。
第12章详细解释了这种功能,第9.13节总结了相关功能和运算符。


8.11.1.tsvector

一个tsvector值是不同词位的排序列表,这些词被归一化以合并同一个词的不同变体(详见第12章)。
排序和duplicate-elimination在输入过程中自动完成,如本例所示:

SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;

tsvector
‘a’ ‘and’ ‘ate’ ‘cat’ ‘fat’ ‘mat’ ‘on’ ‘rat’ ‘sat’

要表示包含空格或标点符号的词位,请用引号将它们括起来:

SELECT $$the lexeme '    ' contains spaces$$::tsvector;
tsvector
’ ’ ‘contains’ ‘lexeme’ ‘spaces’ ‘the’
***
(我们在此示例和下一个示例中使用美元引号字符串文字,以避免必须在文字中使用双引号的混淆。)嵌入的引号和反斜杠必须加倍:```sql
SELECT $$the lexeme 'Joe''s' contains a quote$$::tsvector;

tsvector
‘Joe’‘s’ ‘a’ ‘contains’ ‘lexeme’ ‘quote’ ‘the’

可选地,整数位置可以附加到词位:

SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12'::tsvector;

tsvector
‘a’:1,6,10 ‘and’:8 ‘ate’:9 ‘cat’:3 ‘fat’:2,11 ‘mat’:7 ‘on’:5 ‘rat’:12 ‘sat’:4
***
位置通常表示源词在文档中的位置。
位置信息可用于邻近排名。
位置值的范围可以从1到16383;较大的数字静默设置为16383。
同一词素的重复位置将被丢弃。具有位置的词素可以进一步标记为*权重*,可以是`A`、`B`、`C`或`D`。
`D`是默认值,因此不会显示在输出中:```sql
SELECT 'a:1A fat:2B,4C cat:5D'::tsvector;

tsvector
‘a’:1A ‘cat’:5 ‘fat’:2B,4C
***
权重通常用于反映文档结构,例如通过将标题词与正文词标记不同。
文本搜索排名功能可以为不同的权重标记分配不同的优先级。重要的是要理解`tsvector`类型本身不执行任何单词规范化;它假定给定的单词针对应用程序进行了适当的规范化。
例如,```sql
SELECT 'The Fat Rats'::tsvector;

tsvector
‘Fat’ ‘Rats’ ‘The’

对于大多数English-text-searching应用程序,上面的单词被认为是非规范化的,但是tsvector不在乎。
原始文档文本通常应该通过to_tsvector来适当地规范化单词以进行搜索:

SELECT to_tsvector('english', 'The Fat Rats');
to_tsvector
‘fat’:2 ‘rat’:3
***
再次,请参阅[第12章](https://www.postgresql.org/docs/15/textsearch.html)了解更多详细信息。***
### 8.11.2.`tsquery`一个`tsquery`值存储要搜索的词位,并且可以使用布尔运算符`&`(AND)、`|`(OR)和`!`(Not)以及短语搜索运算符`<->`(FOLLOWED BY)将它们组合起来。
还有一个FOLLOWED BY运算符的变体`<n>`,其中n是一个整数常量,指定要搜索的两个词位之间的距离。
`<->`等价于`<1>`。括号可用于强制对这些运算符进行分组。
如果没有括号,`!`(Not)绑定最紧,`<->`(FOLLOWED BY)紧随其后,然后是`&`(AND),`|`(OR)绑定最紧。这里有一些例子:```sql
SELECT 'fat & rat'::tsquery;
tsquery
‘fat’ & ‘rat’

SELECT 'fat & (rat | cat)'::tsquery;

tsquery
‘fat’ & ( ‘rat’

SELECT ‘fat & rat & ! cat’::tsquery;


***
| tsquery |
| -- |
| 'fat' & 'rat' & !'cat' |

可选地,tsquery中的词位可以用一个或多个权重字母标记,这限制它们仅匹配具有这些权重之一的tsvector词位:

SELECT 'fat:ab & cat'::tsquery;
tsquery
‘fat’:AB & ‘cat’
***
此外,`tsquery`中的词位可以用` 标记以指定前缀匹配:```sql
SELECT 'super:*'::tsquery;
tsquery
‘super’😗
***
此查询将匹配`tsvector`中以“Super”开头的任何单词。词素的引用规则与前面描述的`tsvector`中的词素相同;并且,与`tsvector`一样,任何需要的单词规范化都必须在转换为`tsquery`类型之前完成。
`to_tsquery`函数便于执行这种规范化:```sql
SELECT to_tsquery('Fat:ab & Cats');
to_tsquery
‘fat’:AB & ‘cat’
***
请注意,`to_tsquery`将以与其他单词相同的方式处理前缀,这意味着此比较返回true:```sql
SELECT to_tsvector( 'postgraduate' ) @@ to_tsquery( 'postgres:*' );

?column?
t
***
因为`postgres`的词根是`postgr`:```sql
SELECT to_tsvector( 'postgraduate' ), to_tsquery( 'postgres:*' );

to_tsvectorto_tsquery
‘postgradu’:1‘postgr’😗
***
这将与`postgraduate`的词干形式相匹配。***
## 8.11 文本搜索类型PostgreSQL提供了两种旨在支持全文搜索的数据类型,这是通过搜索自然语言*文档*的集合来定位最匹配*查询*的活动。
`tsvector`类型表示针对文本搜索优化的形式的文档;`tsquery`类型类似地表示文本查询。
[第12章](https://www.postgresql.org/docs/15/textsearch.html)详细解释了这种功能,[第9.13节](https://www.postgresql.org/docs/15/functions-textsearch.html)总结了相关功能和运算符。***
### 8.11.1.`tsvector`一个`tsvector`值是不同*词位*的排序列表,这些词被*归一化*以合并同一个词的不同变体(详见[第12章](https://www.postgresql.org/docs/15/textsearch.html))。
排序和duplicate-elimination在输入过程中自动完成,如本例所示:```sql
SELECT 'a fat cat sat on a mat and ate a fat rat'::tsvector;

tsvector
‘a’ ‘and’ ‘ate’ ‘cat’ ‘fat’ ‘mat’ ‘on’ ‘rat’ ‘sat’
***
要表示包含空格或标点符号的词位,请用引号将它们括起来:```sql
SELECT $$the lexeme '    ' contains spaces$$::tsvector;
tsvector
’ ’ ‘contains’ ‘lexeme’ ‘spaces’ ‘the’
***
(我们在此示例和下一个示例中使用美元引号字符串文字,以避免必须在文字中使用双引号的混淆。)嵌入的引号和反斜杠必须加倍:```sql
SELECT $$the lexeme 'Joe''s' contains a quote$$::tsvector;

tsvector
‘Joe’‘s’ ‘a’ ‘contains’ ‘lexeme’ ‘quote’ ‘the’
***
可选地,整数位置可以附加到词位:```sql
SELECT 'a:1 fat:2 cat:3 sat:4 on:5 a:6 mat:7 and:8 ate:9 a:10 fat:11 rat:12'::tsvector;

tsvector
‘a’:1,6,10 ‘and’:8 ‘ate’:9 ‘cat’:3 ‘fat’:2,11 ‘mat’:7 ‘on’:5 ‘rat’:12 ‘sat’:4
***
位置通常表示源词在文档中的位置。
位置信息可用于邻近排名。
位置值的范围可以从1到16383;较大的数字静默设置为16383。
同一词素的重复位置将被丢弃。具有位置的词素可以进一步标记为*权重*,可以是`A`、`B`、`C`或`D`。
`D`是默认值,因此不会显示在输出中:```sql
SELECT 'a:1A fat:2B,4C cat:5D'::tsvector;

tsvector
‘a’:1A ‘cat’:5 ‘fat’:2B,4C
***
权重通常用于反映文档结构,例如通过将标题词与正文词标记不同。
文本搜索排名功能可以为不同的权重标记分配不同的优先级。重要的是要理解`tsvector`类型本身不执行任何单词规范化;它假定给定的单词针对应用程序进行了适当的规范化。
例如,```sql
SELECT 'The Fat Rats'::tsvector;

tsvector
‘Fat’ ‘Rats’ ‘The’
***
对于大多数English-text-searching应用程序,上面的单词被认为是非规范化的,但是`tsvector`不在乎。
原始文档文本通常应该通过`to_tsvector`来适当地规范化单词以进行搜索:```sql
SELECT to_tsvector('english', 'The Fat Rats');to_tsvector |
| -- |
| 'fat':2 'rat':3

再次,请参阅第12章了解更多详细信息。


8.11.2.tsquery

一个tsquery值存储要搜索的词位,并且可以使用布尔运算符&(AND)、|(OR)和!(Not)以及短语搜索运算符<->(FOLLOWED BY)将它们组合起来。
还有一个FOLLOWED BY运算符的变体<n>,其中n是一个整数常量,指定要搜索的两个词位之间的距离。
<->等价于<1>

括号可用于强制对这些运算符进行分组。
如果没有括号,!(Not)绑定最紧,<->(FOLLOWED BY)紧随其后,然后是&(AND),|(OR)绑定最紧。

这里有一些例子:

SELECT 'fat & rat'::tsquery;
tsquery
‘fat’ & ‘rat’

SELECT 'fat & (rat | cat)'::tsquery;

tsquery
‘fat’ & ( ‘rat’

SELECT 'fat & rat & ! cat'::tsquery;

tsquery
‘fat’ & ‘rat’ & !‘cat’
***
可选地,`tsquery`中的词位可以用一个或多个权重字母标记,这限制它们仅匹配具有这些权重之一的`tsvector`词位:```sql
SELECT 'fat:ab & cat'::tsquery;
tsquery
‘fat’:AB & ‘cat’
***
此外,`tsquery`中的词位可以用` 标记以指定前缀匹配:```sql
SELECT 'super:*'::tsquery;
tsquery
‘super’😗
***
此查询将匹配`tsvector`中以“Super”开头的任何单词。词素的引用规则与前面描述的`tsvector`中的词素相同;并且,与`tsvector`一样,任何需要的单词规范化都必须在转换为`tsquery`类型之前完成。
`to_tsquery`函数便于执行这种规范化:```sql
SELECT to_tsquery('Fat:ab & Cats');

to_tsquery
‘fat’:AB & ‘cat’
***
请注意,`to_tsquery`将以与其他单词相同的方式处理前缀,这意味着此比较返回true:```sql
SELECT to_tsvector( 'postgraduate' ) @@ to_tsquery( 'postgres:*' );

?column?
t
***
因为`postgres`的词根是`postgr`:```sql
SELECT to_tsvector( 'postgraduate' ), to_tsquery( 'postgres:*' );
to_tsvectorto_tsquery
‘postgradu’:1‘postgr’😗

这将与postgraduate的词干形式相匹配。


8.12.UUID类型

数据类型uuid存储由RFC 4122、ISO/IEC 9834-8:2005和相关标准定义的通用唯一标识符(UUID)。
(一些系统将此数据类型称为全局唯一标识符,或GUID,而不是。)此标识符是一个128位的数量,由一个算法生成,该算法被选择为使相同的标识符不太可能由已知宇宙中的任何其他人使用相同的算法生成。
因此,对于分布式系统,这些标识符提供了比序列生成器更好的唯一性保证,序列生成器仅在单个数据库中是唯一的。

UUID由一组小写的十六进制数字组成,以连字符分隔成若干组,特别是一组8位数字后跟三组4位数字后跟一组12位数字,总共32位数字代表128位。
这种标准形式的UUID的一个例子是:

a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11

PostgreSQL还接受以下替代输入形式:使用大写数字、大括号包围的标准格式、省略部分或全部连字符、在任何一组四位数字之后添加连字符。
示例是:

A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11
{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}
a0eebc999c0b4ef8bb6d6bb9bd380a11
a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11
{a0eebc99-9c0b4ef8-bb6d6bb9-bd380a11}

输出始终采用标准形式。

有关如何在PostgreSQL中生成UUID,请参见第9.14节。


8.13 XML类型

可使用xml数据类型来存储XML数据。
与将XML数据存储在text字段中相比,它的优势在于它检查输入值的格式是否正确,并且有支持函数可以对其执行类型安全操作;参见第9.15节。
使用这种数据类型要求安装是使用configure --with-libxml构建的。

按照XML标准的定义,xml类型可以存储格式良好的"文档",也可以存储"内容"片段,这些片段是通过引用XQuery和XPath数据模型的更宽松的"文档节点"来定义的。
粗略地说,这意味着内容片段可以有多个顶级元素或字符节点。
表达式 xmlvalue IS DOCUMENT可用于评估特定的xml值是完整文档还是仅是内容片段。

有关xml数据类型的限制和兼容性说明,请参见第D.3节。


8.13.1创建XML值

要从字符数据生成xml类型的值,请使用函数xmlparse

XMLPARSE ( { DOCUMENT | CONTENT } value)

例子:

XMLPARSE (DOCUMENT '<?xml version="1.0"?><book><title>Manual</title><chapter>...</chapter></book>')
XMLPARSE (CONTENT 'abc<foo>bar</foo><bar>foo</bar>')

虽然这是根据SQL标准将字符串转换为XML值的唯一方法,但PostgreSQL特定的语法:

xml '<foo>bar</foo>'
'<foo>bar</foo>'::xml

也可以使用。

即使输入值指定了DTD,xml类型也不会根据文档类型声明(DTD)验证输入值,目前也没有针对其他XML模式语言(如XML模式)进行验证的内置支持。

xml生成字符串值的逆操作使用函数xmlserialize

XMLSERIALIZE ( { DOCUMENT | CONTENT } value AS type )

type 可以是charactercharacter varyingtext(或其中之一的别名)。
同样,根据SQL标准,这是在类型xml和字符类型之间转换的唯一方法,但PostgreSQL也允许您简单地转换值。

当字符串值转换为或转换为xml类型而不分别通过XMLPARSEXMLSERIALIZE时,DOCUMENTCONTENT的选择由“XML选项” 会话配置参数决定,该参数可以使用标准命令设置:

SET XML OPTION { DOCUMENT | CONTENT };

或者更像PostgreSQL的语法

SET xmloption TO { DOCUMENT | CONTENT };

默认值为CONTENT,因此允许所有形式的XML数据。


8.13.2编码处理

在处理客户端、服务器和通过它们传递的XML数据中的多个字符编码时,必须小心。
当使用文本模式将查询传递给服务器并将查询结果传递给客户端时(这是正常模式),PostgreSQL将在客户端和服务器之间传递的所有字符数据转换为相应端的字符编码,反之亦然;见第24.3节。
这包括XML值的字符串表示,如上面的示例。
这通常意味着XML数据中包含的编码声明可能会变得无效,因为字符数据在客户端和服务器之间传输时被转换为其他编码,因为嵌入的编码声明没有改变。
为了应对这种行为,忽略输入到xml类型的字符串中包含的编码声明,并且假定内容是当前服务器编码。
因此,为了正确处理,必须以当前客户端编码从客户端发送XML数据的字符串。
客户端有责任在将文档发送到服务器之前将文档转换为当前客户端编码,或者适当调整客户端编码。
在输出时,xml类型的值将没有编码声明,客户端应该假定所有数据都采用当前客户端编码。

当使用二进制模式将查询参数传递给服务器并将查询结果返回给客户端时,不进行编码转换,因此情况有所不同。
在这种情况下,将观察到XML数据中的编码声明,如果不存在,则假定数据为UTF-8(根据XML标准的要求;注意PostgreSQL不支持UTF-16)。
在输出时,数据将具有指定客户端编码的编码声明,除非客户端编码为UTF-8,在这种情况下将省略它。

不用说,如果XML数据编码、客户端编码和服务器编码相同,使用PostgreSQL处理XML数据将不太容易出错,并且效率更高。
由于XML数据在内部以UTF-8处理,如果服务器编码也是UTF-8,计算将是最有效的。


Caution : 当服务器编码不是UTF-8时,一些与XML相关的函数可能根本不适用于非ASCII数据。
众所周知,这是xmltable()xpath()的一个问题。


8.13.3访问XML值

不同寻常的是,xml数据类型不提供任何比较运算符。
这是因为没有定义明确且普遍有用的XML数据比较算法。
这样做的一个后果是,您无法通过将xml列与搜索值进行比较来检索行。
因此,XML值通常应附有单独的键字段,例如ID。
比较XML值的另一种解决方案是先将它们转换为字符串,但请注意,字符串比较与有用的XML比较方法几乎没有关系。

由于xml数据类型没有比较运算符,因此不可能直接在这种类型的列上创建索引。
如果需要快速搜索XML数据,可能的解决方法包括将表达式转换为字符串类型并索引该字符串类型,或者索引XPath表达式。
当然,实际查询必须调整为通过索引表达式进行搜索。

PostgreSQL中的文本搜索功能还可用于加速XML数据的全文档搜索。
但是,PostgreSQL发行版中尚不提供必要的预处理支持。


8.14 JSON类型

JSON数据类型用于存储RFC 7159中指定的JSON(JavaScript Object Notation)数据。
此类数据也可以存储为text,但JSON数据类型具有强制每个存储值根据JSON规则有效的优点。
对于这些数据类型中存储的数据,还有各种特定于JSON的函数和运算符;参见第9.16节。

PostgreSQL提供了两种存储JSON数据的类型:jsonjsonb
为了实现这些数据类型的高效查询机制,PostgreSQL还提供了第8.14.7节中描述的jsonpath数据类型。

jsonjsonb数据类型接受几乎相同的值集作为输入。
主要的实际区别在于效率。
json数据类型存储输入文本的精确副本,处理函数必须在每次执行时重新解析;而jsonb数据以分解的二进制格式存储,由于增加了转换开销,输入速度略慢,但处理速度明显更快,因为不需要重新解析。
jsonb还支持索引,这可能是一个显着的优势。

因为json类型存储输入文本的精确副本,它将保留标记之间semantically-insignificant空白,以及JSON对象中键的顺序。
此外,如果值中的JSON对象多次包含相同的键,则保留所有键/值对。
(处理函数将最后一个值视为可操作的值。)相比之下,jsonb不保留空白,不保留对象键的顺序,也不保留重复的对象键。
如果输入中指定了重复的键,则只保留最后一个值。

一般来说,大多数应用程序应该倾向于将JSON数据存储为jsonb,除非有非常特殊的需求,例如关于对象键排序的遗留假设。

RFC 7159指定JSON字符串应该用UTF8编码。
因此,除非数据库编码是UTF8,否则JSON类型不可能严格符合JSON规范。
尝试直接包含无法在数据库编码中表示的字符将失败;相反,可以在数据库编码中表示但不能在UTF8中表示的字符将被允许。

RFC7159允许JSON字符串包含由\u XXXX 表示的Unicode转义序列。
json类型的输入函数中,无论数据库编码如何,都允许Unicode转义,并且只检查语法正确性(即四个十六进制数字跟随\u)。
但是,jsonb的输入函数更严格:它不允许对无法在数据库编码中表示的字符进行Unicode转义。
jsonb类型也拒绝\u0000(因为这无法在PostgreSQL的text类型中表示),并且它坚持使用Unicode代理对来指定Unicode基本多语言平面之外的字符是正确的。
有效的Unicode转义将转换为等效的单个字符进行存储;这包括将代理对折叠成单个字符。


Note : 在第9.16节中描述的许多JSON处理函数会将Unicode转义转换为常规字符,因此即使它们的输入类型是json而不是jsonb,也会抛出相同类型的错误。
json输入函数不进行这些检查的事实可能被视为历史工件,尽管它确实允许在不支持表示字符的数据库编码中简单存储(无需处理)JSON Unicode转义。

当将文本JSON输入转换为jsonb时,RFC7159描述的基本类型被有效地映射到本机PostgreSQL类型,如表8.23所示。
因此,对于什么构成有效的jsonb数据有一些小的附加约束,这些约束不适用于json类型,也不适用于抽象中的JSON,对应于底层数据类型可以表示的限制。
值得注意的是,jsonb将拒绝超出PostgreSQL numeric数据类型范围的数字,而json不会。
RFC7159允许这样implementation-defined限制。
然而,在实践中,这些问题更有可能发生在其他实现中,因为通常将JSON的number基元类型表示为IEEE 754双精度浮点(RFC7159明确预期并允许)。
当使用JSON作为与此类系统的交换格式时,应考虑与PostgreSQL最初存储的数据相比失去数字精度的危险。

相反,如表中所示,对JSON原语类型的输入格式有一些不适用于相应PostgreSQL类型的小限制。


表8.23 JSON原始类型和对应的PostgreSQL类型

JSON原始类型PostgreSQL类型注释
stringtext\u0000是不允许的,因为Unicode转义表示数据库中不可用的字符编码
numbernumericNaNinfinity值是不允许的
booleanboolean只接受小写truefalse拼写
null(无)SQLNULL是一个不同的概念

8.14.1JSON输入输出语法

JSON数据类型的输入/输出语法在RFC 7159中指定。

以下是所有有效的json(或jsonb)表达式:

-- Simple scalar/primitive value
-- Primitive values can be numbers, quoted strings, true, false, or null
SELECT '5'::json;
-- Array of zero or more elements (elements need not be of same type)
SELECT '[1, 2, "foo", null]'::json;
-- Object containing pairs of keys and values
-- Note that object keys must always be quoted strings
SELECT '{"bar": "baz", "balance": 7.77, "active": false}'::json;
-- Arrays and objects can be nested arbitrarily
SELECT '{"foo": [true, "bar"], "tags": {"a": 1, "b": null}}'::json;

如前所述,当输入JSON值然后在没有任何额外处理的情况下打印时,json会输出与输入相同的文本,而jsonb不会保留空白等semantically-insignificant细节。
例如,请注意此处的差异:

SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::json;
json
{“bar”: “baz”, “balance”: 7.77, “active”:false}

(1 row)


SELECT '{"bar": "baz", "balance": 7.77, "active":false}'::jsonb;
jsonb
{“bar”: “baz”, “active”: false, “balance”: 7.77}

(1 row)


值得注意的semantically-insignificant细节是,在jsonb中,数字将根据底层numeric类型的行为打印。
实际上,这意味着用E符号输入的数字将在没有它的情况下打印,例如:

SELECT '{"reading": 1.230e-5}'::json, '{"reading": 1.230e-5}'::jsonb;
jsonjsonb
{“reading”: 1.230e-5}{“reading”: 0.00001230}

(1 row)


但是,jsonb将保留尾随的小数零,如本例所示,即使这些小数零在语义上对于相等性检查等目的无关紧要。

有关可用于构造和处理JSON值的内置函数和运算符列表,请参见第9.16节。


8.14.2设计JSON文档

将数据表示为JSON比传统的关系数据模型灵活得多,后者在需求不稳定的环境中非常引人注目。
这两种方法很有可能在同一个应用程序中共存并相互补充。
然而,即使对于需要最大灵活性的应用程序,仍然建议JSON文档具有某种固定的结构。
该结构通常是未强制执行的(尽管以声明方式强制执行一些业务规则是可能的),但是拥有可预测的结构可以更容易地编写查询,有效地总结表中的一组“文档”(数据)。

JSON数据与任何其他数据类型在存储在表中时都受到相同的并发控制考虑。尽管存储大型文档是可行的,但请记住,任何更新都会在整行上获得行级锁。考虑将JSON文档限制在可管理的大小,以减少更新事务之间的锁争用。理想情况下,JSON文档应该每个都代表一个原子数据,业务规则规定不能合理地将其进一步细分为可以独立修改的较小数据。


8.14.3. jsonb Containment and Existence

测试包含jsonb的一项重要功能。json类型没有并行的工具集。包含测试一个jsonb文档中是否包含另一个文档。这些示例返回true,除非另有说明:

-- Simple scalar/primitive values contain only the identical value:
SELECT '"foo"'::jsonb @> '"foo"'::jsonb;
-- The array on the right side is contained within the one on the left:
SELECT '[1, 2, 3]'::jsonb @> '[1, 3]'::jsonb;
-- Order of array elements is not significant, so this is also true:
SELECT '[1, 2, 3]'::jsonb @> '[3, 1]'::jsonb;
-- Duplicate array elements don't matter either:
SELECT '[1, 2, 3]'::jsonb @> '[1, 2, 2]'::jsonb;
-- The object with a single pair on the right side is contained
-- within the object on the left side:
SELECT '{"product": "PostgreSQL", "version": 9.4, "jsonb": true}'::jsonb @> '{"version": 9.4}'::jsonb;
-- The array on the right side is not considered contained within the
-- array on the left, even though a similar array is nested within it:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[1, 3]'::jsonb;  -- yields false
-- But with a layer of nesting, it is contained:
SELECT '[1, 2, [1, 3]]'::jsonb @> '[[1, 3]]'::jsonb;
-- Similarly, containment is not reported here:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"bar": "baz"}'::jsonb;  -- yields false
-- A top-level key and an empty object is contained:
SELECT '{"foo": {"bar": "baz"}}'::jsonb @> '{"foo": {}}'::jsonb;

一般原则是,包含的对象必须在结构和数据内容上与包含的对象匹配,可能是在从包含的对象中丢弃一些不匹配的数组元素或对象键/值对之后。
但是请记住,在进行包含匹配时,数组元素的顺序并不重要,重复的数组元素只被有效地考虑一次。

作为结构必须匹配的一般原则的一个特殊例外,数组可能包含一个原始值:

-- This array contains the primitive string value:
SELECT '["foo", "bar"]'::jsonb @> '"bar"'::jsonb;
-- This exception is not reciprocal -- non-containment is reported here:
SELECT '"bar"'::jsonb @> '["bar"]'::jsonb;  -- yields false

jsonb还有一个存在运算符,它是包含主题的变体:它测试字符串(以text值的形式给出)是否作为对象键或数组元素出现在jsonb值的顶层。
这些示例返回true,除非另有说明:

-- String exists as array element:
SELECT '["foo", "bar", "baz"]'::jsonb ? 'bar';
-- String exists as object key:
SELECT '{"foo": "bar"}'::jsonb ? 'foo';
-- Object values are not considered:
SELECT '{"foo": "bar"}'::jsonb ? 'bar';  -- yields false
-- As with containment, existence must match at the top level:
SELECT '{"foo": {"bar": "baz"}}'::jsonb ? 'bar'; -- yields false
-- A string is considered to exist if it matches a primitive JSON string:
SELECT '"foo"'::jsonb ? 'foo';

当涉及许多键或元素时,JSON对象比数组更适合测试包含或存在,因为与数组不同,它们在内部针对搜索进行了优化,不需要线性搜索。


Tips : 因为JSON包含是嵌套的,所以适当的查询可以跳过子对象的显式选择。
例如,假设我们有一个doc列,其中包含顶层的对象,大多数对象包含包含子对象数组的tags字段。
此查询查找其中包含"term":"paris""term":"food"的子对象出现的条目,同时忽略tags数组之外的任何此类键:

SELECT doc->'site_name' FROM websitesWHERE doc @> '{"tags":[{"term":"paris"}, {"term":"food"}]}';

一个人可以完成同样的事情,比如说,

SELECT doc->'site_name' FROM websitesWHERE doc->'tags' @> '[{"term":"paris"}, {"term":"food"}]';

但这种方法不太灵活,效率也往往较低。

另一方面,JSON存在运算符不是嵌套的:它只会在JSON值的顶层查找指定的键或数组元素。

各种包含和存在运算符,以及所有其他JSON运算符和函数都记录在第9.16节中。


8.14.4jsonb索引

GIN索引可用于有效搜索大量jsonb文档(数据)中出现的键或键/值对。
提供了两个GIN“运算符类”,提供了不同的性能和灵活性权衡。

用于jsonb的默认GIN运算符类支持使用存在键运算符??|?&、包含运算符@>jsonpath匹配运算符@?@@的查询。
(有关这些运算符实现的语义学的详细信息,请参见表9.46。)使用此运算符类创建索引的示例是:

CREATE INDEX idxgin ON api USING GIN (jdoc);

非默认GIN运算符类jsonb_path_ops不支持存在键的运算符,但支持@>@?@@
使用此运算符类创建索引的示例是:

CREATE INDEX idxginp ON api USING GIN (jdoc jsonb_path_ops);

考虑一个表的示例,该表存储从第三方Web服务检索到的JSON文档,并具有文档化的模式定义。
一个典型的文档是:

{"guid": "9c36adc1-7fb5-4d5b-83b4-90356a46061a","name": "Angela Barton","is_active": true,"company": "Magnafone","address": "178 Howard Place, Gulf, Washington, 702","registered": "2009-11-07T08:53:22 +08:00","latitude": 19.793713,"longitude": 86.513373,"tags": ["enim","aliquip","qui"]
}

我们将这些文档存储在一个名为api的表中,在一个名为jdocjsonb列中。
如果在此列上创建了GIN索引,则如下查询可以使用该索引:

-- Find documents in which the key "company" has value "Magnafone"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"company": "Magnafone"}';

但是,索引不能用于如下查询,因为尽管运算符?是可索引的,但它不能直接应用于索引列jdoc

-- Find documents in which the key "tags" contains key or array element "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc -> 'tags' ? 'qui';

尽管如此,通过适当使用表达式索引,上述查询可以使用索引。
如果查询"tags"键中的特定项目很常见,那么定义这样的索引可能是值得的:

CREATE INDEX idxgintags ON api USING GIN ((jdoc -> 'tags'));

现在,WHERE子句jdoc -> 'tags' ? 'qui'将被识别为可索引运算符?对索引表达式jdoc -> 'tags'的应用。
(有关表达式索引的更多信息,请参见第11.7节。)

另一种查询方法是利用遏制,例如:

-- Find documents in which the key "tags" contains array element "qui"
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @> '{"tags": ["qui"]}';

jdoc列上的简单GIN索引可以支持这个查询。
但是请注意,这样的索引将存储jdoc列中每个键和值的副本,而前面示例的表达式索引只存储在tags键下的数据。
虽然简单索引方法要灵活得多(因为它支持关于任何键的查询),但目标表达式索引可能比简单索引更小、搜索速度更快。

GIN索引还支持执行jsonpath匹配的@?@@运算符

SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @? '$.tags[*] ? (@ == "qui")';
SELECT jdoc->'guid', jdoc->'name' FROM api WHERE jdoc @@ '$.tags[*] == "qui"';

对于这些运算符,GIN索引从jsonpath模式中提取 accessors_chain = constant 形式的子句,并根据这些子句中提到的键和值进行索引搜索。
访问器链可能包括. key[*][ index ]访问器。
jsonb_ops运算符类也支持. 和.* 访问器,但jsonb_path_ops运算符类不支持。

尽管jsonb_path_ops运算符类仅支持使用@>@?@@运算符的查询,但与默认运算符类相比,它具有显著的性能优势jsonb_ops
对于相同的数据,jsonb_path_ops索引通常比jsonb_ops索引小得多,并且搜索的特异性更好,特别是当查询包含数据中经常出现的键时。
因此,搜索操作通常比默认运算符类执行得更好。

jsonb_opsjsonb_path_opsGIN索引之间的技术区别在于前者为数据中的每个键和值创建独立的索引项,而后者仅为数据中的每个值创建索引项。
[7]基本上,每个jsonb_path_ops索引项是值和指向它的键的散列;例如索引{"foo": {"bar": "baz"}},将创建一个包含foobarbaz这三个元素的索引项到散列值中。
因此,查找此结构的包含查询将导致极其特定的索引搜索;但是根本无法找出foo是否显示为键。
另一方面,jsonb_ops索引将创建分别表示foobarbaz的三个索引项;然后要执行包含查询,它将查找包含所有这三个项的行。
虽然GIN索引可以相当有效地执行这样的AND搜索,但它仍然比等效的jsonb_path_ops搜索更不具体,速度也更慢,尤其是当有大量行包含三个索引项中的任何一个时。

这种jsonb_path_ops方法的一个缺点是它不会为不包含任何值的JSON结构生成索引条目,例如{"a": {}}
如果请求搜索包含这种结构的文档,它将需要全索引扫描,这非常慢。
jsonb_path_ops因此不适合经常执行此类搜索的应用程序。

jsonb还支持btreehash索引。
这些索引通常只有在检查完整JSON文档的相等性很重要时才有用。
jsonb数据的btree排序很少引起人们的兴趣,但为了完整性,它是:

Object > Array > Boolean > Number > String > NullObject with n pairs > object with n - 1 pairsArray with n elements > array with n - 1 elements

具有相等数量对的对象按以下顺序进行比较:

key-1, value-1, key-2 ...

请注意,对象键按其存储顺序进行比较;特别是,由于较短的键存储在较长的键之前,这可能会导致不直观的结果,例如:

{ "aa": 1, "c": 1} > {"b": 1, "d": 1}

同样,具有相等数量元素的数组按以下顺序进行比较:

element-1, element-2 ...

使用与底层PostgreSQL数据类型相同的比较规则比较原始JSON值。
使用默认数据库排序规则比较字符串。


8.14.5jsonb订阅

jsonb数据类型支持数组样式的下标表达式来提取和修改元素。
嵌套值可以通过链接下标表达式来指示,遵循与jsonb_set函数中的path参数相同的规则。
如果jsonb值是数组,则数字下标从零开始,负整数从数组的最后一个元素开始倒数。
不支持切片表达式。
下标表达式的结果始终是jsonb数据类型。

UPDATE语句可以在SET子句中使用下标来修改jsonb值。
只要所有受影响的值存在,下标路径必须是可遍历的。
例如,路径val['a']['b']['c']可以一直遍历到c,如果每个valval['a']val['a']['b']是一个对象。
如果任何val['a']val['a']['b']未定义,它将被创建为一个空对象并根据需要填充。
但是,如果任何val本身或中间值之一被定义为非对象,例如字符串、数字或jsonb null,则遍历无法继续,因此会引发错误并中止事务。

下标语法示例:

-- Extract object value by key
SELECT ('{"a": 1}'::jsonb)['a'];
-- Extract nested object value by key path
SELECT ('{"a": {"b": {"c": 1}}}'::jsonb)['a']['b']['c'];
-- Extract array element by index
SELECT ('[1, "2", null]'::jsonb)[1];
-- Update object value by key. Note the quotes around '1': the assigned
-- value must be of the jsonb type as well
UPDATE table_name SET jsonb_field['key'] = '1';
-- This will raise an error if any record's jsonb_field['a']['b'] is something
-- other than an object. For example, the value {"a": 1} has a numeric value
-- of the key 'a'.
UPDATE table_name SET jsonb_field['a']['b']['c'] = '1';
-- Filter records using a WHERE clause with subscripting. Since the result of
-- subscripting is jsonb, the value we compare it against must also be jsonb.
-- The double quotes make "value" also a valid jsonb string.
SELECT * FROM table_name WHERE jsonb_field['key'] = '"value"';

jsonb通过下标赋值处理与jsonb_set不同的一些边缘情况。
当源jsonb值为NULL时,通过下标赋值将继续进行,就好像它是下标键暗示的类型(对象或数组)的空JSON值一样:

-- Where jsonb_field was NULL, it is now {"a": 1}
UPDATE table_name SET jsonb_field['a'] = '1';
-- Where jsonb_field was NULL, it is now [1]
UPDATE table_name SET jsonb_field[0] = '1';

如果为包含太少元素的数组指定了索引,则将附加NULL元素,直到索引可达并且可以设置值。

-- Where jsonb_field was [], it is now [null, null, 2];
-- where jsonb_field was [0], it is now [0, null, 2]
UPDATE table_name SET jsonb_field[2] = '2';

一个jsonb值将接受对不存在的下标路径的赋值,只要要遍历的最后一个现有元素是一个对象或数组,正如相应下标所暗示的那样(路径中最后一个下标指示的元素没有被遍历,可以是任何东西)。
嵌套数组和对象结构将被创建,在前一种情况下是null填充的,由下标路径指定,直到可以放置赋值。

-- Where jsonb_field was {}, it is now {"a": [{"b": 1}]}
UPDATE table_name SET jsonb_field['a'][0]['b'] = '1';
-- Where jsonb_field was [], it is now [null, {"a": 1}]
UPDATE table_name SET jsonb_field[1]['a'] = '1';

8.14.6转换

其他扩展可用于为不同的过程语言实现jsonb类型的转换。

PL/Perl的扩展称为jsonb_plperljsonb_plperlu
如果使用它们,jsonb值将根据需要映射到Perl数组、散列和标量。

PL/Python的扩展称为jsonb_plpython3u
如果使用它,jsonb值会根据需要映射到Python字典、列表和标量。

在这些扩展中,jsonb_plperl被认为是“受信任”的,也就是说,它可以由对当前数据库具有CREATE权限的非超级用户安装。
其余的需要超级用户权限才能安装。


8.14.7jsonpath类型

jsonpathjsonpath实现了对PostgreSQL中SQL/JSON路径语言的支持,以有效地查询JSON数据。
它提供已解析的SQL/JSON路径表达式的二进制表示,该表达式指定路径引擎从JSON数据中检索的项目,以便使用SQL/JSON查询函数进行进一步处理。

SQL/JSON路径谓词和运算符的语义学一般遵循SQL,同时,为了提供处理JSON数据的自然方式,SQL/JSON路径语法使用了一些JavaScript约定:

  • 点(.)用于成员访问。
  • 方括号([])用于数组访问。
  • SQL/JSON数组是0相关的,与从1开始的常规SQL数组不同。

SQL/JSON路径表达式通常在SQL查询中写成SQL字符串文字,因此它必须用单引号括起来,并且值内所需的任何单引号都必须加倍(参见第4.1.2.1节)。
某些形式的路径表达式需要在其中包含字符串文字。
这些嵌入的字符串文字遵循JavaScript/ECMAScript约定:它们必须用双引号括起来,并且可以在其中使用反斜杠转义来表示otherwise-hard-to-type个字符。
特别是,在嵌入的字符串文字中写入双引号的方法是\",而要写入反斜杠本身,则必须写入\\
其他特殊的反斜杠序列包括在JavaScript字符串中识别的序列:\b\f\n\r\t\v用于各种ASCII控制字符,\x NN 用于仅用两个十六进制数字编写的字符代码,\u NNNN 用于由其4个十六进制数字代码点标识的Unicode字符,以及\u{ N... }用于用1到6个十六进制数字编写的Unicode字符代码点。

路径表达式由一系列路径元素组成,这些元素可以是以下任何一种:

  • JSON原始类型的路径文字:Unicode文本、数字、true、false或null。
  • 路径变量列于表8.24。
  • 访问操作符列在表8.25中。
  • 第9.16.2.2节中列出的jsonpath运算符和方法。
  • 括号,可用于提供过滤器表达式或定义路径评估的顺序。

有关将jsonpath表达式与SQL/JSON查询函数一起使用的详细信息,请参见第9.16.2节。


表8.24jsonpath变量

变量描述
$一个变量,表示正在查询的JSON值(上下文项)。
$varname一个命名变量。
它的值可以由几个JSON处理函数的参数 vars 设置;详见表9.48。
@一个变量,表示过滤器表达式中路径评估的结果。

表8.25jsonpath访问

访问操作符说明
. key
."$ varname "
返回具有指定键的对象成员的成员访问器。
如果键名匹配某个以$开头的命名变量或不符合标识符的JavaScript规则,则必须用双引号括起来以使其成为字符串文字。
.*通配符成员访问器,返回位于当前对象顶层的所有成员的值。
.**递归通配符成员访问器,处理当前对象的JSON层次结构的所有级别,并返回所有成员值,而不管它们的嵌套级别如何。
这是SQL/JSON标准的PostgreSQL扩展。
.**{ level }
.**{ start_level to end_level }
类似.** ,但只选择JSON层次结构的指定级别。
嵌套级别指定为整数。
级别零对应于当前对象。
要访问最低嵌套级别,可以使用last关键字。
这是SQL/JSON标准的PostgreSQL扩展。
[subscript, ...]数组元素访问器。
subscript 可以以两种形式给出: index start_index to end_index
第一种形式通过索引返回单个数组元素。
第二种形式通过索引范围返回数组切片,包括与提供的 start_index 和 end_index 相对应的元素。
指定的 index 可以是整数,也可以是返回单个数值的表达式,它会自动转换为整数。
索引零对应于第一个数组元素。
您也可以使用last关键字来表示最后一个数组元素,这对于处理未知长度的数组很有用。
[*]返回所有数组元素的通配符数组元素访问器。

[7]为此,术语“值”包括数组元素,尽管JSON术语有时认为数组元素不同于对象中的值。


8.15 数组

PostgreSQL允许将表的列定义为可变长度的多维数组。
可以创建任何内置或用户定义的基类型、枚举类型、复合类型、范围类型或域的数组。


8.15.1数组类型声明

为了说明数组类型的使用,我们创建了这个表:

CREATE TABLE sal_emp (name            text,pay_by_quarter  integer[],schedule        text[][]
);

如图所示,通过将方括号([])附加到数组元素的数据类型名称来命名数组数据类型。
上述命令将创建一个名为sal_emp的表,其中包含一个类型为textname)的列,一个类型为integerpay_by_quarter)的一维数组,表示员工按季度的工资,以及一个textschedule)的二维数组,表示员工的每周日程安排。

用于CREATE TABLE的语法允许指定数组的确切大小,例如:

CREATE TABLE tictactoe (squares   integer[3][3]
);

但是,当前实现忽略了任何提供的数组大小限制,即行为与未指定长度的数组相同。

当前实现也不强制执行声明的维数。
特定元素类型的数组都被认为是同一类型的,而不管大小或维数如何。
因此,在CREATE TABLE中声明数组大小或维数只是简单的留档;它不会影响运行时行为。

使用关键字ARRAY符合SQL标准的替代语法可用于一维数组。
pay_by_quarter可以定义为:

    pay_by_quarter  integer ARRAY[4],

或者,如果不指定数组大小:

    pay_by_quarter  integer ARRAY,

但是,与以前一样,PostgreSQL在任何情况下都不会强制执行大小限制。


8.15.2数组值输入

要将数组值写入文字常量,请将元素值用花括号括起来,并用逗号分隔。
(如果您了解C,这与初始化结构的C语法没有什么不同。)您可以在任何元素值周围加上双引号,如果它包含逗号或花括号,则必须这样做。
(更多详细信息如下所示。)因此,数组常量的一般格式如下:

'{ val1 delim val2 delim ... }'

其中 delim 是类型的分隔符,记录在pg_type项中。
在PostgreSQL发行版中提供的标准数据类型中,除了使用分号(;)的类型box之外,所有数据类型都使用逗号(,)。
每个 val 要么是数组元素类型的常量,要么是子数组。
数组常量的一个例子是:

'{{1,2,3},{4,5,6},{7,8,9}}'

这个常量是一个由三个整数子数组组成的二维3 x 3数组。

要将数组常量的元素设置为NULL,请将元素值写入NULL
NULL的任何大写或小写变体都可以。)如果您想要一个实际的字符串值“NULL”,您必须在它周围加上双引号。

(这些类型的数组常量实际上只是第4.1.2.7节中讨论的泛型类型常量的一个特例。
常量最初被视为字符串并传递给数组输入转换例程。
可能需要显式类型规范。)

现在我们可以展示一些INSERT

INSERT INTO sal_empVALUES ('Bill','{10000, 10000, 10000, 10000}','{{"meeting", "lunch"}, {"training", "presentation"}}');INSERT INTO sal_empVALUES ('Carol','{20000, 25000, 25000, 25000}','{{"breakfast", "consulting"}, {"meeting", "lunch"}}');

前两次插入的结果如下所示:

SELECT * FROM sal_emp;

namepay_by_quarterschedule
Bill{10000,10000,10000,10000}{{meeting,lunch},{training,presentation}}
Carol{20000,25000,25000,25000}{{breakfast,consulting},{meeting,lunch}}

(2 rows)


多维数组必须具有每个维度的匹配范围。
不匹配会导致错误,例如:

INSERT INTO sal_empVALUES ('Bill','{10000, 10000, 10000, 10000}','{{"meeting", "lunch"}, {"meeting"}}');
ERROR:  multidimensional arrays must have array expressions with matching dimensions

还可以使用ARRAY构造函数语法:

INSERT INTO sal_empVALUES ('Bill',ARRAY[10000, 10000, 10000, 10000],ARRAY[['meeting', 'lunch'], ['training', 'presentation']]);INSERT INTO sal_empVALUES ('Carol',ARRAY[20000, 25000, 25000, 25000],ARRAY[['breakfast', 'consulting'], ['meeting', 'lunch']]);

请注意,数组元素是普通SQL常量或表达式;例如,字符串文字是单引号,而不是数组文字中的双引号。
第4.2.12节将更详细地讨论ARRAY构造函数语法。


8.15.3 访问数组

现在,我们可以对表运行一些查询。
首先,我们展示如何访问数组的单个元素。
此查询检索第二季度薪酬发生变化的员工的姓名:

SELECT name FROM sal_emp WHERE pay_by_quarter[1] <> pay_by_quarter[2];
name
Carol

(1 row)


数组下标号写在方括号内,默认情况下PostgreSQL对数组使用基于一个的编号约定,即n元素的数组以array[1]开始,以array[n]结束。

此查询检索所有员工的第三季度工资:

SELECT pay_by_quarter[3] FROM sal_emp;
pay_by_quarter
10000
25000

(2 rows)


我们还可以访问数组或子数组的任意矩形切片。
数组切片通过为一个或多个数组维度写入 lower-bound : upper-bound 来表示。
例如,此查询检索Bill在一周的前两天的日程安排中的第一项:

SELECT schedule[1:2][1:1] FROM sal_emp WHERE name = 'Bill';
schedule
{{meeting},{training}}

(1 row)


如果任何维度被写成切片,即包含冒号,则所有维度都被视为切片。
任何只有一个数字(没有冒号)的维度都被视为从1到指定的数字。
例如,[2]被视为[1:2],如本例所示:

SELECT schedule[1:2][2] FROM sal_emp WHERE name = 'Bill';
schedule
{{meeting,lunch},{training,presentation}}

(1 row)


为了避免与非切片情况混淆,最好对所有维度使用切片语法,例如,[1:2][1:1],而不是[2][1:1]

可以省略切片说明符 lower-bound 和/或 upper-bound ;缺少的界限被数组下标的下限或上限替换。
例如:

SELECT schedule[:2][2:] FROM sal_emp WHERE name = 'Bill';
schedule
{{lunch},{presentation}}

(1 row)


SELECT schedule[:][1:1] FROM sal_emp WHERE name = 'Bill';
schedule
{{meeting},{training}}

(1 row)


如果数组本身或任何下标表达式为null,则数组下标表达式将返回null。
此外,如果下标超出数组边界,则返回null(这种情况不会引发错误)。
例如,如果schedule当前具有维度[1:3][1:2]然后引用schedule[3][3]产生NULL。
类似地,下标数量错误的数组引用会产生null而不是错误。

如果数组本身或任何下标表达式为null,则数组切片表达式同样会产生null。
但是,在其他情况下,例如选择完全超出当前数组边界的数组切片,切片表达式会产生一个空(零维)数组而不是null。
(这与非切片行为不匹配,并且是出于历史原因。)如果请求的切片部分重叠数组边界,则它会静默减少到仅重叠区域,而不是返回null。

可以使用array_dims函数检索任何数组值的当前维度:

SELECT array_dims(schedule) FROM sal_emp WHERE name = 'Carol';
array_dims
[1:2][1:2]

(1 row)


array_dims生成一个text结果,方便人们阅读,但可能不方便程序。
维度也可以用array_upperarray_lower检索,它们分别返回指定数组维度的上限和下限:

SELECT array_upper(schedule, 1) FROM sal_emp WHERE name = 'Carol';

array_upper
2

(1 row)


array_length将返回指定数组维度的长度:

SELECT array_length(schedule, 1) FROM sal_emp WHERE name = 'Carol';

array_length
2

(1 row)


cardinality返回数组中所有维度的元素总数。
它实际上是调用unnest会产生的行数:

SELECT cardinality(schedule) FROM sal_emp WHERE name = 'Carol';
cardinality
4

(1 row)


8.15.4修改数组

可以完全替换数组值:

UPDATE sal_emp SET pay_by_quarter = '{25000,25000,27000,27000}'WHERE name = 'Carol';

或使用ARRAY表达式语法:

UPDATE sal_emp SET pay_by_quarter = ARRAY[25000,25000,27000,27000]WHERE name = 'Carol';

数组也可以在单个元素处更新:

UPDATE sal_emp SET pay_by_quarter[4] = 15000WHERE name = 'Bill';

或在切片中更新:

UPDATE sal_emp SET pay_by_quarter[1:2] = '{27000,27000}'WHERE name = 'Carol';

省略 lower-bound 切片语法和/或 upper-bound 切片语法也可以使用,但仅限于更新不是NULL或零维的数组值时(否则,没有现有的下标限制可以替换)。

可以通过分配给尚未存在的元素来放大存储的数组值。
以前存在的元素和新分配的元素之间的任何位置都将用空值填充。
例如,如果数组myarray当前有4个元素,则在分配给myarray[6]的更新后将有6个元素;myarray[5]将包含null。
目前,这种方式的放大只允许一维数组,而不允许多维数组。

订阅赋值允许创建不使用基于一的下标的数组。
例如,可以分配给myarray[-2:7]以创建下标值从-2到7的数组。

新的数组值也可以使用连接运算符||

SELECT ARRAY[1,2] || ARRAY[3,4];
?column?
{1,2,3,4}

(1 row)


SELECT ARRAY[5,6] || ARRAY[[1,2],[3,4]];
?column?
{{5,6},{1,2},{3,4}}

(1 row)


连接运算符允许将单个元素推送到一维数组的开头或结尾。
它还接受两个n维数组,或一个n维数组和一个 N+1 维数组。

当单个元素被压入一维数组的开头或结尾时,结果是一个数组,其下限下标与数组操作数相同。
例如:

SELECT array_dims(1 || '[0:1]={2,3}'::int[]);

array_dims
[0:2]

(1 row)


SELECT array_dims(ARRAY[1,2] || 3);

array_dims
[1:3]

(1 row)


当两个维数相等的数组连接时,结果保留左侧操作数外部维数的下限下标。
结果是一个数组,包括左侧操作数的每个元素,后跟右侧操作数的每个元素。
例如:

SELECT array_dims(ARRAY[1,2] || ARRAY[3,4,5]);

array_dims
[1:5]

(1 row)


SELECT array_dims(ARRAY[[1,2],[3,4]] || ARRAY[[5,6],[7,8],[9,0]]);

array_dims
[1:5][1:2]

(1 row)


当n维数组被推到 N+1 维数组的开始或结束时,结果类似于上面的元素数组情况。
每个n维子数组本质上是 N+1 维数组外部维度的一个元素。
例如:

SELECT array_dims(ARRAY[1,2] || ARRAY[[3,4],[5,6]]);

array_dims
[1:3][1:2]

(1 row)


也可以使用函数array_prependarray_appendarray_cat构造数组。
前两个只支持一维数组,但array_cat支持多维数组。
一些例子:

SELECT array_prepend(1, ARRAY[2,3]);

array_prepend
{1,2,3}

(1 row)


SELECT array_append(ARRAY[1,2], 3);

array_append
{1,2,3}

(1 row)


SELECT array_cat(ARRAY[1,2], ARRAY[3,4]);

array_cat
{1,2,3,4}

(1 row)


SELECT array_cat(ARRAY[[1,2],[3,4]], ARRAY[5,6]);

array_cat
{{1,2},{3,4},{5,6}}

(1 row)


SELECT array_cat(ARRAY[5,6], ARRAY[[1,2],[3,4]]);

array_cat
{{5,6},{1,2},{3,4}}
***
在简单的情况下,上面讨论的连接运算符比直接使用这些函数更受欢迎。
但是,由于连接运算符被重载以服务于所有三种情况,因此在某些情况下使用其中一个函数有助于避免歧义。
例如考虑:```sql
SELECT ARRAY[1, 2] || '{3, 4}';  -- the untyped literal is taken as an array
?column?
{1,2,3,4}

SELECT ARRAY[1, 2] || '7';                 -- so is this one
ERROR:  malformed array literal: "7"SELECT ARRAY[1, 2] || NULL;                -- so is an undecorated NULL
?column?
{1,2}

(1 row)


SELECT array_append(ARRAY[1, 2], NULL);    -- this might have been meant

array_append
{1,2,NULL}
***
在上面的例子中,解析器在连接运算符的一侧看到一个整数数组,在另一侧看到一个未确定类型的常量。
它用来解析常量类型的启发式方法是假设它与运算符的另一个输入类型相同——在本例中是整数数组。
因此,假设连接运算符表示`array_cat`,而不是`array_append`。
当这是错误的选择时,可以通过将常量转换为数组的元素类型来修复它;但是明确使用`array_append`可能是更可取的解决方案。***
### 8.15.5数组中的搜索要在数组中搜索值,必须检查每个值。
如果您知道数组的大小,这可以手动完成。
例如:```sql
SELECT * FROM sal_emp WHERE pay_by_quarter[1] = 10000 ORpay_by_quarter[2] = 10000 ORpay_by_quarter[3] = 10000 ORpay_by_quarter[4] = 10000;

然而,对于大型数组,这很快就会变得乏味,如果数组的大小未知,这也没有帮助。
第9.24节中描述了另一种方法。
上述查询可以替换为:

SELECT * FROM sal_emp WHERE 10000 = ANY (pay_by_quarter);

此外,您可以通过以下方式找到数组所有值都等于10000的行:

SELECT * FROM sal_emp WHERE 10000 = ALL (pay_by_quarter);

或者,可以使用generate_subscripts功能。
例如:

SELECT * FROM(SELECT pay_by_quarter,generate_subscripts(pay_by_quarter, 1) AS sFROM sal_emp) AS fooWHERE pay_by_quarter[s] = 10000;

该功能见表9.65。

您还可以使用&&运算符搜索数组,该运算符检查左操作数是否与右操作数重叠。
例如:

SELECT * FROM sal_emp WHERE pay_by_quarter && ARRAY[10000];

这个和其他数组运算符将在第9.19节中进一步描述。
它可以通过适当的索引来加速,如第11.2节所述。

您还可以使用array_positionarray_positions函数在数组中搜索特定值。
前者返回数组中第一次出现的值的下标;后者返回数组中所有出现的值的下标。
例如:

SELECT array_position(ARRAY['sun','mon','tue','wed','thu','fri','sat'], 'mon');

array_position
2

(1 row)


SELECT array_positions(ARRAY[1, 4, 3, 1, 3, 4, 2, 1], 1);

array_positions
{1,4,8}

(1 row)


Tips : 数组不是集合;搜索特定的数组元素可能是数据库设计错误的标志。
考虑使用一个单独的表,每个项目都有一行,这将是一个数组元素。
这将更容易搜索,并且可能对大量元素进行更好的扩展。


8.15.6数组输入输出语法

数组值的外部文本表示由根据数组元素类型的I/O转换规则解释的项目组成,加上指示数组结构的装饰。
装饰由数组值周围的花括号({})加上相邻项目之间的分隔符组成。
分隔符通常是逗号(,),但也可以是其他字符:它由数组元素类型的typdelim设置决定。
在PostgreSQL发行版中提供的标准数据类型中,都使用逗号,除了类型box,它使用分号(;)。
在多维数组中,每个维度(行、平面、立方体等)都有自己的花括号级别,分隔符必须写在同一级别的相邻花括号实体之间。

如果元素值是空字符串、包含花括号、分隔符、双引号、反斜杠或空格,或者匹配单词NULL,数组输出例程将在元素值周围加上双引号。
嵌入在元素值中的双引号和反斜杠将被反斜杠转义。
对于数字数据类型,可以肯定地假设双引号永远不会出现,但对于文本数据类型,应该准备好应对引号的存在或不存在。

默认情况下,数组维度的下限索引值设置为1。
为了用其他下限表示数组,可以在写入数组内容之前显式指定数组下标范围。
此装饰由方括号([])组成,围绕每个数组维度的下限和上限,中间有冒号(:)分隔符。
数组维度装饰后跟等号(=)。
例如:

SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;
e1e2
16

(1 row)


只有当有一个或多个下限与一个不同时,数组输出例程才会在其结果中包含显式维度。

如果为元素写入的值为NULL(在任何情况下都是变体),则该元素被视为NULL。
任何引号或反斜杠的存在都会禁用此功能,并允许输入文字字符串值“NULL”。
此外,为了向后兼容8.2之前的PostgreSQL版本,可以offarray_nulls配置参数以禁止将NULL识别为NULL。

如前所示,在编写数组值时,您可以在任何单个数组元素周围使用双引号。
如果元素值会混淆数组值解析器,您必须这样做。
例如,包含花括号、逗号(或数据类型的分隔符)、双引号、反斜杠或前导或尾随空格的元素必须双引号。
与单词NULL匹配的空字符串和字符串也必须引号。
要在带引号的数组元素值中放置双引号或反斜杠,请在其前面加上反斜杠。
或者,您可以避免引号并使用反斜杠转义来保护所有数据字符,否则这些字符将被视为数组语法。

您可以在左大括号之前或右大括号之后添加空格。
您还可以在任何单个项目字符串之前或之后添加空格。
在所有这些情况下,空格都将被忽略。
但是,双引号元素中的空格或元素两边被非空格字符包围的空格不会被忽略。


Tips : 在SQL命令中写入数组值时,ARRAY构造函数语法(参见第4.2.12节)通常比数组文字语法更容易使用。
ARRAY中,单个元素值的写入方式与非数组成员时的写入方式相同。


8.16 复合类型

复合类型表示行或记录的结构;它本质上只是字段名称及其数据类型的列表。
PostgreSQL允许以许多与简单类型相同的方式使用复合类型。
例如,可以将表的一列声明为复合类型。


8.16.1复合类型声明

以下是定义复合类型的两个简单示例:

CREATE TYPE complex AS (r       double precision,i       double precision
);CREATE TYPE inventory_item AS (name            text,supplier_id     integer,price           numeric
);

语法类似于CREATE TABLE,除了只能指定字段名称和类型;目前不能包含任何约束(如NOT NULL)。
请注意,AS关键字是必不可少的;没有它,系统会认为意味着不同类型的CREATE TYPE命令,您将得到奇怪的语法错误。

定义了类型后,我们可以使用它们来创建表:

CREATE TABLE on_hand (item      inventory_item,count     integer
);INSERT INTO on_hand VALUES (ROW('fuzzy dice', 42, 1.99), 1000);

或功能:

CREATE FUNCTION price_extension(inventory_item, integer) RETURNS numeric
AS 'SELECT $1.price * $2' LANGUAGE SQL;SELECT price_extension(item, 10) FROM on_hand;

每当您创建表时,也会自动创建一个与表同名的复合类型,以表示表的行类型。
例如,如果我们说:

CREATE TABLE inventory_item (name            text,supplier_id     integer REFERENCES suppliers,price           numeric CHECK (price > 0)
);

然后上面显示的相同的inventory_item复合类型将作为副产品出现,并且可以像上面一样使用。
然而,请注意当前实现的一个重要限制:由于没有约束与复合类型相关联,因此表定义中显示的约束不适用于表之外的复合类型的值。
(为了解决这个问题,在复合类型上创建一个域,并将所需的约束应用为域的CHECK约束。)


8.16.2构建综合价值观

要将复合值写入文字常量,请将字段值括在括号内,并用逗号分隔。
您可以在任何字段值周围加上双引号,如果它包含逗号或括号,则必须这样做。
(下面显示了更多详细信息。)因此,复合常量的一般格式如下:

'( val1 , val2 , ... )'

一个例子是:

'("fuzzy dice",42,1.99)'

这将是上面定义的inventory_item类型的有效值。
要使字段为NULL,请在列表中的位置不写入任何字符。
例如,此常量指定NULL第三个字段:

'("fuzzy dice",42,)'

如果您想要一个空字符串而不是NULL,请写双引号:

'("",42,)'

这里第一个字段是非NULL空字符串,第三个是NULL。

(这些常量实际上只是第4.1.2.7节中讨论的泛型类型常量的一个特例。
常量最初被视为字符串并传递给复合类型输入转换例程。
可能需要显式类型规范来告诉要将常量转换为哪种类型。)

还可以使用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语法规则,名称item被视为表名,而不是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修改复合类型

以下是插入和更新复合列的正确语法的一些示例。
首先,插入或更新整个列:

INSERT INTO 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 + 1 WHERE ...;

请注意,我们不需要(实际上也不能)在SET之后出现的列名周围加上括号,但是当在等号右侧引用表达式中的同一列时,我们确实需要括号。

我们也可以指定子字段作为INSERT的目标:

INSERT INTO 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)

(1 row)


但是请注意,简单名称与表名之前的列名匹配,因此此示例仅适用于查询的表中没有名为c的列。

普通qualified-column-name语法 table_name . column_name 可以理解为对表当前行的复合值应用字段选择。
(出于效率原因,实际上并没有这样实现。)

我们写作的时候

SELECT c.* FROM inventory_item c;

然后,根据SQL标准,我们应该将表的内容扩展为单独的列:

namesupplier_idprice
fuzzy dice421.99

(1 row)


就好像查询是

SELECT c.name, c.supplier_id, c.price FROM inventory_item c;

PostgreSQL会将这种扩展行为应用于任何复合值表达式,尽管如上所示,您需要在应用于. 的值周围写括号,只要它不是一个简单的表名。 例如,如果myfunc()是一个返回具有abc`列的复合类型的函数,那么这两个查询具有相同的结果:

SELECT (myfunc(x)).* FROM some_table;
SELECT (myfunc(x)).a, (myfunc(x)).b, (myfunc(x)).c FROM some_table;

Tips : 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/DELETEDELETE中的RETURNING列表、VALUES子句或行构造函数的顶层时,会导致这种列扩展。
在所有其他上下文中(包括嵌套在其中一个构造中时),将.* 附加到复合值不会改变该值,因为它意味着“所有列”因此再次生成相同的复合值。
例如,如果somefunc()接受复合值参数,这些查询是相同的:

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 ORDER BY c;
SELECT * FROM inventory_item c ORDER BY c.*;
SELECT * FROM inventory_item c ORDER BY ROW(c.*);

所有这些ORDER BY子句都指定行的复合值,从而根据第9.24.6节中描述的规则对行进行排序。
但是,如果inventory_item包含名为c的列,第一种情况将与其他情况不同,因为它意味着仅按该列排序。
给定前面显示的列名,这些查询也等价于上面的查询:

SELECT * FROM inventory_item c ORDER BY ROW(c.name, c.supplier_id, c.price);
SELECT * FROM inventory_item c ORDER BY (c.name, c.supplier_id, c.price);

(最后一种情况使用省略关键字ROW的行构造函数。)

与复合值相关的另一个特殊的语法行为是,我们可以使用函数式表示法来提取复合值的字段。
解释这一点的简单方法是表示法 field ( table ) table . field 是可以互换的。
例如,这些查询是等价的:

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不是表的真实列。


Tips : 由于这种行为,给一个接受单个复合类型参数的函数与该复合类型的任何字段同名是不明智的。
如果有歧义,如果使用字段名称语法,将选择字段名称解释,而如果使用函数调用语法,则选择函数。
但是,11之前的PostgreSQL版本总是选择字段名称解释,除非调用的语法要求它是函数调用。
在旧版本中强制解释函数的一种方法是对函数名称进行模式限定,即写 schema . func ( compositevalue )


8.16.6复合类型输入输出语法

复合值的外部文本表示由根据各个字段类型的I/O转换规则解释的项目组成,加上指示复合结构的装饰。
装饰由整个值周围的括号(())组成,加上相邻项目之间的逗号(,)。
括号外的空格被忽略,但在括号内,它被视为字段值的一部分,根据字段数据类型的输入转换规则可能重要,也可能不重要。
例如:

'(  42)'

如果字段类型是整数,则会忽略空格,但如果是文本,则不会。

如前所示,在写入复合值时,可以在任何单个字段值周围写入双引号。
如果字段值会使复合值解析器感到困惑,则必须这样做。
特别是,包含括号、逗号、双引号或反斜杠的字段必须双引号。
要在带引号的复合字段值中放入双引号或反斜杠,请在其前面加上反斜杠。
(此外,双引号字段值中的一对双引号被用来表示双引号字符,类似于SQL文字字符串中单引号的规则。)或者,您可以避免引用,并使用反斜杠转义来保护所有数据字符,否则这些字符将被视为复合语法。

完全为空的字段值(逗号或括号之间根本没有字符)表示NULL。
要写入空字符串而不是NULL的值,请写入""

如果字段值为空字符串或包含括号、逗号、双引号、反斜杠或空格,复合输出例程将在字段值周围加上双引号。
(对空格这样做不是必需的,但有助于易读性。)嵌入在字段值中的双引号和反斜杠将加倍。


Note : 请记住,您在SQL命令中编写的内容将首先被解释为字符串文字,然后被解释为组合。
这将使您需要的反斜杠数量翻倍(假设使用转义字符串语法)。
例如,要在复合值中插入包含双引号和反斜杠的text字段,您需要编写:

INSERT ... VALUES ('("\"\\")');

字符串字面量处理器删除了一级反斜杠,这样到达复合值解析器的内容看起来就像("\"\\")
反过来,馈送到text数据类型的输入例程的字符串变成了"\
(如果我们使用的是一种数据类型,其输入例程也专门处理反斜杠,bytea例如,我们可能需要命令中多达八个反斜杠才能将一个反斜杠放入存储的复合字段中。)美元报价(参见第4.1.2.4节)可用于避免双重反斜杠的需要。


Tips : 在SQL命令中写入复合值时,ROW构造函数语法通常比复合文字语法更容易使用。
ROW中,单个字段值的写入方式与非复合成员时的写入方式相同。


8.17 范围类型

范围类型是表示某个元素类型的值范围的数据类型(称为范围的子类型)。
例如,timestamp范围可用于表示会议室预订的时间范围。
在这种情况下,数据类型是tsrange(“时间戳范围”的缩写),timestamp是子类型。
子类型必须具有总顺序,以便明确定义元素值是在值范围内、之前还是之后。

范围类型很有用,因为它们在单个范围值中表示许多元素值,并且可以清楚地表达重叠范围等概念。
使用时间和日期范围进行调度是最明显的例子;但是价格范围、仪器的测量范围等等也很有用。

每个范围类型都有相应的多范围类型。
多范围是非连续、非空、非空范围的有序列表。
大多数范围运算符也适用于多范围,它们有自己的一些功能。


8.17.1内置范围和多范围类型

PostgreSQL带有以下内置范围类型:

  • int4range-integer的范围,int4multirange-对应Multirange
  • int8range-bigint的范围,int8multirange-对应Multirange
  • numrange-numeric的范围,nummultirange-对应Multirange
  • tsrange-范围的timestamp without time zonetsmultirange-相应的Multirange
  • tstzrange-范围的timestamp with time zonetstzmultirange-相应的Multirange
  • daterange-date范围,datemultirange-对应Multirange

此外,您可以定义自己的范围类型;有关详细信息,请参阅CREATE TYPE。


8.17.2实例

CREATE TABLE reservation (room int, during tsrange);
INSERT INTO reservation VALUES(1108, '[2010-01-01 14:30, 2010-01-01 15:30)');
-- Containment
SELECT int4range(10, 20) @> 3;
-- Overlaps
SELECT numrange(11.1, 22.2) && numrange(20.0, 30.0);
-- Extract the upper bound
SELECT upper(int8range(15, 25));
-- Compute the intersection
SELECT int4range(10, 20) * int4range(15, 25);
-- Is the range empty?
SELECT isempty(numrange(1, 5));

有关范围类型的运算符和函数的完整列表,请参见表9.54和表9.56。


8.17.3包容性和排他性界限

每个非空范围都有两个边界,下界和上限。
这些值之间的所有点都包含在范围内。
包含边界意味着边界点本身也包含在范围内,而排他边界意味着边界点不包含在范围内。

在范围的文本形式中,包容性下限用"[“表示,排他性下限用”(“表示。
同样,包容性上界用”]“表示,排他性上界用”)"表示。
(详见第8.17.5节。)

函数lower_incupper_inc分别测试范围值的下限和上限的包容性。


8.17.4无限(无界)范围

一个范围的下界可以省略,意思是所有小于上界的值都包含在该范围内,例如,(,3]
同样,如果省略了范围的上界,那么所有大于下界的值都包含在该范围内。
如果同时省略了下界和上界,则认为元素类型的所有值都在该范围内。
将缺失的界限指定为包含会自动转换为异,例如,[,]转换为(,)
您可以将这些缺失的值视为+/-infinity,但它们是特殊的范围类型值,被认为超出了任何范围元素类型的+/-infinity值。

具有“infinity”概念的元素类型可以将它们用作显式绑定值。
例如,对于时间戳范围,[today,infinity)排除了特殊的timestampinfinity,而[today,infinity]包括它,[today,)[today,]也是如此。

函数lower_infupper_inf分别测试范围的无限下限和上限。


8.17.5范围输入/输出

范围值的输入必须遵循以下模式之一:

(lower-bound,upper-bound)
(lower-bound,upper-bound]
[lower-bound,upper-bound)
[lower-bound,upper-bound]
empty

如前所述,括号或括号指示下限和上限是互斥的还是包含的。
请注意,最终的模式empty,表示一个空范围(不包含点的范围)。

该 lower-bound 可以是作为子类型的有效输入的字符串,也可以为空以表示没有下限。
同样, upper-bound 可以是作为子类型的有效输入的字符串,也可以为空以表示没有上限。

每个绑定值都可以使用"(双引号)字符进行引号。
如果绑定值包含括号、括号、逗号、双引号或反斜杠,这是必要的,因为否则这些字符将被视为范围语法的一部分。
要在带引号的绑定值中放置双引号或反斜杠,请在其前面加上反斜杠。
(此外,双引号绑定值中的一对双引号被用来表示双引号字符,类似于SQL文字字符串中单引号的规则。)或者,您可以避免引用,并使用反斜杠转义来保护所有数据字符,否则这些字符将被视为范围语法。
此外,要写入作为空字符串的绑定值,请写入"",因为不写入任何内容意味着无限边界。

允许在范围值前后留白,但括号或括号之间的任何空格都被视为下限或上限值的一部分。
(根据元素类型,它可能重要也可能不重要。)


Note : 这些规则与在复合类型文字中写入字段值的规则非常相似。
有关附加评论,请参见第8.16.6节。

例子:

-- includes 3, does not include 7, and does include all points in between
SELECT '[3,7)'::int4range;
-- does not include either 3 or 7, but includes all points in between
SELECT '(3,7)'::int4range;
-- includes only the single point 4
SELECT '[4,4]'::int4range;
-- includes no points (and will be normalized to 'empty')
SELECT '[4,4)'::int4range;

multirange的输入是包含零个或多个有效范围的花括号({}),用逗号分隔。
括号和逗号周围允许空格。
这是为了让人想起数组语法,尽管multirange要简单得多:它们只有一个维度,不需要引用它们的内容。
(然而,它们的范围范围可以如上所述引用。)

例子:

SELECT '{}'::int4multirange;
SELECT '{[3,7)}'::int4multirange;
SELECT '{[3,7), [8,9)}'::int4multirange;

8.17.6构建范围和多范围

每个范围类型都有一个与范围类型同名的构造函数。
使用构造函数通常比编写范围文字常量更方便,因为它避免了额外引用绑定值的需要。
构造函数接受两个或三个参数。
两个参数形式构造标准形式的范围(下限包括,上限不包括),而三个参数形式构造一个范围,其边界由第三个参数指定。
第三个参数必须是字符串之一"()“,”(]“,”[)“,或”[]"。
例如:

-- The full form is: lower bound, upper bound, and text argument indicating
-- inclusivity/exclusivity of bounds.
SELECT numrange(1.0, 14.0, '(]');
-- If the third argument is omitted, '[)' is assumed.
SELECT numrange(1.0, 14.0);
-- Although '(]' is specified here, on display the value will be converted to
-- canonical form, since int8range is a discrete range type (see below).
SELECT int8range(1, 14, '(]');
-- Using NULL for either bound causes the range to be unbounded on that side.
SELECT numrange(NULL, 2.2);

每个范围类型还有一个与多范围类型同名的多范围构造函数。
构造函数接受零个或多个参数,这些参数都是适当类型的范围。
例如:

SELECT nummultirange();
SELECT nummultirange(numrange(1.0, 14.0));
SELECT nummultirange(numrange(1.0, 14.0), numrange(20.0, 25.0));

8.17.7离散范围类型

离散范围是指其元素类型具有明确定义的“步长”,例如integerdate
在这些类型中,当两个元素之间没有有效值时,可以说它们是相邻的。
这与连续范围形成对比,在连续范围中,总是(或几乎总是)可以在两个给定值之间识别其他元素值。
例如,numeric类型上的范围是连续的,timestamp上的范围也是连续的。
(尽管timestamp精度有限,因此理论上可以被视为离散的,但最好认为它是连续的,因为步长通常不感兴趣。)

考虑离散范围类型的另一种方法是,每个元素值都有一个明确的“下一个”或“前一个”值。
知道了这一点,就可以通过选择下一个或前一个元素值而不是最初给出的值,在范围边界的包容和独占表示之间进行转换。
例如,在整数范围类型中,[4,8](3,9)表示相同的值集;但对于数字范围就不是这样了。

离散范围类型应该有一个规范化函数,该函数知道元素类型所需的步长。
规范化函数负责将范围类型的等效值转换为具有相同表示的值,特别是一致的包容性或排他性边界。
如果未指定规范化函数,则具有不同格式的范围将始终被视为不相等,即使它们实际上可能代表相同的值集。

内置的范围类型int4rangeint8rangedaterange都使用包含下限和排除上限的规范形式;即[)
但是,用户定义的范围类型可以使用其他约定。


8.17.8定义新的范围类型

用户可以定义自己的范围类型。
这样做的最常见原因是对内置范围类型中未提供的子类型使用范围。
例如,要定义子类型float8的新范围类型:

CREATE TYPE floatrange AS RANGE (subtype = float8,subtype_diff = float8mi
);SELECT '[1.234, 5.678]'::floatrange;

因为float8没有有意义的“步骤”,我们在这个例子中没有定义规范化函数。

当您定义自己的范围时,您会自动获得相应的多范围类型。

定义您自己的范围类型还允许您指定要使用的不同子类型B树运算符类或排序规则,以便更改确定哪些值属于给定范围的排序顺序。

如果子类型被认为具有离散值而不是连续值,则CREATE TYPE命令应指定canonical函数。
规范化函数采用输入范围值,并且必须返回可能具有不同边界和格式的等效范围值。
表示相同值集的两个范围的规范输出,例如整数范围[1, 7][1, 8),必须相同。
选择哪种表示形式作为规范表示形式并不重要,只要具有不同格式的两个等效值始终映射到具有相同格式的相同值。
除了调整包含/排他边界格式之外,规范化函数还可以舍入边界值,以防所需的步长大于子类型能够存储的步长。
例如,可以将timestamp上的范围类型定义为步长为一小时,在这种情况下,规范化函数需要舍入不是一小时倍数的边界,或者可能会引发错误。

此外,任何打算与GiST或SP-GiST索引一起使用的范围类型都应该定义子类型差异,或subtype_diff,函数。
(索引仍然可以在没有subtype_diff的情况下工作,但它的效率可能比提供差异函数要低得多。)子类型差异函数接受子类型的两个输入值,并返回它们的差异(即 X 减 Y )表示为float8值。
在我们上面的示例中,可以使用作为常规float8减号运算符基础的函数float8mi;但是对于任何其他子类型,都需要一些类型转换。
可能还需要一些关于如何将差异表示为数字的创造性想法。
在最大程度上,subtype_diff函数应该符合所选运算符类和排序规则所隐含的排序顺序;也就是说,根据排序顺序,当它的第一个参数大于第二个参数时,它的结果应该是正的。

一个不那么简单的subtype_diff函数例子是:

CREATE FUNCTION time_subtype_diff(x time, y time) RETURNS float8 AS
'SELECT EXTRACT(EPOCH FROM (x - y))' LANGUAGE sql STRICT IMMUTABLE;CREATE TYPE timerange AS RANGE (subtype = time,subtype_diff = time_subtype_diff
);SELECT '[11:10, 23:00]'::timerange;

有关创建范围类型的更多信息,请参见CREATE TYPE。


8.17.9索引

可以为范围类型的表列创建GiST和SP-GiST索引。
也可以为多范围类型的表列创建GiST索引。
例如,要创建GiST索引:

CREATE INDEX reservation_idx ON reservation USING GIST (during);

范围上的GiST或SP-GiST索引可以加速涉及这些范围运算符的查询:=&&<@@><<>>-|-&<&>
多范围上的GiST索引可以加速涉及同一组多范围运算符的查询。
范围上的GiST索引和多范围上的GiST索引也可以加速涉及这些交叉类型范围到多范围和多范围到范围运算符的查询,相应地:&&<@@><<>>-|-&<&>
有关详细信息,请参见表9.54。

此外,可以为范围类型的表列创建B树和散列索引。
对于这些索引类型,基本上唯一有用的范围操作是相等。
为范围值定义了B树排序排序,并带有相应的<>运算符,但排序相当任意,在现实世界中通常没有用。
范围类型的B树和散列支持主要是为了允许查询中的内部排序和散列,而不是创建实际索引。


8.17.10范围限制

虽然UNIQUE是标量值的自然约束,但它通常不适合范围类型。
相反,排除约束通常更合适(参见CREATE TABLE… CONSTRAINT…EXCLUDE)。
排除约束允许对范围类型指定约束,例如“非重叠”。
例如:

CREATE TABLE reservation (during tsrange,EXCLUDE USING GIST (during WITH &&)
);

该约束将防止表中同时存在任何重叠值:

INSERT INTO reservation VALUES('[2010-01-01 11:30, 2010-01-01 15:00)');
INSERT 0 1INSERT INTO reservation VALUES('[2010-01-01 14:45, 2010-01-01 15:45)');
ERROR:  conflicting key value violates exclusion constraint "reservation_during_excl"
DETAIL:  Key (during)=(["2010-01-01 14:45:00","2010-01-01 15:45:00")) conflicts
with existing key (during)=(["2010-01-01 11:30:00","2010-01-01 15:00:00")).

您可以使用btree_gist扩展定义纯标量数据类型的排除约束,然后可以将其与范围排除相结合,以获得最大的灵活性。
例如,安装btree_gist后,仅当会议室编号相等时,以下约束才会拒绝重叠范围:

CREATE EXTENSION btree_gist;
CREATE TABLE room_reservation (room text,during tsrange,EXCLUDE USING GIST (room WITH =, during WITH &&)
);INSERT INTO room_reservation VALUES('123A', '[2010-01-01 14:00, 2010-01-01 15:00)');
INSERT 0 1INSERT INTO room_reservation VALUES('123A', '[2010-01-01 14:30, 2010-01-01 15:30)');
ERROR:  conflicting key value violates exclusion constraint "room_reservation_room_during_excl"
DETAIL:  Key (room, during)=(123A, ["2010-01-01 14:30:00","2010-01-01 15:30:00")) conflicts
with existing key (room, during)=(123A, ["2010-01-01 14:00:00","2010-01-01 15:00:00")).INSERT INTO room_reservation VALUES('123B', '[2010-01-01 14:30, 2010-01-01 15:30)');
INSERT 0 1


8.18 域类型

域是基于另一种基础类型的用户定义数据类型。
可选地,它可以具有约束,将其逻辑值限制为基础类型允许的子集。
否则,它的行为与基础类型相似——例如,可以应用于基础类型的任何运算符或函数都将适用于域类型。
基础类型可以是任何内置或用户定义的基类型、枚举类型、数组类型、复合类型、范围类型或其他域。

例如,我们可以创建一个仅接受正整数的整数域:

CREATE DOMAIN posint AS integer CHECK (VALUE > 0);
CREATE TABLE mytable (id posint);
INSERT INTO mytable VALUES(1);   -- works
INSERT INTO mytable VALUES(-1);  -- fails

当将基础类型的运算符或函数应用于域值时,域会自动向下转换为基础类型。
因此,例如,mytable.id - 1的结果被认为是integer而不是posint类型。
我们可以编写(mytable.id - 1)::posint将结果转换回posint,从而重新检查域的约束。
在这种情况下,如果表达式应用于id值1,则会导致错误。
允许将基础类型的值分配给域类型的字段或变量,而无需编写显式转换,但将检查域的约束。

有关其他信息,请参阅创建域。


8.19 对象标识符类型

对象标识符(OID)在PostgreSQL内部用作各种系统表的主键。
类型oid表示对象标识符。
oid还有几种别名类型,每种都命名为reg something
表8.26显示了概述。

目前,oid类型被实现为无符号四字节整数,因此,它还不够大,无法在大型数据库中,甚至在大型单个表中提供数据库范围的唯一性。

除了比较之外,oid类型本身很少有操作。
但是,它可以转换为整数,然后使用标准整数运算符进行操作。
(如果这样做,请注意可能的signed-versus-unsigned混淆。)

除了专门的输入和输出例程之外,OID别名类型没有自己的操作。
这些例程能够接受和显示系统对象的符号名称,而不是oid类型将使用的原始数值。
别名类型允许简化对象的OID值查找。
例如,要检查与表mytable相关的pg_attribute行,可以这样写:

SELECT * FROM pg_attribute WHERE attrelid = 'mytable'::regclass;

而不是:

SELECT * FROM pg_attributeWHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'mytable');

虽然这本身看起来并不那么糟糕,但它仍然过于简化。
如果在不同的模式中有多个名为mytable的表,则需要更复杂的子选择来选择正确的OID。
regclass输入转换器根据模式路径设置处理表查找,因此它会自动执行“正确的事情”。
类似地,将表的OID转换为regclass对于数字OID的符号显示很方便。


表8.26 对象标识符类型

名称引用描述值示例
oid任何数字对象标识符564182
regclasspg_class关系名称pg_type
regcollationpg_collation排序规则名称"POSIX"
regconfigpg_ts_config文本搜索配置english
regdictionarypg_ts_dict文本搜索字典simple
regnamespacepg_namespace命名空间名称pg_catalog
regoperpg_operator运算符名称+
regoperatorpg_operator带参数类型的运算符(integer,integer)-(NONE,integer)`
regprocpg_proc函数名称sum
regprocedurepg_proc带参数类型的函数sum(int4)
regrolepg_authid角色名称smithee
regtypepg_type数据类型名称integer

按命名空间分组的对象的所有OID别名类型都接受模式限定名称,并且如果在当前搜索路径中找不到未限定的对象,则会在输出中显示模式限定名称。
例如,myschema.mytableregclass(如果有这样的表)的可接受输入。
该值可能会输出为myschema.mytable,或者仅mytable,具体取决于当前搜索路径。
regprocregoper别名类型将只接受唯一(不重载)的输入名称,因此它们的用途有限;对于大多数用途,regprocedureregoperator更合适。
对于regoperator,一元操作符通过为未使用的操作数写入NONE来标识。

这些类型的输入函数允许标记之间有空格,并且会将大写字母折叠成小写字母,除了在双引号内;这样做是为了使语法规则类似于SQL中对象名称的写入方式。
相反,如果需要,输出函数将使用双引号来使输出成为有效的SQL标识符。
例如,一个名为Foo(大写F)的函数的OID可以输入为' "Foo" ( int, integer ) '::regprocedure
输出看起来像"Foo"(integer,integer)
函数名称和参数类型名称也可以是模式限定的。

许多内置PostgreSQL函数接受表或其他类型数据库对象的OID,为了方便起见,声明为采用regclass(或适当的OID别名类型)。
这意味着您不必手动查找对象的OID,只需将其名称作为字符串文字输入即可。
例如,nextval(regclass)函数采用序列关系的OID,因此您可以这样调用它:

nextval('foo')              operates on sequence foo
nextval('FOO')              same as above
nextval('"Foo"')            operates on sequence Foo
nextval('myschema.foo')     operates on myschema.foo
nextval('"myschema".foo')   same as above
nextval('foo')              searches search path for foo

Note : 当您将这样一个函数的参数写成未经修饰的文字字符串时,它就变成了regclass类型(或适当的类型)的常量。
由于这实际上只是一个OID,它将跟踪最初识别的对象,尽管后来进行了重命名、模式重新分配等。
对于列默认值和视图中的对象引用,这种“早期绑定”行为通常是可取的。
但有时您可能需要“后期绑定”对象引用在运行时解析。
要获得后期绑定行为,请强制将常量存储为text常量而不是regclass

nextval('foo'::text)      foo is looked up at runtime

还可以使用to_regclass()函数及其兄弟函数执行运行时查找。
参见表9.71。

使用regclass的另一个实际例子是查找information_schema视图中列出的表的OID,这些视图不直接提供此类OID。
例如,人们可能希望调用pg_relation_size()函数,它需要表OID。
考虑到上述规则,正确的方法是

SELECT table_schema, table_name,pg_relation_size((quote_ident(table_schema) || '.' ||quote_ident(table_name))::regclass)
FROM information_schema.tables
WHERE ...

在需要时,quote_ident()函数将处理标识符的双引号

SELECT pg_relation_size(table_name)
FROM information_schema.tables
WHERE ...

不推荐使用,因为对于搜索路径之外或名称需要引用的表,它将失败。

大多数OID别名类型的一个附加属性是创建依赖项。
如果这些类型之一的常量出现在存储表达式中(例如列默认表达式或视图),它会创建对引用对象的依赖项。
例如,如果列具有默认表达式nextval('my_seq'::regclass),PostgreSQL了解默认表达式取决于序列my_seq,因此系统不会让序列在没有首先删除默认表达式的情况下被删除。
nextval('my_seq'::text)的替代方案不会创建依赖项。
regrole是此属性的例外。
存储表达式中不允许使用此类型的常量。)

系统使用的另一种标识符类型是xid,即事务(缩写为xact)标识符。
这是系统列xminxmax的数据类型。
事务标识符是32位数量。
在某些上下文中,使用64位变体xid8
xid值不同,`

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com