## Preface-前言
### What is PostgreSQL?
PostgreSQL 是一个基于加州大学伯克利分校计算机科学系开发的POSTGRES4.2版本的对象关系型数据库管理系统(ORDBMS).并且POSTGRES率先提出了许多概念,这些概念直到很晚才在某些商业数据库系统中可用.
PostgreSQL 是基于伯克利的源码上的一个开源产品,它支持大量的SQL标准特性并且还提供许多比较现代化的特性.
- 复杂查询
- 外键
- 触发器
- 可更新的视图
- 事务完整性
- 多版本并发控制(MVCC)
并且PostgreSQL可以非常方便的让用户进行扩展,例如添加新的:
- 数据类型
- 函数
- 操作符
- 聚合函数
- 索引方法
- 存储过程扩展语言支持
由于其自由使用协议,PostgreSQL可以被任何人以任何目的进行修改、分发,并且可以被用做为私有的、商业的或者学术使用.
### What is Postgres-XL?
#### In short
Postgres-XL 是一个基于PostgreSQL的一个开源项目,在此基础上提供可扩展的写入操作和大批量的并行处理.并且它是一个数据库组件集合体,可以将其安装在多个操作系统或虚拟机上.
可扩展的写入特性意味着Postgres-XL多个数据库服务来处理非常多的写入(如:updating SQL 语句),而不是单台数据库服务可以做到的.你可以有多个数据库服务来提供单个数据库视图,并且来至任意数据库服务上的任意数据库的修改操作都会实时同步给其他正在事务运行中的服务节点.透明则意味着,你不需要关系数据库内部是如果将数据存储到多个数据库服务器中.
我们可以配置Postgres-XL 运行在多个节点上.它将我们的数据以分布式模式进行存储,可以让我们对不同的表是以备份模式还是分区模式进行存储.当我们发起查询请求时,
Postgres-XL确定目标数据的存储位置,并将相应的计划分派到包含目标数据的服务器。
在典型的web系统中,我们有许多web服务或者应用服务来处理我们的事务.但是,通常不能对数据库服务器执行此操作,因为所有更改的数据都必须对所有事务可见.与其他数据库集群解决方案不同,Postgres-XL提供了此功能,而且你可以安装任意多的数据服务,每个数据库服务提供统一的数据视图给你的应用,任何数据库更新操作都会及时的同步给应用连接的其他服务器,当然这就是Postgres-XL最重要的一个特性.
另外一个非常重要的特性就是Postgres-XL提供大规模并行处理能力(MPP).我们可以使用在诸如: BI(Business Intelligence) ,数据湖(Data Warehousing)或者是大数据(Big Data).在Postgres-XL中进行MPP操作,是在协调节点上生成一个执行计划,然后分别发送给相关的数据节点, 然后执行此操作,使数据节点彼此直接通信,每个数据节点知道可以从何处接收到需要发送的任何元组,以及从何处发送给其他的节点.
#### Postgres-XL's Goal
Postgres-XL的最终目标是在所有的数据库工作负载中提供具有ACID一致性的数据库可伸缩性.因此Postgres-XL需要提供如下特性:
- Postgres-XL需要为应用提供多台服务器用于接收事务及请求,这也就是我们所谓的协调节点(**Coordinator**)
- 任意协调节点应该给应用提供一致的数据视图.简而言之就是任何协调节点上的更新都应该实时可见,就如同在单个PostgreSQL上做更新一样
- Postgres-XL应该允许Datanode彼此直接通信,并以高效且并行的方式执行查询
- 表应该能够存储在指定为复制或分布式的数据库中(称为片段或分区),备份或者分布式存储应该对于应用而言是透明的.也就是说,这样的复制表和分布式表被视为单个表,每个记录/元组的位置或副本数由Postgres-XL管理,并且对应用程序不可见.
- Postgres-XL需要提供兼容PostgreSQL API接口给应用
- Postgres-XL应该提供基础PostgreSQL数据库服务器的单一和统一视图,因此SQL语句不需要依赖及关心表信息是如何存储的.
#### Postgres-XL Key Components
在本章节中,我们将介绍Postgres-XL的核心组件.
Postgres-XL主要由3个核心组件组成:GTM(全局事务管理),协调节点(Coordinator )、数据节点(Data Node)
##### GTM (Global Transaction Manager)
GTM 是Postgres-XL中的核心组件,主要提供全局统一的事务管理及数据可见性控制.
在文档的后面章节中,会说明到PostgreSQL's的事务管理是基于MVCC(Multi-Version Concurrency Control),多版本并行控制.Postgres-XL将该技术提取到单独的组件(如GTM)中,因此任何Postgres-XL组件的事务管理都基于单个全局状态
##### Coordinator
协调器是应用程序与数据库交互的入口。 它的行为类似于常规的PostgreSQL后端过程,但是协调器不存储任何实际数据,数据实际存储在数据节点中.协调节点的主要工作是用于接收SQL语句,获取全局事务ID已经获取需要的全局快照,然后决定哪些节点需要来进行参与及执行语句.向Datanodes发出语句时,它与GXID和全局快照相关联,以便多版本并发控制(MVCC)属性扩展到整个群集.
##### Datanode
数据节点实际存储用户的数据.表存储以分布式方式或者备份方式存储在所有节点中.数据节点在这个数据库中没有一个全局的视图,它只关心自己本地存储的数据.传入的SQL语句被协调节点解析及做下一步操作以及子计划的制定.然后根据需要将它们与GXID和全局快照一起传输到每个涉及的数据节点.数据节点可以在单独的会话中接收来自各种协调器的请求。 但是,由于每个事务都是唯一标识的,并且与一致的(全局)快照相关联,因此每个Datanode都可以在其事务和快照上下文中正确执行.
#### Postgres-XL Inherits From PostgreSQL
Postgres-XL是PostgreSQL的扩展,并继承了它的大多数功能.
它是PostgreSQL及其原始Berkeley代码基础上的开源数据库产品,支持大量的SQL标准及提供许多先进的特性:
- 复杂查询
- 外键Key
- 触发器
- 视图
- 完整的事务支持,但是针对SSI(Serializable Snapshot Isolation)支持本地不完整
- 多版本并发控制(MVCC)
当然Postgres-XL 与PostgreSQL 一样,提供许多方式可以让用户进行自定义扩展,比如:
- 数据类型
- 函数
- 操作类型
- 聚合函数
- 索引方法定义
- 存储过程语言支持
由于其自由使用协议,PostgreSQL-XL可以被任何人以任何目的进行修改、分发,并且可以被用做为私有的、商业的或者学术使用.
### Getting Started
#### Installation
在我们使用Postgres之前先进行安装,当然你已经在你的站点上安装好了Postgres,是因为它已包含在您的操作系统发行版中,或者是因为系统管理员已经安装了它。在这种情况下,您应该从操作系统文档或系统管理员那里获取有关如何访问PostgreSQL的信息。
如果不确定PostgreSQL是否已经可用或是否可以在测试中使用它,则可以自己安装它。这样做并不难,这可能是一个很好的锻炼,PostgreSQL可以由任何非特权用户安装; 无需超级用户(root)访问权限。
如果您要自己安装PostgreSQL,请参阅第16章以获取有关安装的说明,并在安装完成后返回本指南。 确保紧密遵循有关设置适当的环境变量的部分。
如果您的站点管理员未以默认方式进行设置,则您可能还有更多工作要做。 例如,如果数据库服务器计算机是远程计算机,则需要将PGHOST环境变量设置为数据库服务器计算机的名称。 可能还必须设置环境变量PGPORT。 底线是这样的:如果您尝试启动一个应用程序,但抱怨它无法连接到数据库,则应咨询您的站点管理员,或者如果是您,请向该文档咨询以确保您的环境已正确设置。 如果您不理解上一段,请阅读下一节。
#### Architectural Fundamentals
在我们使用前,你应该对PostgreSQL 系统的结构有所了解.了解PostgreSQL的各个部分如何交互将使本章更加清晰。
简而言之,Postgres-XL是PostgreSQL数据库集群的集合,其行为就像整个集合是单个数据库集群一样。 根据您的数据库设计,每个表都在成员数据库之间复制或分片. 为了提供该能力,Postgres-XL 由3大核心组件组成,分别是: GTM,Coordinator,Datanode. GTM主要提供事务管理的ACID能力.Datanode 存储表数据及处理本地SQL能力.Coordinator处理来至应用的SQL 语句,并且决定调度哪些数据节点,然后发送任务计划进行处理.
我们通常将GTM运行在单台的服务器上,因为GTM管理者来至我们数据节点、协调节点需要的事务信息.要对同一服务器上运行的Coordinator和Datanode进程的多个请求和响应进行分组,可以配置GTM-Proxy。 GTM代理减少了与GTM的交互次数和数据量。 GTM代理还有助于处理GTM故障。
在同一台服务器上同时运行Coordinator和Datanode通常是一个好习惯,因为我们不必担心两者之间的工作负载平衡,并且您通常可以从本地复制表中获取数据,而无需在网络上发送额外的请求 。 您可以在任何数量的服务器上运行这两个组件。 因为Coordinator和Datanode本质上都是PostgreSQL实例,所以您应该对其进行配置以避免资源冲突。 为它们分配不同的工作目录和端口号非常重要。
Postgres-XL允许多个协调器独立但以集成方式接受来自应用程序的SQL 语句.来至任何协调节点上的写操作对于其他协调节点而言都是可见的,这些协调节点看上去就像一个单一的数据库一样. 协调节点的扮演的角色就是,接收请求,然后找到哪些数据节点需要进行执行参与、然后将请求发送给需要进行处理的节点,最终聚合结果然后回写给应用.
协调节点是不存储任何用户,它仅仅存储目录数据来确定如何处理语句,目标Datanode所在的位置等.因此你不必担心协调节点失败,一旦协调节点失败,你只需要选择另外的一个来进行连接.
GTM可能是单点故障(SPOF).为了防止这个,我们可以运行另外一个GTM(GTM-Standby)来作为GTM状态的备份.当GTM失败后,GTM代理节点可以选择备份节点进行连接.这个操作我们将在高可用章节进行具体说明.
基于上面所述的,Postgres-XL的协调节点和数据节点其实就是PostgreSQL 中的数据库server.在数据库术语中,PostgreSQL使用客户端/服务器模型。 PostgreSQL会话由以下协作过程(程序)组成:
- 服务器进程管理数据库文件,接受来自客户端应用程序的数据库连接,并代表客户端执行数据库操作。 数据库服务器程序称为postgres。
- 想要执行数据库操作的用户的客户端(前端)应用程序。 客户端应用程序的性质可能非常多样:客户端可以是面向文本的工具,图形应用程序,访问数据库以显示网页的Web服务器或专用的数据库维护工具。 PostgreSQL发行版中提供了一些客户端应用程序。 大多数是由用户开发的。
做为一个典型的客户端/服务端应用程序,客户端与服务端可以在不同的主机上.在这种情况下客户端与服务端的连接是通过TCP/IP网络协议进行连接.您应该牢记这一点,因为可以在数据库服务器计算机上无法访问(或只能使用其他文件名访问)可以在客户端计算机上进行访问.
PostgreSQL服务器可以处理来自客户端的多个并发连接。 为此,它为每个连接启动一个新的进程。 从那时起,客户端和新服务器进程进行通信,而无需原始postgres进程进行干预。 因此,主服务器进程始终在运行,等待客户端连接,而客户端及关联的服务器进程来来往往.(所有这些当然对用户都是不可见的。为完整性起见,我们在这里只提及它)
#### Creating a Postgres-XL cluster
如架构基础中所述,Postgres-XL是多个组件的集合。 在您开始初始工作设置可能会有些麻烦。 在本教程中,我们将展示如何从一个空的配置文件开始,并使用`pgxc_ctl`实用工具从头开始创建Postgres-XL集群。
每个要成为Postgres-XL设置一部分的节点都需要满足一些先决条件:
- 如果需要通过pgxc_ctl帮助程序来管理节点,节点则需要进行无密码ssh访问设置。
- 需要在所有的Postgres-XL集群上配置好能够访问可执行程序的环境变量信息,特别是当我们通过命令行进行访问时,比如ssh
- 需要更新`pg_hba.conf`来允许远程的访问,`pgxc_ctl.conf`配置文件中的诸如coordPgHbaEntries和datanodePgHbaEntries之类的变量可能需要进行适当的更改。
- 防火墙及iptables 需要开放相关的访问端口
`pgxc_ctl`扩展程序应该出现在您的PATH中。 如果不存在,则可以从源代码进行编译。
```
$ cd $XLSRC/src/bin/pgxc_ctl
$ make install
```
我们现在可以准备好我们的模板配置文件.`pgxc_ctl`扩展应用程序可以让我们创建3种不同的配置.我们现在通过选择empty类型的配置文件来创建我们的Postgres-XL集群.请注意,我们还需要为pgxc_ctl的正确执行来设置dataDirRoot环境变量.
```
$ export dataDirRoot=$HOME/DATA/pgxl/nodes
$ mkdir $HOME/pgxc_ctl
$ pgxc_ctl
Installing pgxc_ctl_bash script as /Users/postgres/pgxc_ctl/pgxc_ctl_bash.
Installing pgxc_ctl_bash script as /Users/postgres/pgxc_ctl/pgxc_ctl_bash.
Reading configuration using /Users/postgres/pgxc_ctl/pgxc_ctl_bash --home
/Users/postgres/pgxc_ctl --configuration
/Users/postgres/pgxc_ctl/pgxc_ctl.conf
Finished reading configuration.
******** PGXC_CTL START ***************
Current directory: /Users/postgres/pgxc_ctl
PGXC$ prepare config empty
PGXC$ exit
```
empty 配置文件已经准备就位.接下来需要修改`pgxc_ctl.conf`.作为一个最小的操作,pgxcOwner编写需要设置正确.该配置文件包含USER和HOME环境变量,以允许为当前用户轻松设置默认值。
接下来的一个操作就是添加主的GTM节点在集群中.
```
$ pgxc_ctl
PGXC$ add gtm master gtm localhost 20001 $dataDirRoot/gtm
```
使用监控命令来检查集群中的状态
```
$ pgxc_ctl
PGXC$ monitor all
Running: gtm master
```
接下来我们将添加2个协调节点.当第一个协调节点被添加后,他就立即启动.当另一个协调节点添加后,他将会连接任意一个已经存在的协调节点来进行元数据的获取(如:表结构信息、数据库结构信息等)
```
PGXC$ add coordinator master coord1 localhost 30001 30011 $dataDirRoot/coord_master.1 none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
PGXC$ add coordinator master coord2 localhost 30002 30012 $dataDirRoot/coord_master.2 none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
```
接下来我们将添加2个数据节点,当我们第一个数据节点被添加后,它将会连接一个已经存在的协调节点来获取全局的元数据信息.然后当我们第二个数据节点被添加后,它将会从已经存在的数据节点上来获取那些全局元数据信息.
```
PGXC$ add datanode master dn1 localhost 40001 40011 $dataDirRoot/dn_master.1 none none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
PGXC$ add datanode master dn2 localhost 40002 40012 $dataDirRoot/dn_master.2 none none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode master dn2
```
经过上面步骤的操作,你已经创建好一个Postgres-XL集群,接下来你可以进行快速开始的章节的阅读.强烈建议您仔细阅读整个文档,以获取有关我们下面将涉及的每个命令的更多详细信息。
连接协调节点并且创建一个测试数据库
```
#psql 连接数据库
$ psql -p 30001 postgres
postgres=# CREATE DATABASE testdb;
CREATE DATABASE
#\q 退出交互命令端
postgres=# \q
```
查看pgxc_node目录。 它应该显示所有已配置的节点。 负的节点ID值是正常的。 这将很快修复。
```
$ psql -p 30001 testdb
testdb=# SELECT * FROM pgxc_node;
node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-----------+-----------+-----------+-----------+----------------+------------------+-------------
coord1 | C | 30001 | localhost | f | f | 1885696643
coord2 | C | 30002 | localhost | f | f | -1197102633
dn1 | D | 40001 | localhost | t | t | -560021589
dn2 | D | 40002 | localhost | f | t | 352366662
(4 rows)
```
接下来我们将创建一个分布式存储的表,以该表的第一列的hash方式进行分布式存储
```
testdb=# CREATE TABLE disttab(col1 int, col2 int, col3 text) DISTRIBUTE BY HASH(col1);
CREATE TABLE
testdb=# \d+ disttab
Table "public.disttab"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+----------+--------------+-------------
col1 | integer | | plain | |
col2 | integer | | plain | |
col3 | text | | extended | |
Has OIDs: no
Distribute By: HASH(col1)
Location Nodes: ALL DATANODES
```
同理我们可以创建一个备份表
```
testdb=# CREATE TABLE repltab (col1 int, col2 int) DISTRIBUTE BY
REPLICATION;
CREATE TABLE
testdb=# \d+ repltab
Table "public.repltab"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
col1 | integer | | plain | |
col2 | integer | | plain | |
Has OIDs: no
Distribute By: REPLICATION
Location Nodes: ALL DATANODES
```
表创建好了,接下来往这2个表中插入一些样例数据
```
testdb=# INSERT INTO disttab SELECT generate_series(1,100), generate_series(101, 200), 'foo';
INSERT 0 100
testdb=# INSERT INTO repltab SELECT generate_series(1,100), generate_series(101, 200);
INSERT 0 100
```
进行上面的插入后,分布式存储的表数据有100行
```
testdb=# SELECT count(*) FROM disttab;
count
-------
100
(1 row)
```
并且这一百条数据不可能所有的都在同一个节点上.`xc_node_id` 是一个系统列,主要用于展示每行数据所属的数据节点的`node_id`.由于HASH函数的计算,数据的分布可能存在略微的分布不均
```
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 42
352366662 | 58
(2 rows)
```
对于复制的表,我们希望所有行都来自单个数据节点(即使另一个节点也具有副本)。
```
testdb=# SELECT count(*) FROM repltab;
count
-------
100
(1 row)
testdb=# SELECT xc_node_id, count(*) FROM repltab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 100
(1 row)
```
现在我们往集群中添加新的数据节点
```
PGXC$ add datanode master dn3 localhost 40003 40013 $dataDirRoot/dn_master.3 none none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode master dn2
Running: datanode master dn3
```
请注意,在群集重新配置期间,所有未完成的事务都将中止,会话将被重置。 因此,您通常会在进行中的会话看到此类错误
```
testdb=# SELECT * FROM pgxc_node;
ERROR: canceling statement due to user request <==== pgxc_pool_reload() resets all sessions and aborts all open transactions
testdb=# SELECT * FROM pgxc_node;
node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-----------+-----------+-----------+-----------+----------------+------------------+-------------
coord1 | C | 30001 | localhost | f | f | 1885696643
coord2 | C | 30002 | localhost | f | f | -1197102633
dn1 | D | 40001 | localhost | t | t | -560021589
dn2 | D | 40002 | localhost | f | t | 352366662
dn3 | D | 40003 | localhost | f | f | -700122826
(5 rows)
```
我们添加的新数据节点不会对已有的表产生影响.现在分布式信息还是显示的久节点信息
```
testdb=# \d+ disttab
Table "public.disttab"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+----------+--------------+-------------
col1 | integer | | plain | |
col2 | integer | | plain | |
col3 | text | | extended | |
Has OIDs: no
Distribute By: HASH(col1)
Location Nodes: dn1, dn2
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 42
352366662 | 58
(2 rows)
testdb=# \d+ repltab
Table "public.repltab"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+---------+--------------+-------------
col1 | integer | | plain | |
col2 | integer | | plain | |
Has OIDs: no
Distribute By: REPLICATION
Location Nodes: dn1, dn2
```
接下来让我们对表进行重新分布式操作,这样以便能够使用到新添加的数据节点
```
## 修改节点并添加一个数据节点
testdb=# ALTER TABLE disttab ADD NODE (dn3);
ALTER TABLE
testdb=# \d+ disttab
Table "public.disttab"
Column | Type | Modifiers | Storage | Stats target | Description
--------+---------+-----------+----------+--------------+-------------
col1 | integer | | plain | |
col2 | integer | | plain | |
col3 | text | | extended | |
Has OIDs: no
Distribute By: HASH(col1)
Location Nodes: ALL DATANODES
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-700122826 | 32
352366662 | 32
-560021589 | 36
(3 rows)
```
接下来让我们添加第三个的一个数据节点
```
PGXC$ add coordinator master coord3 localhost 30003 30013 $dataDirRoot/coord_master.3 none none
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: coordinator master coord3
Running: datanode master dn1
Running: datanode master dn2
Running: datanode master dn3
testdb=# SELECT * FROM pgxc_node;
ERROR: canceling statement due to user request
testdb=# SELECT * FROM pgxc_node;
node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-----------+-----------+-----------+-----------+----------------+------------------+-------------
coord1 | C | 30001 | localhost | f | f | 1885696643
coord2 | C | 30002 | localhost | f | f | -1197102633
dn1 | D | 40001 | localhost | t | t | -560021589
dn2 | D | 40002 | localhost | f | t | 352366662
dn3 | D | 40003 | localhost | f | f | -700122826
coord3 | C | 30003 | localhost | f | f | 1638403545
(6 rows)
```
我们可以尝试更多的ALTER TABLE,以便从分布式表中删除节点并将其添加回去
```
testdb=# ALTER TABLE disttab DELETE NODE (dn1);
ALTER TABLE
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
352366662 | 42
-700122826 | 58
(2 rows)
testdb=# ALTER TABLE disttab ADD NODE (dn1);
ALTER TABLE
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-700122826 | 32
352366662 | 32
-560021589 | 36
(3 rows)
```
你可以将备份表修改为一个分布式存储的表.即使集群中现在有3个数据节点,但你任然可以继续仅仅只使用备份表原来使用的2个数据节点.
```
testdb=# ALTER TABLE repltab DISTRIBUTE BY HASH(col1);
ALTER TABLE
testdb=# SELECT xc_node_id, count(*) FROM repltab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 42
352366662 | 58
(2 rows)
testdb=# ALTER TABLE repltab DISTRIBUTE BY REPLICATION;
ALTER TABLE
testdb=# SELECT xc_node_id, count(*) FROM repltab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 100
(1 row)
```
移除上面添加的协调节点.同时我们可以使用clean命令来移除相应的数据命令
```
PGXC$ remove coordinator master coord3 clean
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode master dn2
Running: datanode master dn3
testdb=# SELECT oid, * FROM pgxc_node;
ERROR: canceling statement due to user request
testdb=# SELECT oid, * FROM pgxc_node;
oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-------+-----------+-----------+-----------+-----------+----------------+------------------+-------------
11197 | coord1 | C | 30001 | localhost | f | f | 1885696643
16384 | coord2 | C | 30002 | localhost | f | f | -1197102633
16385 | dn1 | D | 40001 | localhost | t | t | -560021589
16386 | dn2 | D | 40002 | localhost | f | t | 352366662
16397 | dn3 | D | 40003 | localhost | f | f | -700122826
(5 rows)
```
接下来我们尝试移除一个数据节点.**注意**:Postgres-XL不使用任何其他检查来确定要删除的datanode是否具有来自已复制/分布式表的数据.其实这是用户自己的职责确保要移除的数据节点是否安全.你可以使用以下查询语句来确定将要被移除的数据节点是否还有任何数据.请注意,这仅显示当前数据库中的表。 您可能希望在继续删除datanode之前为所有数据库确保相同。 在以下查询中使用要删除的datanode的OID
```
testdb=# SELECT * FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];
pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids
---------+---------------+----------+-----------------+---------------+-------------------
16388 | H | 1 | 1 | 4096 | 16385 16386 16397
(1 row)
testdb=# ALTER TABLE disttab DELETE NODE (dn3);
ALTER TABLE
testdb=# SELECT * FROM pgxc_class WHERE nodeoids::integer[] @> ARRAY[16397];
pcrelid | pclocatortype | pcattnum | pchashalgorithm | pchashbuckets | nodeoids
---------+---------------+----------+-----------------+---------------+----------
(0 rows)
```
通过上面的操作,我们现在可以安全的移除dn3数据节点了
```
PGXC$ remove datanode master dn3 clean
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode master dn2
testdb=# SELECT oid, * FROM pgxc_node;
ERROR: canceling statement due to user request
testdb=# SELECT oid, * FROM pgxc_node;
oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-------+-----------+-----------+-----------+-----------+----------------+------------------+-------------
11197 | coord1 | C | 30001 | localhost | f | f | 1885696643
16384 | coord2 | C | 30002 | localhost | f | f | -1197102633
16385 | dn1 | D | 40001 | localhost | t | t | -560021589
16386 | dn2 | D | 40002 | localhost | f | t | 352366662
(4 rows)
```
`pgxc_ctl`帮助工具也提供了对于数据节点与协调节点的从节点配置.接下来我们将配置一个从节点来模拟当主节点宕机时,怎么来进行故障转移.
```
PGXC$ add datanode slave dn1 localhost 40101 40111 $dataDirRoot/dn_slave.1 none $dataDirRoot/datanode_archlog.1
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode slave dn1
Running: datanode master dn2
testdb=# EXECUTE DIRECT ON(dn1) 'SELECT client_hostname, state, sync_state FROM pg_stat_replication';
client_hostname | state | sync_state
-----------------+-----------+------------
| streaming | async
(1 row)
```
接下来再添加更多的数据来测试故障转移
```
testdb=# INSERT INTO disttab SELECT generate_series(1001,1100), generate_series(1101, 1200), 'foo';
INSERT 0 100
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 94
352366662 | 106
(2 rows)
```
接下来我们来模拟一个数据节点故障转移.首先我们停掉我们配置了从节点的的数据节点dn1.请注意,由于从节点连接到主节点,因此我们将使用“立即”模式停止它。
```
PGXC$ stop -m immediate datanode master dn1
```
一旦我们停止了数据节点,查询将会失败.尽管对于某些未使用该节点进行数据查询的部分查询任何可以正常运行,但这取决于数据的分布和所使用的WHERE子句。
```
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
ERROR: Failed to get pooled connections
testdb=# SELECT xc_node_id, * FROM disttab WHERE col1 = 3;
xc_node_id | col1 | col2 | col3
------------+------+------+------
352366662 | 3 | 103 | foo
(1 row)
```
现在,我们将执行故障转移,并检查一切正常。
```
PGXC$ failover datanode dn1
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
ERROR: canceling statement due to user request
testdb=# SELECT xc_node_id, count(*) FROM disttab GROUP BY xc_node_id;
xc_node_id | count
------------+-------
-560021589 | 94
352366662 | 106
(2 rows)
```
pgxc_node目录现在应该具有更新的条目。 特别是,已故障转移的数据节点node_host和node_port应该已替换为从属节点的主机和端口值。
```
testdb=# SELECT oid, * FROM pgxc_node;
oid | node_name | node_type | node_port | node_host | nodeis_primary | nodeis_preferred | node_id
-------+-----------+-----------+-----------+-----------+----------------+------------------+-------------
11197 | coord1 | C | 30001 | localhost | f | f | 1885696643
16384 | coord2 | C | 30002 | localhost | f | f | -1197102633
16386 | dn2 | D | 40002 | localhost | f | t | 352366662
16385 | dn1 | D | 40101 | localhost | t | t | -560021589
(4 rows)
PGXC$ monitor all
Running: gtm master
Running: coordinator master coord1
Running: coordinator master coord2
Running: datanode master dn1
Running: datanode master dn2
```
#### Creating a Database
测试我们部署的数据库服务是否可以就是创建一个数据.一个运行着的数据库服务可以管理多个数据库.通常,每个项目或每个用户使用一个单独的数据库。
如果管理员已经为你准备好了一个数据库.你可以跳过本节进行下一章节的阅读
创建一个名叫mydb的数据库,我们可以使用以下命令
```
$ createdb mydb
```
如果没有响应,则说明此步骤成功,您可以跳过本节的其余部分。
如果你看到如下的消息:
```
createdb: command not found
```
则说明PostgreSQL没有被正确的安装.或者说根据就没安装,也有可能是没配置shell环境变量.这个时候,你可以使用绝对路径进行代替使用
```
$ /usr/local/pgsql/bin/createdb mydb
```
您网站上的路径可能不同。 请与您的站点管理员联系,或查看安装说明以纠正这种情况。
如果看到的响应是如下情况:
```
createdb: could not connect to database postgres: could not connect to server: No such file or directory
Is the server running locally and accepting
connections on Unix domain socket "/tmp/.s.PGSQL.5432"?
```
这意味着数据服务没有启动,或者是当访问`createdb`时没有启动.再或者是检查安装说明或咨询管理员。
如果是另外一个响应结果:
```
createdb: could not connect to database postgres: FATAL: role "joe" does not exist
```
提到您自己的登录名。 如果管理员没有为您创建PostgreSQL用户帐户,则会发生这种情况。 (PostgreSQL用户帐户与操作系统用户帐户不同。)如果您是管理员,请参见第21章,以获取有关创建帐户的帮助。 您需要成为安装PostgreSQL的操作系统用户(通常为postgres)才能创建第一个用户帐户。 也可能是您被分配了一个与操作系统用户名不同的PostgreSQL用户名。 在这种情况下,您需要使用-U开关或设置PGUSER环境变量来指定您的PostgreSQL用户名。
如果你有账号,但是没有权限创建数据库,则你会看到如下信息:
```
createdb: database creation failed: ERROR: permission denied to create database
```
并非每个用户都有权创建新数据库。 如果PostgreSQL拒绝为您创建数据库,则站点管理员需要授予您创建数据库的权限。 如果发生这种情况,请咨询您的站点管理员。 如果您自己安装了PostgreSQL,则出于本教程的目的,应以启动服务器的用户帐户登录。
当然你可以以其他名字进行数据库创建.PostgreSQL允许我们在一个站点上创建多个数据库.数据库名称必须以字母开头的字符,并且长度限制为63个字节一个方便的选择是使用与当前用户名相同的名称创建一个数据库。 许多工具都假定数据库名称为默认名称,因此可以节省一些键入时间。 要创建该数据库,只需键入:
```
$ createdb
```
如果你不需要使用你的数据库,你可以将它删除.例如: 如果你是mydb数据库的创建者,你可以通过如下命令进行删除:
```
$ dropdb mydb
```
(对于此命令,数据库名称并非默认为用户帐户名称。您始终需要指定它。)此操作实际上会删除与数据库关联的所有文件,并且无法撤消,因此只能做很多事情 的预见。
有关createdb和dropdb的更多信息,可以分别在createdb和dropdb中找到。
作为其工作原理的解释:PostgreSQL用户名与操作系统用户帐户分开。 连接数据库时,可以选择连接的PostgreSQL用户名。 如果您不这样做,它将默认使用与当前操作系统帐户相同的名称。 碰巧的是,总会有一个PostgreSQL用户帐户与启动服务器的操作系统用户同名,并且碰巧该用户始终具有创建数据库的权限。 除了以该用户身份登录外,您还可以在各处指定-U选项以选择一个PostgreSQL用户名进行连接。
#### Accessing a Database
一旦你创建好了数据库后,你可以进行如下操作:
- 使用PostgreSQL提供的交互式命令程序psql来进行访问、编辑及执行SQL语句
- 使用像pgAdmin之类的图形化工具或者使用官方提供的ODBC或者JDBC来进行创建及操作数据库。这些东西的使用不在本教程中.
- 可以使用支持的语言来来自定义一个应用.这些东西将在后面的章节中介绍.
在本节示例中我们将以psql来开始我们案例测试.例如我们要访问mydb数据库,可以通过如下命令:
```
$ psql mydb
```
如果您不提供数据库名称,则它将默认为您的用户帐户名称。 您已经在上一节中使用createdb发现了该方案。
输入psql ,你会收到如下消息:
```
psql (10r1.1)
Type "help" for help.
mydb=>
```
最后一行是:
```
mydb=#
```
这意味着您是数据库超级用户,如果您自己安装PostgreSQL实例,则很可能是这种情况。 成为超级用户意味着您不受访问控制。 对于本教程而言,这并不重要。
如果启动psql时遇到问题,请返回上一部分。 createdb和psql的诊断类似,如果前者有效,后者也应有效。
由psql打印的最后一行是提示符,它表明psql正在监听您,并且您可以在psql维护的工作空间中键入SQL查询。 试用以下命令:
```
mydb=> SELECT version();
version
------------------------------------------------------------------------------------------
PostgreSQL 10r1.1 on x86_64-pc-linux-gnu, compiled by gcc (Debian 4.9.2-10) 4.9.2, 64-bit
(1 row)
mydb=> SELECT current_date;
date
------------
2016-01-07
(1 row)
mydb=> SELECT 2 + 2;
?column?
----------
4
(1 row)
```
`psql`程序有大量的不是SQL命令.他们以斜杠"\"开头.比如你想获取PostgreSQL SQL的帮助命令,可以输入:
```
mydb=> \h
```
退出psql程序,输入:
```
mydb=> \q
```
然后psql将退出并返回到您的命令外壳。 (有关更多内部命令,请在psql提示符下键入\?。)psql的全部功能在psql中有说明。 在本教程中,我们将不会显式使用这些功能,但是如果有帮助,您可以自己使用它们。
### The SQL Language
#### Introduction
这章节主要提供怎样通过SQL来进行简单的操作.这个教程只是一个基本的介绍不是一个完整的SQL教程.关于SQL的书籍很多,包括[melt93]和[date97]。 您应该知道,某些PostgreSQL语言功能是该标准的扩展。
在下面的示例中,我们假设您已经按照上一章的描述创建了一个名为mydb的数据库,并且已经能够启动psql。
在这节的示例都可以在PostgreSQL源码中src/tutorial/中查看(对于编译好的二级制文件可能不包含这些文件),对于要使用这些文件,首先先切换目录并且运行`make`命令:
```
$ cd ..../src/tutorial
$ make
```
这将创建脚本并编译包含用户定义的函数和类型的C文件。 然后,要开始本教程,请执行以下操作:
```
$ cd ..../tutorial
$ psql -s mydb
...
mydb=> \i basics.sql
```
\ i命令从指定文件中读取命令。 psql的-s选项使您进入单步模式,该模式在将每个语句发送到服务器之前会暂停。 本节中使用的命令在basics.sql文件中。
#### Concepts
PostgreSQL 是一个关系数据库管理系统(RDBMS),这就意味着系统数据存储的方式是以关系进行存储的.关系本质上是表的数学术语。 在表中存储数据的概念如今已变得司空见惯,以至于在本质上似乎显而易见,但是还有许多其他组织数据库的方式。 类似于Unix的操作系统上的文件和目录构成了分层数据库的示例。 面向对象的数据库是一个更现代的发展。
每个表都是行的命名集合。 给定表的每一行都有相同的一组命名列,并且每一列都是特定的数据类型。 尽管列在每一行中都有固定的顺序,但重要的是要记住,SQL不能以任何方式保证表中行的顺序(尽管可以对它们进行显式排序以进行显示)
表被分组为数据库,由单个PostgreSQL服务器实例管理的数据库集合构成数据库集群。
#### Creating a New Table
我们可以给新表给定表名及对列名及其类型:
```
CREATE TABLE weather (
city varchar(80),
temp_lo int, -- low temperature
temp_hi int, -- high temperature
prcp real, -- precipitation
date date
);
```
你可以在psql输入多行进行分割,psql能够认识到这是一个完整的命令,除非遇到分号,则表示命令结束.
可以在SQL命令中自由使用T空格(即空格,制表符和换行符)。 这意味着您可以键入与上面不同的对齐方式,甚至可以将所有命令都排成一行。 两个破折号(“-”)介绍注释。 跟随它们的所有内容将一直忽略到行尾。 SQL对关键字和标识符不区分大小写,除非对标识符加双引号以保留大小写(上面没有做过)。
varchar(80)指定一种数据类型,该数据类型可以存储长度不超过80个字符的任意字符串。 int是普通的整数类型。 real是用于存储单精度浮点数的类型。 日期应该是不言自明的。 (是的,date类型的列也被称为date。这可能很方便或令人困惑-您选择了。)
PostgreSQL支持以下标准SQL类型:int,smallint,real,double precision,char(N),varchar(N),日期,时间,时间戳和时间间隔,以及其他类型的常规实用程序和一组丰富的几何类型。 可以使用任意数量的用户定义数据类型来自定义PostgreSQL。 因此,类型名称不是语法中的关键字,除非需要在SQL标准中支持特殊情况。
第二个示例将存储城市及其关联的地理位置:
```
CREATE TABLE cities (
name varchar(80),
location point
);
```
点类型是PostgreSQL特定数据类型的示例。
最后,应该提到的是,如果您不再需要一个表或想要以其他方式重新创建它,则可以使用以下命令将其删除:
```
DROP TABLE tablename;
```
#### Populating a Table With Rows
`INSERT` 语句是用于往表中插入数据:
```
INSERT INTO weather VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');
```
请注意,所有数据类型都使用相当明显的输入格式。 如示例所示,不是简单数值的常量通常必须用单引号(')引起来。 日期类型实际上在接受方式上非常灵活,但是在本教程中,我们将坚持此处所示的明确格式。
`point` 类型要求的是一个坐标对作为输入,如下:
```
INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)');
```
到目前为止使用的语法要求您记住列的顺序。 另一种语法允许您显式列出列:
```
INSERT INTO weather (city, temp_lo, temp_hi, prcp, date)
VALUES ('San Francisco', 43, 57, 0.0, '1994-11-29');
```
如果希望,甚至可以省略一些列,例如,如果降水未知,则可以按不同的顺序列出这些列:
```
INSERT INTO weather (date, city, temp_hi, temp_lo)
VALUES ('1994-11-29', 'Hayward', 54, 37);
```
许多开发人员认为显式列出列要比隐式依赖顺序更好。
请输入上面显示的所有命令,以便在以下各节中使用一些数据。
当然你也可以使用`COPY`命令进行从纯文本文件中加载大量的数据.这通常会更快,因为COPY命令已针对该应用程序进行了优化,但灵活性却比INSERT小。 例子如下:
```
COPY weather FROM '/home/user/weather.txt';
```
源文件的文件名必须在运行后端进程的计算机(而不是客户端)上可用,因为后端进程直接读取文件。 您可以在COPY中阅读有关COPY命令的更多信息。
#### Querying a Table
要从一个表中检索数据,我们要进行查询操作.而SQL SELECT就是用于干这个事的.该语句分为一个选择列表(列出要返回的列的部分),一个表列表(列出从中检索数据的表的部分)和一个可选的限定条件(指定任何限制的部分) 。 例如,要检索表weather的所有行,请键入:
```
SELECT * FROM weather;
```
这里的*是查询所有列的一个简写.同样的结果我们可以写成:
```
SELECT city, temp_lo, temp_hi, prcp, date FROM weather;
```
输出结果如下:
```
city | temp_lo | temp_hi | prcp | date
---------------+---------+---------+------+------------
San Francisco | 46 | 50 | 0.25 | 1994-11-27
San Francisco | 43 | 57 | 0 | 1994-11-29
Hayward | 37 | 54 | | 1994-11-29
(3 rows)
```
当然你也可以在查询列表中使用表达式,而不是只是简单的列的引用.例如,你可以做如下操作:
```
SELECT city, (temp_hi+temp_lo)/2 AS temp_avg, date FROM weather;
```
这个结果如下:
```
city | temp_avg | date
---------------+----------+------------
San Francisco | 48 | 1994-11-27
San Francisco | 50 | 1994-11-29
Hayward | 45 | 1994-11-29
(3 rows)
```
Notice how the `AS` clause is used to relabel the output column. (The `AS` clause is optional.)
通过添加指定需要哪些行的WHERE子句,可以“限定”查询。 WHERE子句包含一个布尔(真值)表达式,并且仅返回布尔表达式为true的行。 限定中允许使用常规布尔运算符(AND,OR和NOT)。 例如,以下内容检索雨天的旧金山天气:
```
SELECT * FROM weather
WHERE city = 'San Francisco' AND prcp > 0.0;
```
结果如下:
```
city | temp_lo | temp_hi | prcp | date
---------------+---------+---------+------+------------
San Francisco | 46 | 50 | 0.25 | 1994-11-27
(1 row)
```
您可以请求按排序顺序返回查询结果:
```sql
SELECT * FROM weather
ORDER BY city;
city | temp_lo | temp_hi | prcp | date
---------------+---------+---------+------+------------
Hayward | 37 | 54 | | 1994-11-29
San Francisco | 43 | 57 | 0 | 1994-11-29
San Francisco | 46 | 50 | 0.25 | 1994-11-27
```
在此示例中,未完全指定排序顺序,因此您可能会以任一顺序获得San Francisco行。 但是,如果执行以下操作,则始终会得到上面显示的结果:
```sql
SELECT * FROM weather
ORDER BY city, temp_lo;
```
你可以发起移除重复行的查询请求:
```sql
SELECT DISTINCT city
FROM weather;
city
---------------
Hayward
San Francisco
(2 rows)
```
同样,结果行的顺序可能会有所不同。 通过将DISTINCT和ORDER BY一起使用,可以确保结果一致:
```sql
SELECT DISTINCT city
FROM weather
ORDER BY city;
```
尽管SELECT *对于即席查询很有用,但它在生产代码中被广泛认为是不好的样式,因为在表中添加一列会改变结果。
在某些数据库系统中,包括旧版本的PostgreSQL,DISTINCT的实现会自动对行进行排序,因此不需要ORDER BY。 但这不是SQL标准所必需的,并且当前的PostgreSQL不保证DISTINCT导致行被排序。
#### Joins Between Tables
到目前为止,我们的查询一次只能访问一个表。 查询可以一次访问多个表,也可以以同时处理表的多行的方式访问同一表。 一次访问相同或不同表的多行的查询称为联接查询。 例如,假设您希望列出所有天气记录以及相关城市的位置。 为此,我们需要将天气表每一行的城市列与城市表中所有行的名称列进行比较,然后选择这些值匹配的行对。
这可以通过以下查询完成:
```sql
SELECT *
FROM weather, cities
WHERE city = name;
city | temp_lo | temp_hi | prcp | date | name | location
---------------+---------+---------+------+------------+---------------+-----------
San Francisco | 46 | 50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco | 43 | 57 | 0 | 1994-11-29 | San Francisco | (-194,53)
(2 rows)
```
观察关于结果集的两件事:
- Haywards没有结果行。 这是因为在Haywards的城市表中没有匹配的条目,因此联接将忽略天气表中不匹配的行。 我们很快将看到如何解决此问题。
- 有两列包含城市名称。 这是正确的,因为天气和城市表中的列列表是串联的。 但是,在实践中这是不希望的,因此您可能希望显式列出输出列,而不是使用*:
```sql
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather, cities
WHERE city = name;
```
由于这些列的名称均不同,因此解析器会自动找到它们所属的表。 如果两个表中的列名重复,则需要限定这些列名以显示您的意思,例如:
```sql
SELECT weather.city, weather.temp_lo, weather.temp_hi,
weather.prcp, weather.date, cities.location
FROM weather, cities
WHERE cities.name = weather.city;
```
在联接查询中限定所有列名称是一种公认的好习惯,因此,如果以后将重复的列名称添加到一个表中,则查询不会失败。
到目前为止看到的那种联接查询也可以用这种替代形式来写:
```sql
SELECT *
FROM weather INNER JOIN cities ON (weather.city = cities.name);
```
这种语法不像上面那样常用,但是我们在这里显示它来帮助您理解以下主题。
现在,我们将弄清楚如何获取Hayward记录。我们希望查询执行的操作是扫描天气表,并针对每一行查找匹配的城市行。 如果找不到匹配的行,我们希望将一些“空值”替换为citys表的列。 这种查询称为外部联接。 (到目前为止,我们已经看到的联接是内部联接。)命令如下所示:
```sql
SELECT *
FROM weather LEFT OUTER JOIN cities ON (weather.city = cities.name);
city | temp_lo | temp_hi | prcp | date | name | location
---------------+---------+---------+------+------------+---------------+-----------
Hayward | 37 | 54 | | 1994-11-29 | |
San Francisco | 46 | 50 | 0.25 | 1994-11-27 | San Francisco | (-194,53)
San Francisco | 43 | 57 | 0 | 1994-11-29 | San Francisco | (-194,53)
(3 rows)
```
此查询称为左外部联接,因为联接运算符左侧提到的表将在输出中至少包含其每一行,而右侧表仅具有与表中某些行匹配的那些行。 当输出没有右表匹配项的左表行时,将空(空)值替换为右表列。
我们还可以针对自己加入表格。 这称为自连接。 例如,假设我们希望找到其他天气记录的温度范围内的所有天气记录。 因此,我们需要将每个天气行的temp_lo和temp_hi列与所有其他天气行的temp_lo和temp_hi列进行比较。 我们可以使用以下查询来做到这一点:
```sql
SELECT W1.city, W1.temp_lo AS low, W1.temp_hi AS high,
W2.city, W2.temp_lo AS low, W2.temp_hi AS high
FROM weather W1, weather W2
WHERE W1.temp_lo < W2.temp_lo
AND W1.temp_hi > W2.temp_hi;
city | low | high | city | low | high
---------------+-----+------+---------------+-----+------
San Francisco | 43 | 57 | San Francisco | 46 | 50
Hayward | 37 | 54 | San Francisco | 46 | 50
(2 rows)
```
在这里,我们将天气表重新标记为W1和W2,以便能够区分联接的左侧和右侧。 您还可以在其他查询中使用这些别名来保存一些输入内容,例如:
```sql
SELECT *
FROM weather w, cities c
WHERE w.city = c.name;
```
您会经常遇到这种缩写形式。
#### Aggregate Functions
像大多数其他的关系型数据库产品,PostgreSQL也支持聚合函数.聚合函数就是将多行查询的行计算为单个值.例如:有对行进行计算的集合函数count,sum,avg,max,min。
例如,我们可以查出低温的最高温度值:
```sql
SELECT max(temp_lo) FROM weather;
max
-----
46
(1 row)
```
如果我们想知道最高温度所在的城市,可以尝试:
```sql
SELECT city FROM weather WHERE temp_lo = max(temp_lo); WRONG
```
但是对于聚合函数max用在where字句中是不起效的(之所以存在此限制,是因为WHERE子句确定了哪些行将包含在聚合计算中; 因此很明显,必须在计算聚合函数之前对其进行评估。)但是,在通常情况下,可以通过使用子查询来重新查询以实现所需结果。
```sql
SELECT city FROM weather
WHERE temp_lo = (SELECT max(temp_lo) FROM weather);
city
---------------
San Francisco
(1 row)
```
这是可以的,因为子查询是一个独立的计算,可以独立计算外部查询中发生的事情自己的聚合。
聚集与GROUP BY子句结合使用也非常有用。 例如,我们可以通过以下方法获得每个城市观测到的最高低温:
```sql
SELECT city, max(temp_lo)
FROM weather
GROUP BY city;
city | max
---------------+-----
Hayward | 37
San Francisco | 46
(2 rows)
```
这样我们每个城市就有一个输出行。 每个汇总结果都是在与该城市匹配的表行上计算的。 我们可以使用HAVING过滤这些分组的行:
```sql
SELECT city, max(temp_lo)
FROM weather
GROUP BY city
HAVING max(temp_lo) < 40;
city | max
---------+-----
Hayward | 37
(1 row)
```
这仅对所有temp_lo值均低于40的城市提供了相同的结果。最后,如果我们只关心名称以“ S”开头的城市,则可以这样做:
```sql
SELECT city, max(temp_lo)
FROM weather
WHERE city LIKE 'S%' -- (1)
GROUP BY city
HAVING max(temp_lo) < 40;
```
了解聚合与SQL的WHERE和HAVING子句之间的交互非常重要。 WHERE和HAVING之间的根本区别在于:WHERE在计算组和聚合之前选择输入行(因此,它控制哪些行进入聚合计算),而HAVING在计算组和聚合之后选择组行。 因此,WHERE子句不能包含聚合函数; 尝试使用聚合来确定哪些行将被输入聚合是没有意义的。 另一方面,HAVING子句始终包含聚合函数。 (严格来说,允许您编写不使用聚合的HAVING子句,但是它很少有用。在WHERE阶段可以更有效地使用相同的条件。)
在前面的示例中,我们可以在WHERE中应用城市名称限制,因为它不需要聚合。 这比对HAVING添加限制更为有效,因为我们避免对所有未通过WHERE检查的行进行分组和汇总计算。
#### Updates
我们可以通过`UPDATE`语句来更新已经存在的行数据.假设您发现11月28日之后温度读数全部偏离了2度。您可以按以下方式更正数据:
```sql
UPDATE weather
SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2
WHERE date > '1994-11-28';
```
查看我们更新数据的新状态如下:
```sql
SELECT * FROM weather;
city | temp_lo | temp_hi | prcp | date
---------------+---------+---------+------+------------
San Francisco | 46 | 50 | 0.25 | 1994-11-27
San Francisco | 41 | 55 | 0 | 1994-11-29
Hayward | 35 | 52 | | 1994-11-29
(3 rows)
```
#### Deletions
我们可以通过`DELETE`命令来讲表中的数据进行移除.假设你不再关心Hayward的天气信息,你可以做如下操作来将数据进行移除:
```sql
DELETE FROM weather WHERE city = 'Hayward';
```
所有属于Hayward的数据将被移除掉
```sql
SELECT * FROM weather;
city | temp_lo | temp_hi | prcp | date
---------------+---------+---------+------+------------
San Francisco | 46 | 50 | 0.25 | 1994-11-27
San Francisco | 41 | 55 | 0 | 1994-11-29
(2 rows)
```
需要注意的一个删除操作语句:
```sql
DELETE FROM tablename;
```
对于没有带where限定符的操作,`DELETE`将会移除掉所有数据,释放资源.并且当做该操作时,系统不会给出任何的警告通知.
### Advanced Features
#### Introduction
在上一章节中我们简要的介绍了如果使用SQL在PostgreSQL中进行数据的存储和访问.接下来,我们将讨论SQL的一些更高级的功能,这些功能可简化管理并防止数据丢失或损坏.最后,我们将看下PostgreSQL的一些扩展功能.
本章有时会参考第2章中的示例以进行更改或改进,因此阅读该章将很有用。 本章中的一些示例也可以在tutorial目录中的advanced.sql中找到。 该文件还包含一些要加载的样本数据,这里不再赘述。 (有关如何使用该文件,请参阅第2.1节。)
#### Views
请参阅第2.6节中的查询。 假设您的应用程序特别关注气象记录和城市位置的组合列表,但是您不想在每次需要时都键入查询。 您可以在查询上创建一个视图,该视图为查询提供一个名称,您可以像普通表一样引用该查询:
```sql
CREATE VIEW myview AS
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather, cities
WHERE city = name;
SELECT * FROM myview;
```
充分利用视图是良好的SQL数据库设计的关键方面。 通过视图,您可以在一致的接口后面封装表结构的详细信息,这些结构可能会随着应用程序的发展而改变。
视图几乎可以在任何可以使用真实表的地方使用。 在其他视图上构建视图并不少见。
#### Foreign Keys
回顾第2章中的天气和城市表。考虑以下问题:您要确保没有人可以在天气表中插入在城市表中没有匹配条目的行。 这称为维护数据的参照完整性。 在简单的数据库系统中,这可以通过首先查看城市表以检查是否存在匹配的记录,然后插入或拒绝新的天气记录来实现(如果有的话)。 这种方法有很多问题,而且非常不方便,因此PostgreSQL可以为您做到这一点。
新的表结构声明如下:
```sql
CREATE TABLE cities (
city varchar(80) primary key,
location point
);
CREATE TABLE weather (
city varchar(80) references cities(city),
temp_lo int,
temp_hi int,
prcp real,
date date
);
```
现在我们尝试插入一个不合法的数据:
```sql
INSERT INTO weather VALUES ('Berkeley', 45, 53, 0.0, '1994-11-28');
ERROR: insert or update on table "weather" violates foreign key constraint "weather_city_fkey"
DETAIL: Key (city)=(Berkeley) is not present in table "cities".
```
外键的行为可以根据您的应用程序进行微调。 我们将不会超出本教程中的这个简单示例,而只是请您参考第5章以获取更多信息。 正确使用外键肯定会提高数据库应用程序的质量,因此强烈建议您学习它们。
请注意,只有在分配表时这些列是分配键时,才允许使用主键和参考键。 默认情况下,Postgres-XC根据表的第一列的值分配表的每一行。 您可以选择任何列作为表分布的基础,或者可以在所有Datanode中具有表的副本。
#### Transactions
事务是所有数据库系统的基本概念。事务的关键点在于它将多个步骤捆绑为一个单一的“全部成功或全部失败”操作.步骤之间的中间状态对于其他并发事务是不可见的,并且如果发生某些故障导致该事务无法完成,则所有步骤都不会影响数据库.
例如,考虑一个银行数据库,其中包含各种客户帐户的余额以及分支机构的总存款余额。 假设我们要记录从Alice的帐户到Bob的帐户的$ 100.00的付款。 简而言之,SQL命令看起来像
```sql
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
UPDATE branches SET balance = balance - 100.00
WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Alice');
UPDATE accounts SET balance = balance + 100.00
WHERE name = 'Bob';
UPDATE branches SET balance = balance + 100.00
WHERE name = (SELECT branch_name FROM accounts WHERE name = 'Bob');
```
这些命令的细节在这里并不重要; 重要的一点是,要完成此相当简单的操作,需要涉及几个单独的更新。 我们银行的管理人员将希望确保所有这些更新都发生了,或者没有发生。 当然,如果系统故障导致Bob收到未从Alice借记的$ 100.00,也不会这样做。 如果在没有Bob的信用的情况下将其借记,Alice也永远不会成为一个满意的客户。 我们需要保证,如果在操作过程中出现问题,到目前为止执行的所有步骤都不会生效。 将更新分组到一个事务中为我们提供了保证。 交易是原子的:从其他交易的角度来看,它要么完全发生,要么根本不发生。
我们还希望保证,一旦事务完成并由数据库系统确认,该事务确实已被永久记录,即使随后不久发生崩溃也不会丢失。 例如,如果我们正在记录Bob提取的现金,我们不希望他的帐户中的借方在他走出银行大门后的崩溃中消失的任何可能性。 事务性数据库保证在报告事务完成之前,事务所做的所有更新都记录在永久性存储中(即,在磁盘上)。
事务数据库的另一个重要属性与原子更新的概念密切相关:当多个事务同时运行时,每个事务都应该看不到其他事务所做的不完整更改。 例如,如果一笔交易正忙于汇总所有分支机构的余额,那么它就不会包括从Alice的分支机构借来的款项,而不包括Bob分支机构的贷方,反之亦然。 因此,交易不仅必须是对数据库的永久影响,而且还必须视它们发生时的可见性而定。 迄今为止,一个打开的事务所做的更新对于其他事务是不可见的,直到该事务完成为止,所有更新同时变为可见。
在PostgreSQL中,通过用BEGIN和COMMIT命令包围事务的SQL命令来建立事务。因此我们得银行操作事务将会如下:
```sql
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
-- etc etc
COMMIT;
```
如果在交易过程中,我们决定不想提交(也许我们刚刚注意到Alice的余额为负),则可以发出命令ROLLBACK而不是COMMIT,并且到目前为止所有更新都将被取消。
PostgreSQL实际上将每个SQL语句都视为在事务内执行。 如果您不发出BEGIN命令,则每个单独的语句都有一个隐式的BEGIN和(如果成功的话)用COMMIT包装。 由BEGIN和COMMIT包围的一组语句有时称为事务块。
通过使用保存点,可以以更精细的方式控制事务中的语句。 使用保存点,您可以在提交其余事务的同时有选择地丢弃部分事务。 使用SAVEPOINT定义保存点后,如果需要,您可以使用ROLLBACK TO回滚到该保存点。 在定义保存点和回滚到保存点之间的所有事务数据库更改都将被放弃,但会保留早于保存点的更改。
回滚到保存点后,将继续对其进行定义,因此您可以回滚几次。 相反,如果您确定不需要再次回滚到特定的保存点,则可以将其释放,因此系统可以释放一些资源。 请记住,释放或回滚到保存点将自动释放其后定义的所有保存点。
所有这些都是在事务块内发生的,因此其他数据库会话都看不到它。 当并且如果您提交事务块,则已提交的操作作为一个单元在其他会话中变得可见,而回滚的操作则永远不会变得可见。
记住银行数据库,假设我们从爱丽丝的帐户中借了100.00美元,并将鲍勃的帐户记入贷方,后来才发现我们应该贷到沃利的帐户。 我们可以使用如下保存点来做到这一点:
```sql
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
SAVEPOINT my_savepoint;
UPDATE accounts SET balance = balance + 100.00
WHERE name = 'Bob';
-- oops ... forget that and use Wally's account
ROLLBACK TO my_savepoint;
UPDATE accounts SET balance = balance + 100.00
WHERE name = 'Wally';
COMMIT;
```
当然,此示例过于简化,但是通过使用保存点,可以在事务块中进行很多控制。 此外,ROLLBACK TO是重新获得对由于错误而被系统置于异常中止状态的事务块的控制的唯一方法,因为它无法完全回滚并重新启动。
#### Window Functions
window function对一组与当前行相关的表行执行计算.这相当于可以使用聚合函数完成的计算类型.但是,window function不会像non-window 聚合调用那样将行分组为单个输出行.相反的,这些行保留其各自的标识。 在后台,窗口功能不仅可以访问查询结果的当前行,还可以访问更多内容
这是一个示例,显示如何比较每个员工的薪水和其所在部门的平均薪水:
```sql
SELECT depname, empno, salary, avg(salary) OVER (PARTITION BY depname) FROM empsalary;
depname | empno | salary | avg
-----------+-------+--------+-----------------------
develop | 11 | 5200 | 5020.0000000000000000
develop | 7 | 4200 | 5020.0000000000000000
develop | 9 | 4500 | 5020.0000000000000000
develop | 8 | 6000 | 5020.0000000000000000
develop | 10 | 5200 | 5020.0000000000000000
personnel | 5 | 3500 | 3700.0000000000000000
personnel | 2 | 3900 | 3700.0000000000000000
sales | 3 | 4800 | 4866.6666666666666667
sales | 1 | 5000 | 4866.6666666666666667
sales | 4 | 4800 | 4866.6666666666666667
(10 rows)
```
前三个输出列直接来自表的经验,表中的每一行都有一个输出行。 第四列表示与当前行具有相同depname值的所有表行的平均值。 (这实际上与非窗口平均聚合函数相同,但是OVER子句将其视为窗口函数,并在整个窗口框架内进行计算。)
窗口函数调用始终在窗口函数的名称和参数之后直接包含一个OVER子句。 这是从语法上将其与常规函数或非窗口聚合区分开来的。 OVER子句确定窗口函数如何精确地拆分查询的行以进行处理。 OVER中的PARTITION BY子句将行分成共享相同PARTITION BY表达式值的组或分区。 对于每一行,都在与当前行属于同一分区的行之间计算窗口函数。
您还可以使用OVER中的ORDER BY控制窗口函数处理行的顺序。 (窗口ORDER BY甚至不必匹配行的输出顺序。)这是一个示例:
```sql
SELECT depname, empno, salary,
rank() OVER (PARTITION BY depname ORDER BY salary DESC)
FROM empsalary;
depname | empno | salary | rank
-----------+-------+--------+------
develop | 8 | 6000 | 1
develop | 10 | 5200 | 2
develop | 11 | 5200 | 2
develop | 9 | 4500 | 4
develop | 7 | 4200 | 5
personnel | 2 | 3900 | 1
personnel | 5 | 3500 | 2
sales | 1 | 5000 | 1
sales | 4 | 4800 | 2
sales | 3 | 4800 | 2
(10 rows)
```
如此处所示,rank函数使用ORDER BY子句定义的顺序为当前行分区中的每个不同的ORDER BY值生成一个数字等级。 等级不需要显式参数,因为其行为完全由OVER子句确定。
窗口函数考虑的行是查询的FROM子句生成的“虚拟表”的行,并由其WHERE,GROUP BY和HAVING子句(如有)过滤。 例如,任何窗口函数都看不到因为不满足WHERE条件而被删除的行。 一个查询可以包含多个窗口函数,这些窗口函数使用不同的OVER子句以不同的方式分割数据,但是它们都作用于该虚拟表定义的同一行集合上。
我们已经看到,如果行的顺序不重要,则可以省略ORDER BY。 也可以省略PARTITION BY,在这种情况下,存在一个包含所有行的分区。
窗口函数还有另一个重要的概念:对于每一行,在其分区内都有一组行,称为窗口框。 某些窗口函数仅作用于窗口框架的行,而不作用于整个分区的行。 默认情况下,如果提供了ORDER BY,则该帧由分区开始到当前行的所有行组成,再加上根据ORDER BY子句等于当前行的所有后续行。 当省略ORDER BY时,默认框架由分区中的所有行组成。 [9]以下是使用sum的示例:
```sql
SELECT salary, sum(salary) OVER () FROM empsalary;
salary | sum
--------+-------
5200 | 47100
5000 | 47100
3500 | 47100
4800 | 47100
3900 | 47100
4200 | 47100
4500 | 47100
4800 | 47100
6000 | 47100
5200 | 47100
(10 rows)
```
上面,由于OVER子句中没有ORDER BY,所以窗口框架与分区相同,缺少分区就是整个表。 换句话说,每个总和都包含在整个表中,因此对于每个输出行我们都得到相同的结果。 但是,如果我们添加ORDER BY子句,则会得到非常不同的结果:
```sql
SELECT salary, sum(salary) OVER (ORDER BY salary) FROM empsalary;
salary | sum
--------+-------
3500 | 3500
3900 | 7400
4200 | 11600
4500 | 16100
4800 | 25700
4800 | 25700
5000 | 30700
5200 | 41100
5200 | 41100
6000 | 47100
(10 rows)
```
在这里,总和是从第一(最低)工资一直到当前工资,包括当前工资的任何重复项(请注意重复工资的结果)。
窗口函数仅在查询的SELECT列表和ORDER BY子句中允许。 禁止在其他地方使用它们,例如GROUP BY,HAVING和WHERE子句。 这是因为它们在处理这些子句之后在逻辑上执行。 同样,窗口函数在非窗口聚合函数之后执行。 这意味着在窗口函数的参数中包括聚合函数调用是有效的,反之亦然。
如果在执行窗口计算之后需要对行进行过滤或分组,则可以使用子选择。 例如:
```sql
SELECT depname, empno, salary, enroll_date
FROM
(SELECT depname, empno, salary, enroll_date,
rank() OVER (PARTITION BY depname ORDER BY salary DESC, empno) AS pos
FROM empsalary
) AS ss
WHERE pos < 3;
```
上面的查询仅显示来自内部查询的行,其排名小于3。
当查询涉及多个窗口函数时,可以用单独的OVER子句写出每个窗口函数,但是如果多个函数都希望使用相同的窗口行为,则这是重复的并且容易出错。 而是可以在WINDOW子句中命名每个窗口行为,然后在OVER中引用。 例如:
```sql
SELECT sum(salary) OVER w, avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
```
#### Inheritance
继承是面向对象数据库中的一个概念.它为数据库设计开辟了有趣的新可能性
接下来我们创建2个表,一个表是cities,一个表是capitals.当然,首都也是城市,因此当您列出所有城市时,您需要某种方式隐式显示首都。 如果您真的很聪明,可以发明这样的方案:
```sql
CREATE TABLE capitals (
name text,
population real,
altitude int, -- (in ft)
state char(2)
);
CREATE TABLE non_capitals (
name text,
population real,
altitude int -- (in ft)
);
CREATE VIEW cities AS
SELECT name, population, altitude FROM capitals
UNION
SELECT name, population, altitude FROM non_capitals;
```
就查询而言,这行得通,但是当您需要更新几行时,它变得很麻烦
一个更好的解决方式如下:
```sql
CREATE TABLE cities (
name text,
population real,
altitude int -- (in ft)
);
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);
```
在这个例子中,首都表的将继承他父表cities中的所有列(name,population,altitude).列name的类型为text,这是PostgreSQL的基础类型,用于可变长度的字符串。 州首府有一个额外的栏,state用于显示他们的状态。 在PostgreSQL中,一个表可以从零个或多个其他表继承。
例如,以下查询查找海拔500英尺以上的所有城市(包括州首府)的名称:
```sql
SELECT name, altitude
FROM cities
WHERE altitude > 500;
```
结果如下:
```sql
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
(3 rows)
```
另一方面,以下查询将查找非首府且海拔超过500英尺的所有城市:
```sql
SELECT name, altitude
FROM ONLY cities
WHERE altitude > 500;
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
(2 rows)
```
在此,`ONLY`在城市之前表示查询应仅在继承表中的城市表上而不是在城市下的表上运行。 我们已经讨论过的许多命令(SELECT,UPDATE和DELETE)仅支持`ONLY`写法
#### Conclusion
PostgreSQL具有本教程介绍中未涉及的许多功能,这些功能面向SQL的入门用户.这些特性将在本书的后面章节进行详细的说明。
如果您觉得需要更多入门资料,请访问[PostgreSQL](https://www.postgresql.org/)网站以获得更多资源的链接。
## The SQL Language
本部分描述了PostgreSQL中SQL语言的用法。 我们首先描述SQL的一般语法,然后说明如何创建用于保存数据的结构,如何填充数据库以及如何查询数据库。 中间部分列出了可用于SQL命令的可用数据类型和函数。 其余部分讨论了几个方面,这些方面对于优化数据库以获得最佳性能非常重要。
安排本部分中的信息,以便新手用户可以从头开始进行学习,以全面了解主题,而不必多次引用。 这些章节旨在成为独立的章节,以便高级用户可以根据需要单独阅读这些章节。 本部分中的信息以主题单元的叙述方式呈现。 寻求特定命令完整描述的读者应参阅第六部分。
这部分的读者应该知道如何连接到PostgreSQL数据库并发出SQL命令。 鼓励不熟悉这些问题的读者先阅读第一部分。 通常使用PostgreSQL交互式终端psql输入SQL命令,但是也可以使用具有类似功能的其他程序。
### 4.SQL Syntax
本章介绍SQL的语法。 它为理解以下章节奠定了基础,这些章节将详细介绍如何使用SQL命令定义和修改数据。
我们还建议已经熟悉SQL的用户仔细阅读本章,因为它包含一些规则和概念,这些规则和概念在SQL数据库之间不一致地实现,或者特定于PostgreSQL。
#### 4.1 Lexical Structure
SQL输入由一系列命令组成。 命令由一系列标记组成,这些标记以分号(“;”)终止。 输入流的末尾还会终止命令。 哪些标记有效,取决于特定命令的语法。
标记符可以是关键字,标识符,带引号的标识符,文字(或常量)或特殊字符符号。 令牌通常由空格(空格,制表符,换行符)分隔,但如果不存在歧义则不需要(通常只有特殊字符与某些其他令牌类型相邻时才是这种情况)。
例如,以下是(在语法上)有效的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 Identifiers and Key Words
上例中的SELECT,UPDATE或VALUES之类的标记是关键字的示例,即在SQL语言中具有固定含义的单词。标记MY_TABLE和A是标识符的示例。它们根据使用的命令来标识表,列或其他数据库对象的名称。因此,有时将它们简称为“名称”。关键字和标识符具有相同的词法结构,这意味着在不了解语言的情况下无法知道标识符是标识符还是关键字。关键字的完整列表可以在附录C中找到。
SQL标识符和关键字必须以字母(a-z,但也可以带有变音符号和非拉丁字母)或下划线(_)开头。标识符或关键字中的后续字符可以是字母,下划线,数字(0-9)或美元符号($)。请注意,根据SQL标准字母,不允许在标识符中使用美元符号,因此使用美元符号可能会使应用程序的可移植性降低。 SQL标准不会定义包含数字或以下划线开头或结尾的关键字,因此此格式的标识符可以避免与该标准的将来扩展可能发生冲突。
系统使用的标识符不超过NAMEDATALEN-1个字节;较长的名称可以写在命令中,但是会被截断。默认情况下,NAMEDATALEN为64,因此最大标识符长度为63个字节。如果此限制有问题,可以通过更改src / include / pg_config_manual.h中的NAMEDATALEN常量来提高此限制。
关键字和未加引号的标识符不区分大小写。 因此:
```sql
UPDATE MY_TABLE SET A = 5;
```
也可以写为以下形式:
```sql
uPDaTE my_TabLE SeT a = 5;
```
经常使用的约定是用大写字母写关键词,用小写字母写名字,例如:
```sql
UPDATE my_table SET a = 5;
```
还有第二种标识符:定界标识符或带引号的标识符。 它是通过将任意字符序列括在双引号(“)中形成的。定界的标识符始终是标识符,而不是关键字。因此,“ select”可用于引用名为“ select”的列或表, 而未加引号的选择将作为关键字,因此在期望使用表或列名的地方使用时会引发解析错误,该示例可以用加引号的标识符编写,如下所示:
```sql
UPDATE "my_table" SET "a" = 5;
```
带引号的标识符可以包含任何字符,但代码零的字符除外。 (要包含双引号,请写两个双引号。)这允许构造原本不可能的表名或列名,例如包含空格或&的名称。长度限制仍然适用。
带引号的标识符的变体允许包含由其代码点标识的转义Unicode字符。此变体以双引号之前的U&(大写或小写U,后跟与号)开头,中间没有空格,例如U&“ foo”。 (请注意,这会与运算符&产生歧义。请在运算符周围使用空格以避免出现此问题。)在引号内,可以通过写反斜杠后跟四位数的十六进制代码点号或反斜杠来指定Unicode字符。或者,反斜杠后跟加号,后跟六位十六进制代码点编号。例如,标识符“数据”可以写为
```
U&"d\0061t\+000061"
```
以下不那么简单的示例用西里尔字母写俄语单词“ slon”(大象):
```
U&"\0441\043B\043E\043D"
```
如果需要与反斜杠不同的转义字符,则可以在字符串后使用UESCAPE子句来指定它,例如:
```
U&"d!0061t!+000061" UESCAPE '!'
```
转义字符可以是十六进制数字,加号,单引号,双引号或空格字符以外的任何单个字符。请注意,转义符用单引号而不是双引号书写。
要在标识符中实际包含转义字符,请将其写入两次。
Unicode转义语法仅在服务器编码为UTF8时有效。当使用其他服务器编码时,只能指定ASCII范围(最大\ 007F)中的代码点。 4位和6位格式都可以用于指定UTF-16代理对,以组成代码点大于U + FFFF的字符,尽管从6位格式的可用性来看,这是不必要的。 (代理对不直接存储,而是组合成一个代码点,然后以UTF-8编码。)
引用标识符也使其区分大小写,而未引用的名称总是折叠为小写。例如,PostgreSQL认为标识符FOO,foo和“ foo”相同,但是“ Foo”和“ FOO”彼此不同。 (在PostgreSQL中将未加引号的名称折叠成小写是与SQL标准不兼容的,SQL标准说,未加引号的名称应该被折叠成大写。因此,根据标准,foo应该等于“ FOO”而不是“ foo”。如果您想编写可移植的应用程序,建议您始终使用特定名称,也不要使用任何特定名称。)
##### 4.1.2 Constants
PostgreSQL中有三种隐式类型的常量:字符串,位字符串和数字。 常量也可以用显式类型指定,这可以使系统实现更准确的表示和更有效的处理。 以下各节将讨论这些替代方案。
**4.1.2.1 String Constants**
SQL中的字符串常量是由单引号(')界定的任意字符序列,例如'This is a string'。 要在字符串常量中包含单引号字符,请写两个相邻的单引号,例如“ Dianne's horse”。 请注意,这与双引号字符(“)不同。
将仅用空格和至少一个换行符分隔的两个字符串常量连接起来并有效地对待,就好像该字符串已被写为一个常量一样。 例如:
```sql
SELECT 'foo'
'bar';
```
与如下格式等效:
```sql
SELECT 'foobar'
```
但是与如下格式不一样(是无效的语法。(这种有点奇怪的行为由SQL指定; PostgreSQL遵循该标准。)
```sql
SELECT 'foo' 'bar';
```
**4.1.2.2 String Constants with C-style Escapes**
PostgreSQL还接受“转义”字符串常量,这是SQL标准的扩展。 通过在开头的单引号之前写字母E(大写或小写)来指定转义字符串常量,例如E'foo'。 (当跨行继续使用转义字符串常量时,请仅在第一个开始的引号之前写E。)在转义字符串中,反斜杠字符(\)开始类似C的反斜杠转义序列,其中反斜杠和后跟字符( s)代表一个特殊的字节值,如表4.1所示。
**Table 4.1. Backslash Escape Sequences**
| Backslash Escape Sequence | Interpretation |
| -------------------------------------- | ------------------------------------------------ |
| `\b` | backspace |
| `\f` | form feed |
| `\n` | newline |
| `\r` | carriage return |
| `\t` | tab |
| \o, \oo, \ooo (o= 0 - 7) | octal byte value |
| \xh, \xhh(h = 0 - 9, A - F) | hexadecimal byte value |
| \uxxxx, `\Uxxxxxxxx (x = 0 - 9, A - F) | 16 or 32-bit hexadecimal Unicode character value |
反斜杠后的所有其他字符均按字面意义使用。因此,要包含反斜杠字符,请写两个反斜杠(\\)。另外,除常规的''之外,还可以通过写\'将单引号包含在转义字符串中。
您要负责创建的字节序列(尤其是在使用八进制或十六进制转义符时)在服务器字符集编码中组成有效字符。如果服务器编码为UTF-8,则应改用Unicode转义或第4.1.2.3节中说明的替代Unicode转义语法。 (另一种方法是手工进行UTF-8编码并写出字节,这将非常麻烦。)
仅当服务器编码为UTF8时,Unicode转义语法才能完全起作用。使用其他服务器编码时,只能指定ASCII范围(最大\ u007F)中的代码点。 4位和8位格式均可用于指定UTF-16代理对,以组成代码点大于U + FFFF的字符,尽管从技术上说,8位格式的可用性使这不必要。 (在服务器编码为UTF8的情况下使用代理对时,它们首先会组合成一个代码点,然后再以UTF-8进行编码。)
警告
如果配置参数standard_conforming_strings关闭,则PostgreSQL会在常规和转义字符串常量中识别反斜杠转义。但是,从PostgreSQL 9.1开始,默认设置为on,这意味着仅在转义字符串常量中识别反斜杠转义。此行为更符合标准,但是可能会破坏依赖历史行为的应用程序,在该行为中始终识别出反斜杠转义符。解决方法是,可以将此参数设置为off,但是最好不要使用反斜杠转义来进行迁移。如果需要使用反斜杠转义符来表示特殊字符,请使用E编写字符串常量。
除了standard_conforming_strings之外,配置参数escape_string_warning和backslash_quote还控制字符串常量中反斜杠的处理。
代码为零的字符不能为字符串常量。
**4.1.2.3 String Constants with Unicode Escapes**
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'!'
转义字符可以是十六进制数字,加号,单引号,双引号或空格字符以外的任何单个字符。
Unicode转义语法仅在服务器编码为UTF8时有效。当使用其他服务器编码时,只能指定ASCII范围(最大\ 007F)中的代码点。 4位和6位格式都可以用于指定UTF-16代理对,以组成代码点大于U + FFFF的字符,尽管从6位格式的可用性来看,这是不必要的。 (在服务器编码为UTF8的情况下使用代理对时,它们首先会组合成一个代码点,然后再以UTF-8进行编码。)
同样,仅在打开配置参数standard_conforming_strings时,字符串常量的Unicode转义语法才起作用。这是因为否则该语法可能会使解析SQL语句的客户端感到困惑,以至于可能导致SQL注入和类似的安全问题。如果参数设置为off,则此语法将被拒绝并显示一条错误消息。
要从字面上包括转义字符,请将其写入两次。
**4.1.2.4 Dollar-quoted String Constants**
虽然指定字符串常量的标准语法通常很方便,但是当所需的字符串包含许多单引号或反斜杠时,可能很难理解,因为每个引号或反斜杠必须加倍。为了在这种情况下允许更具可读性的查询,PostgreSQL提供了另一种称为“美元引号”的方式来编写字符串常量。以美元报价的字符串常量包含一个美元符号($),一个零个或多个字符的可选“标记”,另一个美元符号,构成字符串内容的任意字符序列,一个美元符号以及与之相同的标记开始用这美元报价和一个美元符号。例如,以下两种使用美元引号指定字符串“ Dianne's horse”的方法:
```
$$Dianne's horse$$
$SomeTag$Dianne's horse$SomeTag$
```
注意,在用美元报价的字符串中,可以使用单引号而不需要对其进行转义。确实,用美元引号引起的字符串中的任何字符都不会转义:字符串内容始终按字面意义编写。反斜杠不是特别的,也不是美元符号,除非它们是与开始标记匹配的序列的一部分。
通过在每个嵌套级别选择不同的标签,可以嵌套用美元报价的字符串常量。这在编写函数定义中最常用。例如:
```
$function$
BEGIN
RETURN ($1 ~ $q$[\t\r\n\v\\]$q$);
END;
$function$
```
$$代表用美元报价的文字字符串[\ t \ r \ n \ v \\],当函数体被识别时由PostgreSQL执行。但是由于该序列与外部美元引号分隔符$ function $不匹配,因此就外部字符串而言,它只是常量中的更多字符。
用美元引号的字符串的标签(如果有)遵循与未引用标识符相同的规则,不同之处在于它不能包含美元符号。标签区分大小写,因此$ tag $ String content $ tag $是正确的,但$ TAG $ String content $ tag $不是。
关键字或标识符后面用美元报价的字符串必须用空格分隔;否则,美元报价定界符将被视为先前标识符的一部分。
美元引号不是SQL标准的一部分,但是,与标准兼容的单引号语法相比,美元引号通常是一种更方便的写复杂字符串文字的方法。当在过程常量定义中经常需要在其他常量中表示字符串常量时,它特别有用。使用单引号语法时,上例中的每个反斜杠都必须写为四个反斜杠,在解析原始字符串常量时将其减少为两个反斜杠,然后在函数期间重新解析内部字符串常量时将其减少为一个反斜杠。执行。
**4.1.2.5 Bit-string Constants**
位字符串常量看起来像常规字符串常量,紧接在开引号之前(其间没有空格)带有B(大写或小写),例如B'1001'。 位字符串常量中唯一允许的字符是0和1。
或者,可以使用前导X(大写或小写),例如X'1FF',以十六进制表示法指定位串常量。 此表示法等效于每个十六进制数具有四个二进制数字的位字符串常量。
两种形式的位字符串常量都可以以与常规字符串常量相同的方式跨行继续。 美元引用不能在位字符串常量中使用。
**4.1.2.6 Numeric Constants**
可以以下列一般形式接受数字常数:
```
digits
digits.[digits][e[+-]digits]
[digits].digits[e[+-]digits]
digitse[+-]digits
```
其中数字是一个或多个十进制数字(0到9)。 如果使用小数点,则必须在小数点之前或之后至少有一位数字。 指数标记(e)后面必须至少有一位数字(如果有)。 常量中不能嵌入任何空格或其他字符。 请注意,任何前导的正号或负号实际上并未视为常量的一部分; 它是应用于常量的运算符。
这些是有效数字常量的一些示例:
42
3.5
4.
.001
5e2
1.925e-3
如果既不包含小数点也不包含指数的数字常量的值适合整数类型(32位),则最初将其假定为整数类型。 否则,如果其值适合bigint类型(64位),则假定它为bigint类型;否则,假定为bigint类型。 否则,将其视为数字类型。 最初总是假定包含小数点和/或指数的常量为数字类型。
最初分配的数值常量的数据类型只是类型解析算法的起点。 在大多数情况下,常量将根据上下文自动强制为最合适的类型。 必要时,可以通过强制转换数值将其解释为特定的数据类型。 例如,您可以通过编写以下代码来将数值强制为实数(float4)类型:
```
REAL '1.23' -- string style
1.23::REAL -- PostgreSQL (historical) style
```
这些实际上只是接下来讨论的一般转换符号的特殊情况。
**4.1.2.7 Constants of Other Types**
可以使用以下任何一种表示法输入任意类型的常量:
```
type 'string'
'string'::type
CAST ( 'string' AS type )
```
字符串常量的文本将传递给类型为type的输入转换例程。 结果是所指示类型的常数。 如果在常量类型上没有任何歧义(例如,当直接将其分配给表列时),则可以省略显式类型强制转换,在这种情况下,它将被自动强制。
可以使用常规SQL表示法或美元引号编写字符串常量。
也可以使用类似函数的语法来指定类型强制:
type('string')
但并非所有类型名称都可以这种方式使用; 有关详细信息,请参见第4.2.9节。
::,CAST()和函数调用语法也可以用于指定任意表达式的运行时类型转换,如第4.2.9节所述。 为避免语法歧义,“字符串”类型的语法只能用于指定简单文字常量的类型。 类型'string'语法的另一个限制是它不适用于数组类型。 使用::或CAST()指定数组常量的类型。
CAST()语法符合SQL。 类型'string'的语法是标准的概括:SQL仅针对几种数据类型指定此语法,但是PostgreSQL允许所有类型的语法。 ::的语法是PostgreSQL的历史用法,函数调用的语法也是如此。
##### 4.1.3 Operators
操作符名称是以下列表中最多NAMEDATALEN-1(默认为63)个字符的序列:+-* / <> =〜! @#%^&| `?
- --和/ *不能出现在运算符名称的任何位置,因为它们将被用作注释的开头。
- 多字符运算符名称不能以+或-结尾,除非名称也包含以下至少一个字符:~ ! @ # % ^ & | ` ?。例如,@-是允许的运算符名称,但*-不是。 此限制使PostgreSQL可以解析SQL兼容查询,而无需在标记之间使用空格。
使用非SQL标准的运算符名称时,通常需要用空格分隔相邻的运算符,以避免产生歧义。 例如,如果定义了一个名为@的左一元运算符,则不能写X * @ Y; 您必须编写X * @Y以确保PostgreSQL将其读取为两个运算符名称,而不是一个。
##### 4.1.4 Special Characters
某些不是字母数字的字符具有特殊的含义,不同于成为运算符。 有关用法的详细信息可以在描述相应语法元素的位置找到。 本部分仅用于建议存在和总结这些字符的目的。
- 美元符号($)后跟数字用于表示函数定义或准备好的语句主体中的位置参数。 在其他情况下,美元符号可以是标识符的一部分,也可以是美元引用的字符串常量。
- 括号(())具有将表达式分组和强制执行优先级的通常含义。 在某些情况下,括号是特定SQL命令的固定语法的一部分。
- 方括号([])用于选择数组的元素。 有关数组的更多信息,请参见第8.15节。
- 在某些语法构造中使用逗号(,)分隔列表的元素。
- 分号(;)终止SQL命令。 除了字符串常量或带引号的标识符外,它不能出现在命令中的任何位置。
- 冒号(:)用于从数组中选择“切片”。 (请参见第8.15节。)在某些SQL方言(例如Embedded SQL)中,冒号用于为变量名添加前缀。
- 在某些情况下,星号(*)用于表示表行或复合值的所有字段。 当用作聚合函数的参数时,它还具有特殊含义,即该聚合不需要任何显式参数。
- 句点(.)用于数字常量,并分隔模式,表和列名
##### 4.1.5 Comments
注释是由双破折号开始并延伸到行尾的一系列字符,例如:
```
-- This is a standard SQL comment
```
或者,可以使用C语言格式的块注释:
```
/* multiline comment
* with nesting: /* nested block comment */
*/
```
其中注释以/ *开头,并扩展到匹配的* /。 这些块注释按照SQL标准中的规定嵌套,但与C不同,因此可以注释掉可能包含现有块注释的较大代码块。
在进行进一步的语法分析之前,已从输入流中删除注释,并有效地将其替换为空格。
##### 4.1.6 Operator Precedence
表4.2显示了PostgreSQL中运算符的优先级和关联性。 大多数运算符具有相同的优先级,并且是左关联的。 运算符的优先级和关联性硬连接到解析器中。
当使用二进制和一元运算符的组合时,有时需要添加括号。 例如:
```
SELECT 5 ! - 6;
```
将会被解析为:
```
SELECT 5 ! (- 6);
```
因为解析器不知道,直到为时已晚为止! 被定义为后缀运算符,而不是后缀运算符。 为了在这种情况下获得所需的行为,您必须编写:
```
SELECT (5 !) - 6;
```
这是人们为扩展性付出的代价。
**Table 4.2. Operator Precedence (highest to lowest)**
| Operator/Element | Associativity | Description |
| --------------------------------------- | ------------- | --------------------------------------------------------- |
| `.` | left | table/column name separator |
| `::` | left | PostgreSQL-style typecast |
| `[` `]` | left | array element selection |
| `+` `-` | right | unary plus, unary minus |
| `^` | left | exponentiation |
| `*` `/` `%` | left | multiplication, division, modulo |
| `+` `-` | left | addition, subtraction |
| (any other operator) | left | all other native and user-defined operators |
| `BETWEEN` `IN` `LIKE` `ILIKE` `SIMILAR` | | range containment, set membership, string matching |
| `<` `>` `=` `<=` `>=` `<>` | | comparison operators |
| `IS` `ISNULL` `NOTNULL` | | `IS TRUE`, `IS FALSE`, `IS NULL`, `IS DISTINCT FROM`, etc |
| `NOT` | right | logical negation |
| `AND` | left | logical conjunction |
| `OR` | left | logical disjunction |
请注意,运算符优先级规则也适用于与上述内置运算符同名的用户定义的运算符。例如,如果您为某些自定义数据类型定义“ +”运算符,则无论您做什么,它都将与内置“ +”运算符具有相同的优先级。
在OPERATOR语法中使用模式限定的运算符名称时,例如:
SELECT 3 OPERATOR(pg_catalog.+) 4;
对于“任何其他运算符”,OPERATOR构造具有表4.2中所示的默认优先级。不管哪个特定运算符出现在OPERATOR()中都是如此。
9.5之前的PostgreSQL版本使用略有不同的运算符优先级规则。特别是,<=> =和<>过去被视为通用运算符;过去,IS测试具有更高的优先级;和NOT BETWEEN及其相关构造的行为不一致,在某些情况下被认为具有NOT而不是BETWEEN的优先权。更改这些规则是为了更好地符合SQL标准,并减少对逻辑等效结构的不一致处理所造成的混淆。在大多数情况下,这些更改不会导致任何行为更改,或者可能导致“没有此类操作员”失败,可以通过添加括号来解决。但是,在某些极端情况下,查询可能会更改行为而没有报告任何解析错误。如果您担心这些更改是否在默默地破坏了某些内容,则可以在打开配置参数operator_precedence_warning的情况下测试您的应用程序,以查看是否记录了任何警告。
#### 4.2 Value Expressions
##### 4.2.1 Column References
##### 4.2.2 Pesitional Parameters
##### 4.2.3 Subscripts
##### 4.2.4 Field Selection
##### 4.2.5 Operator Invocations
##### 4.2.6 Function Calls
##### 4.2.7 Aggregate Expressions
##### 4.2.8 Window Function Calls
##### 4.2.9 Type Casts
##### 4.2.10 Collation Expressions
##### 4.2.11 Scalar Subqueries
##### 4.2.12 Array Constructors
##### 4.2.13 Row Constructors
##### 4.2.14 Expression Evaluation Rules
#### 4.3 Calling Functions
##### 4.3.1 Using Positional Notation
##### 4.3.2 Using Named Notation
##### 4.3.3 Using Mixed Notation
### 5.Data Definition
本章介绍如何创建将保存数据的数据库结构。 在关系数据库中,原始数据存储在表中,因此,本章的大部分内容专门用于解释如何创建和修改表以及可用于控制表中存储哪些数据的功能。 随后,我们讨论如何将表组织成模式,以及如何将特权分配给表。 最后,我们将简要介绍影响数据存储的其他功能,例如继承,表分区,视图,函数和触发器。
#### 5.1 Table Basics
关系数据库中的表很像纸上表:它由行和列组成。 列的数量和顺序是固定的,并且每列都有一个名称。 行数是可变的-它反映了给定时刻存储了多少数据。 SQL对表中行的顺序不做任何保证。 读取表时,除非明确要求排序,否则行将以未指定的顺序显示。 这将在第7章中介绍。此外,SQL不会为行分配唯一的标识符,因此表中可能有几个完全相同的行。 这是基于SQL的数学模型的结果,但通常是不希望的。 在本章的后面,我们将看到如何处理这个问题。
每列都有一个数据类型。 数据类型限制了可以分配给列的可能值的集合,并为存储在列中的数据分配了语义,以便可以将其用于计算。 例如,声明为数字类型的列将不接受任意文本字符串,并且存储在此类列中的数据可用于数学计算。 相比之下,声明为字符串类型的列几乎可以接受任何类型的数据,但是它不适合进行数学计算,尽管可以使用其他操作,例如字符串连接。
PostgreSQL包括一组适合许多应用程序的内置数据类型。 用户还可以定义自己的数据类型。 大多数内置数据类型都有明显的名称和语义,因此我们在第8章进行详细说明。一些常用的数据类型是:整数表示整数,数字表示可能的小数,字符串表示文本,日期表示日期, 表示时间值的时间,以及包含日期和时间的值的时间戳。
要创建表,请使用适当命名的CREATE TABLE命令。 在此命令中,您至少要为新表指定一个名称,各列的名称以及各列的数据类型。 例如:
```sql
CREATE TABLE my_first_table (
first_column text,
second_column integer
);
```
这将创建一个名为my_first_table的表,该表具有两列。 第一列名为first_column,其数据类型为text。 第二列的名称为second_column,类型为整数。 表名和列名遵循第4.1.1节中解释的标识符语法。 类型名称通常也是标识符,但是有一些例外。 请注意,列列表用逗号分隔,并用括号括起来。
当然,前面的示例是人为设计的。 通常,您将为表和列命名,以传达它们存储的数据类型。 因此,让我们看一个更现实的例子:
```sql
CREATE TABLE products (
product_no integer,
name text,
price numeric
);
```
(数字类型可以存储小数部分,这是典型的货币金额。)创建许多相互关联的表时,明智的做法是为表和列选择一致的命名模式。 例如,可以选择使用单数形式或复数形式的名词作为表名,这两者都受到某些理论家或其他理论家的青睐。
一个表可以包含多少列是有限制的。 根据列的类型,它介于250到1600之间。但是,定义一个表中包含如此多列的位置的表是非常不寻常的,并且通常是一个有问题的设计。
在Postgres-XL中,每个表都可以在Datanode之间分布或复制。 通过分配表,每个查询都有可能由单个或小的Datanode子集处理,并且可以并行处理更多事务。 如果复制表,则只能从所有位置读取它们,尽管所有写操作都需要在所有节点上进行。
分布式表时,几乎可以选择基本数据类型的任何列作为分布式列。 有关详细信息,请参阅创建表。 如果使用了DISTRIBUTE BY HASH,则根据分发列的值确定特定行的Datanode。 如果未使用,则使用主键或唯一约束的第一列(如果存在于CREATE TABLE子句中)。 如果两者都不存在,则使用外键约束的第一列,这样的想法是子数据可以与父表并置在同一节点上。 如果未定义此类约束,则Postgres-XL将选择它可以找到的第一个合理列,即具有确定性可分配数据类型的第一列。 您还可以选择其他分发方法,例如MODULO和ROUNDROBIN。 要指定选择哪个列作为分布列以及选择哪个值测试,您可以执行以下操作:
```sql
CREATE TABLE products (
product_no integer,
name text,
price numeric
) DISTRIBUTE BY HASH(product_no);
```
在这种情况下,选择列product_no作为分发列,并根据该列的哈希值确定每行的目标Datanode。 您可以使用MODULO指定模数来测试和确定目标Datanode。 您还可以指定ROUNDROBIN来根据插入每一行的顺序确定Datanode。
请注意,使用HASH和MODULO,协调器可能能够从传入语句中确定目标行的确切位置。 这减少了涉及的Datanode的数量,并且可以增加并行处理的事务的数量。
另一方面,如果无法从传入的语句中获得确切的位置,则协调器将必须涉及在其上分布了所涉及表的所有Datanode。
要将表复制到所有Datanode中,请将DISTRIBUTE BY REPLICATION指定为:
```sql
CREATE TABLE products (
product_no integer,
name text,
price numeric
) DISTRIBUTE BY REPLICATION;
```
如果不再需要表,则可以使用DROP TABLE命令将其删除。 例如:
```sql
DROP TABLE my_first_table;
DROP TABLE products;
```
尝试删除不存在的表是一个错误。 但是,在SQL脚本文件中,通常会在创建表之前无条件地尝试删除它们,而忽略任何错误消息,以便脚本无论表是否存在都可以工作。 (如果愿意,可以使用DROP TABLE IF EXISTS变量来避免出现错误消息,但这不是标准的SQL。)
如果需要修改已经存在的表,请参阅本章后面的第5.5节。
使用到目前为止讨论的工具,您可以创建功能齐全的表。 本章的其余部分涉及为表定义添加功能以确保数据完整性,安全性或便利性。 如果您现在渴望用数据填充表格,则可以跳到第6章,稍后再阅读本章的其余部分。
#### 5.2 Default Values
可以为列分配默认值。 当创建新行并且未为某些列指定值时,这些列将使用其各自的默认值填充。 数据操作命令还可以显式请求将列设置为其默认值,而不必知道该值是什么。 (有关数据操作命令的详细信息,请参阅第6章。)
如果未明确声明默认值,则默认值为空值。 这通常很有意义,因为可以将空值视为代表未知数据。
在表定义中,默认值在列数据类型之后列出。 例如:
```sql
CREATE TABLE products (
product_no integer,
name text,
price numeric DEFAULT 9.99
);
```
默认值可以是一个表达式,只要插入默认值即可(而不是在创建表时)将对其求值。 一个常见的示例是,时间戳列的默认值为CURRENT_TIMESTAMP,以便将其设置为行插入的时间。 另一个常见的示例是为每一行生成一个“序列号”。 在PostgreSQL中,这通常通过以下方式完成:
```sql
CREATE TABLE products (
product_no integer DEFAULT nextval('products_product_no_seq'),
...
);
```
其中nextval()函数提供来自序列对象的连续值(请参见第9.16节)。 这种安排非常普遍,因此有一个特殊的缩写:
```sql
CREATE TABLE products (
product_no SERIAL,
...
);
```
SERIAL将在第8.1.4节中进一步讨论。