赞
踩
目录
前言
San Francisco
(1 row)
这样做是 OK 的,因为子查询是一次独立的计算,它独立于外层的查询计算出自己的聚集。
聚集同样也常用于和GROUP BY子句组合。比如,我们可以获取每个城市观测到的最低温度
的最高值:
SELECT city, max(temp_lo)
FROM weather
GROUP BY city;
city | max
---------------±----
Hayward | 37
San Francisco | 46
(2 rows)
这样给我们每个城市一个输出。每个聚集结果都是在匹配该城市的表行上面计算的。我们可
以用HAVING 过滤这些被分组的行:
SELECT city, max(temp_lo)
FROM weather
GROUP BY city
13
SQL语言
HAVING max(temp_lo) < 40;
city | max
---------±----
Hayward | 37
(1 row)
这样就只给出那些所有temp_lo值曾都低于 40的城市。最后,如果我们只关心那些名字
以“S”开头的城市,我们可以用:
SELECT city, max(temp_lo)
FROM weather
WHERE city LIKE ‘S%’ – 1
GROUP BY city
HAVING max(temp_lo) < 40;
1 LIKE操作符进行模式匹配,在第 9.7 节里有解释。
理解聚集和SQL的WHERE以及HAVING子句之间的关系对我们非常重要。WHERE和HAVING的基本
区别如下:WHERE在分组和聚集计算之前选取输入行(因此,它控制哪些行进入聚集计
算), 而HAVING在分组和聚集之后选取分组行。因此,WHERE子句不能包含聚集函
数; 因为试图用聚集函数判断哪些行应输入给聚集运算是没有意义的。相反,HAVING子句
总是包含聚集函数(严格说来,你可以写不使用聚集的HAVING子句, 但这样做很少有用。
同样的条件用在WHERE阶段会更有效)。
在前面的例子里,我们可以在WHERE里应用城市名称限制,因为它不需要聚集。这样比放
在HAVING里更加高效,因为可以避免那些未通过 WHERE检查的行参与到分组和聚集计算中。
2.8. 更新
你可以用UPDATE命令更新现有的行。假设你发现所有 11 月 28 日以后的温度读数都低了两
度,那么你就可以用下面的方式改正数据:
UPDATE weather
SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2
WHERE date > ‘1994-11-28’;
看看数据的新状态:
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)
2.9. 删除
数据行可以用DELETE命令从表中删除。假设你对Hayward的天气不再感兴趣,那么你可以用
下面的方法把那些行从表中删除:
14
SQL语言
DELETE FROM weather WHERE city = ‘Hayward’;
所有属于Hayward的天气记录都被删除。
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)
我们用下面形式的语句的时候一定要小心
DELETE FROM tablename;
如果没有一个限制,DELETE将从指定表中删除所有行,把它清空。做这些之前系统不会请求
你确认!
15
第 3 章 高级特性
3.1. 简介
在之前的章节里我们已经涉及了使用SQL在PostgreSQL中存储和访问数据的基础知识。现在
我们将要讨论SQL中一些更高级的特性,这些特性有助于简化管理和防止数据丢失或损坏。
最后,我们还将介绍一些PostgreSQL扩展。
本章有时将引用第 2 章中的例子并对其进行改变或改进以便于阅读本章。本章中的某些例子
可以在教程目录的advanced.sql文件中找到。该文件也包含一些样例数据,在这里就不在赘
述(查看第 2.1 节了解如何使用该文件)。
3.2. 视图
回想一下第 2.6 节中的查询。假设天气记录和城市位置的组合列表对我们的应用有用,但我
们又不想每次需要使用它时都敲入整个查询。我们可以在该查询上创建一个视图,这会给该
查询一个名字,我们可以像使用一个普通表一样来使用它:
CREATE VIEW myview AS
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather, cities
WHERE city = name;
SELECT * FROM myview;
对视图的使用是成就一个好的SQL数据库设计的关键方面。视图允许用户通过始终如一的接
口封装表的结构细节,这样可以避免表结构随着应用的进化而改变。
视图几乎可以用在任何可以使用表的地方。在其他视图基础上创建视图也并不少见。
3.3. 外键
回想第2章中的weather和cities表。考虑以下问题:我们希望确保在cities表中有相应项之
前任何人都不能在weather表中插入行。这叫做维持数据的引用完整性。在过分简化的数据
库系统中,可以通过先检查cities表中是否有匹配的记录存在,然后决定应该接受还是拒绝
即将插入weather表的行。这种方法有一些问题且并不方便,于是PostgreSQL可以为我们来
解决:
新的表定义如下:
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
);
现在尝试插入一个非法的记录:
16
高级特性
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 章中的信息。正确使用外键无疑会提高数据库应用的质量,因此强烈建议用户学会如
何使用它们。
3.4. 事务
事务是所有数据库系统的基础概念。事务最重要的一点是它将多个步骤捆绑成了一个单一
的、要么全完成要么全不完成的操作。步骤之间的中间状态对于其他并发事务是不可见的,
并且如果有某些错误发生导致事务不能完成,则其中任何一个步骤都不会对数据库造成影
响。
例如,考虑一个保存着多个客户账户余额和支行总存款额的银行数据库。假设我们希望记录
一笔从Alice的账户到Bob的账户的额度为100.00美元的转账。在最大程度地简化后,涉及到
的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收到100美元而Alice并未被扣款的情况。Alice当然也不希望自己被扣款
而Bob没有收到钱。我们需要一种保障,当操作中途某些错误发生时已经执行的步骤不会产
生效果。将这些更新组织成一个事务就可以给我们这种保障。一个事务被称为是原子的:从
其他事务的角度来看,它要么整个发生要么完全不发生。
我们同样希望能保证一旦一个事务被数据库系统完成并认可,它就被永久地记录下来且即便
其后发生崩溃也不会被丢失。例如,如果我们正在记录Bob的一次现金提款,我们当然不希
望他刚走出银行大门,对他账户的扣款就消失。一个事务型数据库保证一个事务在被报告为
完成之前它所做的所有更新都被记录在持久存储(即磁盘)。
事务型数据库的另一个重要性质与原子更新的概念紧密相关:当多个事务并发运行时,每一
个都不能看到其他事务未完成的修改。例如,如果一个事务正忙着总计所有支行的余额,它
不会只包括Alice的支行的扣款而不包括Bob的支行的存款,或者反之。所以事务的全做或全
不做并不只体现在它们对数据库的持久影响,也体现在它们发生时的可见性。一个事务所做
的更新在它完成之前对于其他事务是不可见的,而之后所有的更新将同时变得可见。
在PostgreSQL中,开启一个事务需要将SQL命令用BEGIN和COMMIT命令包围起来。因此我们的
银行事务看起来会是这样:
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = ‘Alice’;
– etc etc
17
高级特性
COMMIT;
如果,在事务执行中我们并不想提交(或许是我们注意到Alice的余额不足),我们可以发
出ROLLBACK命令而不是COMMIT命令,这样所有目前的更新将会被取消。
PostgreSQL实际上将每一个SQL语句都作为一个事务来执行。如果我们没有发出BEGIN命令,
则每个独立的语句都会被加上一个隐式的BEGIN以及(如果成功)COMMIT来包围它。一组
被BEGIN和COMMIT包围的语句也被称为一个事务块。
注意
某些客户端库会自动发出BEGIN和COMMIT命令,因此我们可能会在不被告知的
情况下得到事务块的效果。具体请查看所使用的接口文档。
也可以利用保存点来以更细的粒度来控制一个事务中的语句。保存点允许我们有选择性地放
弃事务的一部分而提交剩下的部分。在使用SAVEPOINT定义一个保存点后,我们可以在必要
时利用ROLLBACK TO回滚到该保存点。该事务中位于保存点和回滚点之间的数据库修改都会
被放弃,但是早于该保存点的修改则会被保存。
在回滚到保存点之后,它的定义依然存在,因此我们可以多次回滚到它。反过来,如果确定
不再需要回滚到特定的保存点,它可以被释放以便系统释放一些资源。记住不管是释放保存
点还是回滚到保存点都会释放定义在该保存点之后的所有其他保存点。
所有这些都发生在一个事务块内,因此这些对于其他数据库会话都不可见。当提交整个事务
块时,被提交的动作将作为一个单元变得对其他会话可见,而被回滚的动作则永远不会变得
可见。
记住那个银行数据库,假设我们从Alice的账户扣款100美元,然后存款到Bob的账户,结果
直到最后才发现我们应该存到Wally的账户。我们可以通过使用保存点来做这件事:
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是唯一的途径来重新控制一个由于错误被系统置为中断状态的事务块,
而不是完全回滚它并重新启动。
3.5. 窗口函数
一个窗口函数在一系列与当前行有某种关联的表行上执行一种计算。这与一个聚集函数所完
成的计算有可比之处。但是窗口函数并不会使多行被聚集成一个单独的输出行,这与通常的
非窗口聚集函数不同。取而代之,行保留它们独立的标识。在这些现象背后,窗口函数可以
访问的不仅仅是查询结果的当前行。
下面是一个例子用于展示如何将每一个员工的薪水与他/她所在部门的平均薪水进行比较:
18
高级特性
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)
最开始的三个输出列直接来自于表empsalary,并且表中每一行都有一个输出行。第四列表
示对与当前行具有相同depname值的所有表行取得平均值(这实际和非窗口avg聚集函数是相
同的函数,但是OVER子句使得它被当做一个窗口函数处理并在一个合适的窗口帧上计
算。)。
一个窗口函数调用总是包含一个直接跟在窗口函数名及其参数之后的OVER子句。这使得它从
句法上和一个普通函数或非窗口函数区分开来。OVER子句决定究竟查询中的哪些行被分离出
来由窗口函数处理。OVER子句中的PARTITION BY子句指定了将具有相同PARTITION BY表达式
值的行分到组或者分区。对于每一行,窗口函数都会在当前行同一分区的行上进行计算。
我们可以通过OVER上的ORDER BY控制窗口函数处理行的顺序(窗口的ORDER BY并不一定要符
合行输出的顺序。)。下面是一个例子:
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值产生了一个数字等级。rank不需要显式的参数,因为它的行为完全决定于OVER子句。
一个窗口函数所考虑的行属于那些通过查询的FROM子句产生并通过WHERE、GROUP
BY、HAVING过滤的“虚拟表”。例如,一个由于不满足WHERE条件被删除的行是不会被任何
窗口函数所见的。在一个查询中可以包含多个窗口函数,每个窗口函数都可以用不同
的OVER子句来按不同方式划分数据,但是它们都作用在由虚拟表定义的同一个行集上。
我们已经看到如果行的顺序不重要时ORDER BY可以忽略。PARTITION BY同样也可以被忽略,
在这种情况下会产生一个包含所有行的分区。
19
高级特性
这里有一个与窗口函数相关的重要概念:对于每一行,在它的分区中的行集被称为它的窗口
帧。 一些窗口函数只作用在窗口帧中的行上,而不是整个分区。默认情况下,如果使
用ORDER BY,则帧包括从分区开始到当前行的所有行,以及后续任何与当前行在ORDER BY子
句上相等的行。如果ORDER BY被忽略,则默认帧包含整个分区中所有的行。 1
下面是使
用sum的例子:
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,窗口帧和分区一样,而如果缺少PARTITION
BY则和整个表一样。换句话说,每个合计都会在整个表上进行,这样我们为每一个输出行得
到的都是相同的结果。但是如果我们加上一个ORDER BY子句,我们会得到非常不同的结果:
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子句中。这是因为窗口函数的执行逻辑是在处理完这些子句
之后。另外,窗口函数在非窗口聚集函数之后执行。这意味着可以在窗口函数的参数中包括
一个聚集函数,但反过来不行。
如果需要在窗口计算执行后进行过滤或者分组,我们可以使用子查询。例如:
SELECT depname, empno, salary, enroll_date
1
还有些选项用于以其他方式定义窗口帧,但是这不包括在本教程内。详见第 4.2.8 节。
20
高级特性
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;
上述查询仅仅显示了内层查询中rank低于3的结果。
当一个查询涉及到多个窗口函数时,可以将每一个分别写在一个独立的OVER子句中。但如果
多个函数要求同一个窗口行为时,这种做法是冗余的而且容易出错的。替代方案是,每一个
窗口行为可以被放在一个命名的WINDOW子句中,然后在OVER中引用它。例如:
SELECT sum(salary) OVER w, avg(salary) OVER w
FROM empsalary
WINDOW w AS (PARTITION BY depname ORDER BY salary DESC);
关于窗口函数的更多细节可以在第 4.2.8 节、第 9.21 节、第 7.2.5 节以及SELECT参考页
中找到。
3.6. 继承
继承是面向对象数据库中的概念。它展示了数据库设计的新的可能性。
让我们创建两个表:表cities和表capitals。自然地,首都也是城市,所以我们需要有某种
方式能够在列举所有城市的时候也隐式地包含首都。如果真的聪明,我们会设计如下的模
式:
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;
这个模式对于查询而言工作正常,但是当我们需要更新一些行时它就变得不好用了。
更好的方案是:
CREATE TABLE cities (
name text,
population real,
altitude int – (in ft)
);
21
高级特性
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);
在这种情况下,一个capitals的行从它的父亲cities继承了所有列
(name、population和altitude)。列name的类型是text,一种用于变长字符串的本地
PostgreSQL类型。州首都有一个附加列state用于显示它们的州。在PostgreSQL中,一个表
可以从0个或者多个表继承。
例如,如下查询可以寻找所有海拔500尺以上的城市名称,包括州首都:
SELECT name, altitude
FROM cities
WHERE altitude > 500;
它的返回为:
name | altitude
-----------±---------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
(3 rows)
在另一方面,下面的查询可以查找所有海拔高于500尺且不是州首府的城市:
SELECT name, altitude
FROM ONLY cities
WHERE altitude > 500;
name | altitude
-----------±---------
Las Vegas | 2174
Mariposa | 1953
(2 rows)
其中cities之前的ONLY用于指示查询只在cities表上进行而不会涉及到继承层次中位
于cities之下的其他表。很多我们已经讨论过的命令 — SELECT、UPDATE 和DELETE — 都支
持这个ONLY记号。
注意
尽管继承很有用,但是它还未与唯一约束或外键集成,这也限制了它的可用
性。更多详情见第 5.10 节。
3.7. 小结
PostgreSQL中有很多特性在这个面向SQL新用户的教程中并未触及。有关这些特性的更多详
情将在本书的后续部分进行讨论。
如果需要更多介绍材料,请访问 PostgreSQL 官方网站2来获得更多资源链接。
2
https://www.postgresql.org
22
部分 II. SQL 语言
这部份描述在PostgreSQL中SQL语言的使用。我们从描述SQL的一般语法开始,然后解释如何创建保存数
据的结构、如何填充数据库以及如何查询它。中间的部分列出了在SQL命令中可用的数据类型和函数。
剩余的部分则留给对于调优数据性能的重要方面。
这部份的信息被组织成让一个新用户可以从头到尾跟随它来全面理解主题,而不需要多次参考后面的内
容。这些章都是自包含的,这样高级用户可以根据他们的选择阅读单独的章。这一部分的信息被以一种
叙事的风格展现。需要查看一个特定命令的完整描述的读者应该去看看第 VI 部分。
这一部分的阅读者应该知道如何连接到一个PostgreSQL数据库并且发出SQL命令。我们鼓励不熟悉这些
问题的读者先去阅读第 I 部分。SQL通常使用PostgreSQL的交互式终端psql输入,但是其他具有相似功
能的程序也可以被使用。
目录
4. SQL语法 … 31
4.1. 词法结构 … 31
4.1.1. 标识符和关键词 … 31
4.1.2. 常量 … 33
4.1.3. 操作符 … 37
4.1.4. 特殊字符 … 37
4.1.5. 注释 … 38
4.1.6. 操作符优先级 … 38
4.2. 值表达式 … 39
4.2.1. 列引用 … 40
4.2.2. 位置参数 … 40
4.2.3. 下标 … 40
4.2.4. 域选择 … 41
4.2.5. 操作符调用 … 41
4.2.6. 函数调用 … 42
4.2.7. 聚集表达式 … 42
4.2.8. 窗口函数调用 … 44
4.2.9. 类型转换 … 46
4.2.10. 排序规则表达式 … 47
4.2.11. 标量子查询 … 48
4.2.12. 数组构造器 … 48
4.2.13. 行构造器 … 49
4.2.14. 表达式计算规则 … 51
4.3. 调用函数 … 52
4.3.1. 使用位置记号 … 52
4.3.2. 使用命名记号 … 53
4.3.3. 使用混合记号 … 53
5. 数据定义 … 55
5.1. 表基础 … 55
5.2. 默认值 … 56
5.3. 生成列 … 57
5.4. 约束 … 57
5.4.1. 检查约束 … 58
5.4.2. 非空约束 … 60
5.4.3. 唯一约束 … 60
5.4.4. 主键 … 61
5.4.5. 外键 … 62
5.4.6. 排他约束 … 64
5.5. 系统列 … 65
5.6. 修改表 … 65
5.6.1. 增加列 … 66
5.6.2. 移除列 … 66
5.6.3. 增加约束 … 67
5.6.4. 移除约束 … 67
5.6.5. 更改列的默认值 … 67
5.6.6. 修改列的数据类型 … 67
5.6.7. 重命名列 … 68
5.6.8. 重命名表 … 68
5.7. 权限 … 68
5.8. 行安全性策略 … 72
5.9. 模式 … 77
5.9.1. 创建模式 … 78
5.9.2. 公共模式 … 79
5.9.3. 模式搜索路径 … 79
5.9.4. 模式和权限 … 80
24
SQL 语言
5.9.5. 系统目录模式 … 81
5.9.6. 使用模式 … 81
5.9.7. 可移植性 … 81
5.10. 继承 … 82
5.10.1. 警告 … 84
5.11. 表分区 … 85
5.11.1. 概述 … 85
5.11.2. 声明式划分 … 86
5.11.3. 使用继承实现 … 89
5.11.4. 分区剪枝 … 93
5.11.5. 分区和约束排除 … 95
5.11.6. 声明分区最佳实践 … 95
5.12. 外部数据 … 96
5.13. 其他数据库对象 … 96
5.14. 依赖跟踪 … 97
6. 数据操纵 … 99
6.1. 插入数据 … 99
6.2. 更新数据 … 100
6.3. 删除数据 … 101
6.4. 从修改的行中返回数据 … 101
7. 查询 … 103
7.1. 概述 … 103
7.2. 表表达式 … 103
7.2.1. FROM子句 … 103
7.2.2. WHERE子句 … 111
7.2.3. GROUP BY和HAVING子句 … 112
7.2.4. GROUPING SETS、CUBE和ROLLUP … 114
7.2.5. 窗口函数处理 … 117
7.3. 选择列表 … 117
7.3.1. 选择列表项 … 117
7.3.2. 列标签 … 117
7.3.3. DISTINCT … 118
7.4. 组合查询 … 118
7.5. 行排序 … 119
7.6. LIMIT和OFFSET … 120
7.7. VALUES列表 … 120
7.8. WITH查询(公共表表达式) … 121
7.8.1. WITH中的SELECT … 121
7.8.2. WITH中的数据修改语句 … 125
8. 数据类型 … 128
8.1. 数字类型 … 129
8.1.1. 整数类型 … 130
8.1.2. 任意精度数字 … 130
8.1.3. 浮点类型 … 131
8.1.4. 序数类型 … 133
8.2. 货币类型 … 134
8.3. 字符类型 … 134
8.4. 二进制数据类型 … 136
8.4.1. bytea的十六进制格式 … 136
8.4.2. bytea的转义格式 … 137
8.5. 日期/时间类型 … 138
8.5.1. 日期/时间输入 … 139
8.5.2. 日期/时间输出 … 142
8.5.3. 时区 … 143
8.5.4. 间隔输入 … 144
8.5.5. 间隔输出 … 146
8.6. 布尔类型 … 146
8.7. 枚举类型 … 147
25
SQL 语言
8.7.1. 枚举类型的声明 … 147
8.7.2. 排序 … 148
8.7.3. 类型安全性 … 148
8.7.4. 实现细节 … 149
8.8. 几何类型 … 149
8.8.1. 点 … 149
8.8.2. 线 … 150
8.8.3. 线段 … 150
8.8.4. 方框 … 150
8.8.5. 路径 … 150
8.8.6. 多边形 … 151
8.8.7. 圆 … 151
8.9. 网络地址类型 … 151
8.9.1. inet … 152
8.9.2. cidr … 152
8.9.3. inet vs. cidr … 153
8.9.4. macaddr … 153
8.9.5. macaddr8 … 153
8.10. 位串类型 … 154
8.11. 文本搜索类型 … 154
8.11.1. tsvector … 155
8.11.2. tsquery … 156
8.12. UUID类型 … 157
8.13. XML类型 … 158
8.13.1. 创建XML值 … 158
8.13.2. 编码处理 … 159
8.13.3. 访问XML值 … 159
8.14. JSON 类型 … 159
8.14.1. JSON 输入和输出语法 … 161
8.14.2. 设计 JSON 文档 … 162
8.14.3. jsonb 包含和存在 … 162
8.14.4. jsonb 索引 … 163
8.14.5. 转换 … 166
8.14.6. jsonpath Type … 166
8.15. 数组 … 167
8.15.1. 数组类型的定义 … 167
8.15.2. 数组值输入 … 168
8.15.3. 访问数组 … 169
8.15.4. 修改数组 … 171
8.15.5. 在数组中搜索 … 174
8.15.6. 数组输入和输出语法 … 175
8.16. 组合类型 … 176
8.16.1. 组合类型的声明 … 176
8.16.2. 构造组合值 … 177
8.16.3. 访问组合类型 … 178
8.16.4. 修改组合类型 … 178
8.16.5. 在查询中使用组合类型 … 179
8.16.6. 组合类型输入和输出语法 … 181
8.17. 范围类型 … 182
8.17.1. 内建范围类型 … 182
8.17.2. 例子 … 182
8.17.3. 包含和排除边界 … 183
8.17.4. 无限(无界)范围 … 183
8.17.5. 范围输入/输出 … 183
8.17.6. 构造范围 … 184
8.17.7. 离散范围类型 … 184
8.17.8. 定义新的范围类型 … 185
8.17.9. 索引 … 186
26
SQL 语言
8.17.10. 范围上的约束 … 186
8.18. 域类型 … 187
8.19. 对象标识符类型 … 187
8.20. pg_lsn 类型 … 189
8.21. 伪类型 … 189
9. 函数和操作符 … 191
9.1. 逻辑操作符 … 191
9.2. 比较函数和操作符 … 191
9.3. 数学函数和操作符 … 194
9.4. 字符串函数和操作符 … 197
9.4.1. format … 208
9.5. 二进制串函数和操作符 … 209
9.6. 位串函数和操作符 … 211
9.7. 模式匹配 … 212
9.7.1. LIKE … 213
9.7.2. SIMILAR TO正则表达式 … 213
9.7.3. POSIX正则表达式 … 215
9.8. 数据类型格式化函数 … 227
9.9. 时间/日期函数和操作符 … 234
9.9.1. EXTRACT, date_part … 239
9.9.2. date_trunc … 243
9.9.3. AT TIME ZONE … 244
9.9.4. 当前日期/时间 … 245
9.9.5. 延时执行 … 246
9.10. 枚举支持函数 … 247
9.11. 几何函数和操作符 … 247
9.12. 网络地址函数和操作符 … 251
9.13. 文本搜索函数和操作符 … 253
9.14. XML 函数 … 259
9.14.1. 产生 XML 内容 … 259
9.14.2. XML 谓词 … 263
9.14.3. 处理 XML … 264
9.14.4. 将表映射到 XML … 268
9.15. JSON 函数和操作符 … 271
9.15.1. 处理和创建JSON数据 … 272
9.15.2. SQL/JSON路径语言 … 280
9.16. 序列操作函数 … 285
9.17. 条件表达式 … 288
9.17.1. CASE … 288
9.17.2. COALESCE … 289
9.17.3. NULLIF … 290
9.17.4. GREATEST和LEAST … 290
9.18. 数组函数和操作符 … 290
9.19. 范围函数和操作符 … 293
9.20. 聚集函数 … 294
9.21. 窗口函数 … 300
9.22. 子查询表达式 … 302
9.22.1. EXISTS … 302
9.22.2. IN … 302
9.22.3. NOT IN … 303
9.22.4. ANY/SOME … 303
9.22.5. ALL … 304
9.22.6. 单一行比较 … 304
9.23. 行和数组比较 … 304
9.23.1. IN … 305
9.23.2. NOT IN … 305
9.23.3. ANY/SOME (array) … 305
9.23.4. ALL (array) … 306
27
SQL 语言
9.23.5. 行构造器比较 … 306
9.23.6. 组合类型比较 … 307
9.24. 集合返回函数 … 307
9.25. 系统信息函数和运算符 … 310
9.26. 系统管理函数 … 325
9.26.1. 配置设定函数 … 325
9.26.2. 服务器信号函数 … 326
9.26.3. 备份控制函数 … 326
9.26.4. 恢复控制函数 … 329
9.26.5. 快照同步函数 … 330
9.26.6. 复制函数 … 331
9.26.7. 数据库对象管理函数 … 334
9.26.8. 索引维护函数 … 337
9.26.9. 通用文件访问函数 … 337
9.26.10. 咨询锁函数 … 339
9.27. 触发器函数 … 341
9.28. 事件触发器函数 … 342
9.28.1. 在命令结束处捕捉更改 … 342
9.28.2. 处理被 DDL 命令删除的对象 … 342
9.28.3. 处理表重写事件 … 343
9.29. Statistics Information Functions … 344
9.29.1. 检查MCV列表 … 344
10. 类型转换 … 345
10.1. 概述 … 345
10.2. 操作符 … 346
10.3. 函数 … 349
10.4. 值存储 … 353
10.5. UNION、CASE和相关结构 … 354
10.6. SELECT的输出列 … 355
11. 索引 … 357
11.1. 简介 … 357
11.2. 索引类型 … 358
11.3. 多列索引 … 359
11.4. 索引和ORDER BY … 360
11.5. 组合多个索引 … 361
11.6. 唯一索引 … 362
11.7. 表达式索引 … 362
11.8. 部分索引 … 363
11.9. 只用索引的扫描和覆盖索引 … 365
11.10. 操作符类和操作符族 … 367
11.11. 索引和排序规则 … 368
11.12. 检查索引使用 … 369
12. 全文搜索 … 371
12.1. 介绍 … 371
12.1.1. 什么是一个文档? … 371
12.1.2. 基本文本匹配 … 372
12.1.3. 配置 … 374
12.2. 表和索引 … 374
12.2.1. 搜索一个表 … 374
12.2.2. 创建索引 … 375
12.3. 空值文本搜索 … 376
12.3.1. 解析文档 … 376
12.3.2. 解析查询 … 377
12.3.3. 排名搜索结果 … 380
12.3.4. 加亮结果 … 381
12.4. 额外特性 … 382
12.4.1. 操纵文档 … 383
12.4.2. 操纵查询 … 383
28
SQL 语言
12.4.3. 用于自动更新的触发器 … 385
12.4.4. 收集文档统计数据 … 387
12.5. 解析器 … 387
12.6. 词典 … 389
12.6.1. 停用词 … 390
12.6.2. 简单词典 … 390
12.6.3. 同义词词典 … 392
12.6.4. 分类词典 … 393
12.6.5. Ispell 词典 … 395
12.6.6. Snowball 词典 … 397
12.7. 配置例子 … 398
12.8. 测试和调试文本搜索 … 399
12.8.1. 配置测试 … 399
12.8.2. 解析器测试 … 401
12.8.3. 词典测试 … 402
12.9. GIN 和 GiST 索引类型 … 403
12.10. psql支持 … 404
12.11. 限制 … 406
13. 并发控制 … 408
13.1. 介绍 … 408
13.2. 事务隔离 … 408
13.2.1. 读已提交隔离级别 … 409
13.2.2. 可重复读隔离级别 … 410
13.2.3. 可序列化隔离级别 … 411
13.3. 显式锁定 … 413
13.3.1. 表级锁 … 413
13.3.2. 行级锁 … 415
13.3.3. 页级锁 … 416
13.3.4. 死锁 … 416
13.3.5. 咨询锁 … 417
13.4. 应用级别的数据完整性检查 … 417
13.4.1. 用可序列化事务来强制一致性 … 418
13.4.2. 使用显式锁定强制一致性 … 418
13.5. 提醒 … 419
13.6. 锁定和索引 … 419
14. 性能提示 … 420
14.1. 使用EXPLAIN … 420
14.1.1. EXPLAIN基础 … 420
14.1.2. EXPLAIN ANALYZE … 425
14.1.3. 警告 … 429
14.2. 规划器使用的统计信息 … 430
14.2.1. 单列统计信息 … 430
14.2.2. 扩展统计信息 … 431
14.3. 用显式JOIN子句控制规划器 … 434
14.4. 填充一个数据库 … 436
14.4.1. 禁用自动提交 … 436
14.4.2. 使用COPY … 436
14.4.3. 移除索引 … 436
14.4.4. 移除外键约束 … 436
14.4.5. 增加maintenance_work_mem … 437
14.4.6. 增加max_wal_size … 437
14.4.7. 禁用 WAL 归档和流复制 … 437
14.4.8. 事后运行ANALYZE … 437
14.4.9. 关于pg_dump的一些注记 … 437
14.5. 非持久设置 … 438
15. 并行查询 … 439
15.1. 并行查询如何工作 … 439
15.2. 何时会用到并行查询? … 440
29
SQL 语言
15.3. 并行计划 … 440
15.3.1. 并行扫描 … 440
15.3.2. 并行连接 … 441
15.3.3. 并行聚集 … 441
15.3.4. 并行Append … 441
15.3.5. 并行计划小贴士 … 442
15.4. 并行安全性 … 442
15.4.1. 为函数和聚集加并行标签 … 442
30
第 4 章 SQL语法
这一章描述了SQL的语法。它构成了理解后续具体介绍如何使用SQL定义和修改数据的章节的
基础 。
我们同时建议已经熟悉SQL的用户仔细阅读本章,因为本章包含一些在SQL数据库中实现得不
一致的以及PostgreSQL中特有的规则和概念。
4.1. 词法结构
SQL输入由一个命令序列组成。一个命令由一个记号的序列构成,并由一个分号(“;”)终
结。输入流的末端也会标志一个命令的结束。具体哪些记号是合法的与具体命令的语法有
关。
一个记号可以是一个关键词、一个标识符、一个带引号的标识符、一个literal(或常量)
或者一个特殊字符符号。记号通常以空白(空格、制表符、新行)来分隔,但在无歧义时并
不强制要求如此(唯一的例子是一个特殊字符紧挨着其他记号)。
例如,下面是一个(语法上)合法的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来完成命令。每个命令的精确语法规则在第 VI 部分中介绍。
4.1.1. 标识符和关键词
上例中的SELECT、UPDATE或VALUES记号是关键词的例子,即SQL语言中具有特定意义的词。
记号MY_TABLE和A则是标识符的例子。它们标识表、列或者其他数据库对象的名字,取决于
使用它们的命令。因此它们有时也被简称为“名字”。关键词和标识符具有相同的词法结
构,这意味着我们无法在没有语言知识的前提下区分一个标识符和关键词。一个关键词的完
整列表可以在附录 C中找到。
SQL标识符和关键词必须以一个字母(a-z,也可以是带变音符的字母和非拉丁字母)或一个
下划线()开始。后续字符可以是字母、下划线()、数字(0-9)或美元符号(KaTeX parse error: Expected 'EOF', got '&' at position 778: …nicode字符。这种变体以U&̲(大写 或小写U跟上一个花号)…)、一个可选的另个或更多字符的“标签”、另一个美元
符号、一个构成字符串内容的任意字符序列、一个美元符号、开始这个美元引用的相同标签
和一个美元符号组成。例如,这里有两种不同的方法使用美元引用指定字符串“Dianne’s
horse”:
D
i
a
n
n
e
′
s
h
o
r
s
e
Dianne's horse
Dianne′shorse
S
o
m
e
T
a
g
SomeTag
SomeTagDianne’s horse
S
o
m
e
T
a
g
SomeTag
SomeTag
注意在美元引用字符串中,单引号可以在不被转义的情况下使用。事实上,在一个美元引用
字符串中不需要对字符进行转义:字符串内容总是按其字面意思写出。反斜线不是特殊的,
并且美元符号也不是特殊的,除非它们是匹配开标签的一个序列的一部分。
可以通过在每一个嵌套级别上选择不同的标签来嵌套美元引用字符串常量。这最常被用在编
写函数定义上。例如:
f
u
n
c
t
i
o
n
function
function
BEGIN
RETURN ($1 ~
q
q
q[\t\r\n\v\]
q
q
q);
END;
f
u
n
c
t
i
o
n
function
function
这里,序列
q
q
q[\t\r\n\v\]
q
q
q表示一个美元引用的文字串[\t\r\n\v\],当该函数体被
PostgreSQL执行时它将被识别。但是因为该序列不匹配外层的美元引用的定界符$function
,它只是一些在外层字符串所关注的常量中的字符而已。一个美元引用字符串的标签(如果有)遵循一个未被引用标识符的相同规则,除了它不能包含一个美元符号之外。标签是大小写敏感的,因此
,它只是一些在外层字符串所关注的常量中的字符而已。 一个美元引用字符串的标签(如果有)遵循一个未被引用标识符的相同规则,除了它不能包 含一个美元符号之外。标签是大小写敏感的,因此
,它只是一些在外层字符串所关注的常量中的字符而已。一个美元引用字符串的标签(如果有)遵循一个未被引用标识符的相同规则,除了它不能包含一个美元符号之外。标签是大小写敏感的,因此tag
S
t
r
i
n
g
c
o
n
t
e
n
t
String content
Stringcontenttag
是正确的,但是
是正确的,但 是
是正确的,但是TAG
S
t
r
i
n
g
c
o
n
t
e
n
t
String content
Stringcontenttag$不正确。
一个跟着一个关键词或标识符的美元引用字符串必须用空白与之分隔开,否则美元引用定界
符可能会被作为前面标识符的一部分。
美元引用不是 SQL 标准的一部分,但是在书写复杂字符串文字方面,它常常是一种比兼容标
准的单引号语法更方便的方法。当要表示的字符串常量位于其他常量中时它特别有用,这种
情况常常在过程函数定义中出现。如果用单引号语法,上一个例子中的每个反斜线将必须被
写成四个反斜线,这在解析原始字符串常量时会被缩减到两个反斜线,并且接着在函数执行
期间重新解析内层字符串常量时变成一个。
4.1.2.5. 位串常量
位串常量看起来像常规字符串常量在开引号之前(中间无空白)加了一个B(大写或小写形
式),例如B’1001’。位串常量中允许的字符只有0和1。
作为一种选择,位串常量可以用十六进制记号法指定,使用一个前导X(大写或小写形式),
例如X’1FF’。这种记号法等价于一个用四个二进制位取代每个十六进制位的位串常量。
两种形式的位串常量可以以常规字符串常量相同的方式跨行继续。美元引用不能被用在位串
常量中。
35
SQL语法
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’ )
36
SQL语法
但是并非所有类型名都可以用在这种方法中,详见第 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)的一个字符序列,其中的字符来自下面的
列表:
? 不过,在操作符名上有一些限制: • -- and /*不能在一个操作符名的任何地方出现,因为它们将被作为一段注释的开始。 • 一个多字符操作符名不能以+或-结尾,除非该名称也至少包含这些字符中的一个: ~ ! @ # % ^ & |
?HELLO WORLD
(1 row)
在上述查询中,参数a和b被以位置指定,而uppercase通过名字指定。在这个例子中,这只
增加了一点文档。在一个具有大量带默认值参数的复杂函数中,命名的或混合的记号法可以
节省大量的书写并且减少出错的机会。
53
SQL语法
注意
命名的和混合的调用记号法当前不能在调用聚集函数时使用(但是当聚集函数
被用作窗口函数时它们可以被使用)。
54
第 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类型能够存储小数部分,典型的例子是金额。)
55
数据定义
提示
当我们创建很多相关的表时,最好为表和列选择一致的命名模式。例如,一种
选择是用单数或复数名词作为表名,每一种都受到一些理论家支持。
一个表能够拥有的列的数据是有限的,根据列的类型,这个限制介于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.16 节)。还有一种特别的速写:
CREATE TABLE products (
56
数据定义
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 。
生成列不能被直接写入. 在INSERT 或 UPDATE 命令中, 不能为生成列指定值, 但是可以指
定关键字DEFAULT。
考虑列缺省情况和生成列之间的差异。 如果没有提供其他值,列缺省情况下在行被首次
插入时计算一次;生成列则在行每次改变时进行更新,并且不能被取代。 列缺省情况下不
能引用表的其他列;生成表达式通常会这样做。 列缺省情况下可以使用易失性函数,例
如random()或引用当前时间函数; 而对于生成列这是不允许的。
生成列和涉及生成列的表的定义有几个限制:
• 生成表达式只能使用不可变函数,并且不能使用子查询或以任何方式引用当前行以外的任
何内容。
• 生成表达式不能引用另一个生成列。
• 生成表达式不能引用系统表,除了 tableoid。
• 生成列不能具有列默认或标识定义。
• 生成列不能是分区键的一部分。
• 外部表可以有生成列. 更多细节请参见 CREATE FOREIGN TABLE .
使用生成列的其他注意事项。
• 生成列保留着有别于其下层的基础列的访问权限。因此,可以对其进行排列以便于从生成
列中读取特定的角色,而不是从下层基础列。
• 从概念上讲,生成列在BEFORE 触发器运行后更新。 因此,BEFORE 触发器中的基础列所做
的变更将反映在生成列中。 但相反,不允许访问BEFORE 触发器中的生成列。
5.4. 约束
数据类型是一种限制能够存储在表中数据类别的方法。但是对于很多应用来说,它们提供的
约束太粗糙。例如,一个包含产品价格的列应该只接受正值。但是没有任何一种标准数据类
57
数据定义
型只接受正值。另一个问题是我们可能需要根据其他列或行来约束一个列中的数据。例如,
在一个包含产品信息的表中,对于每个产品编号应该只有一行。
到目前为止,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,
58
数据定义
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)
);
需要注意的是,一个检查约束在其检查表达式值为真或空值时被满足。因为当任何操作数为
空时大部分表达式将计算为空值,所以它们不会阻止被约束列中的控制。为了保证一个列不
包含控制,可以使用下一节中的非空约束。
注意
PostgreSQL不支持引用表数据以外的要检查的新增或更新的行的CHECK约束。
虽然违反此规则的CHECK约束在简单测试中看起来能工作,它不能保证数据库
不会达到约束条件为假(false)的状态(由于涉及的其他行随后发生了更
改)。 这将导致数据库转储和重新加载失败。 即使完整的数据库状态与约束
一致,重新加载也可能失败,因为行未按照满足约束的顺序加载。 如果可能
的话,使用UNIQUE, EXCLUDE,或 FOREIGN KEY约束以表示跨行和跨表限制。
如果你希望的是在插入行时的时候对其他行进行一次性检查,而不是持续维护
的一致性保证,一个自定义的 trigger 可以用于实现这个功
能。 (此方法避免了转储/重新加载问题,因为pg_dump不会重新安装触发器
直到重新加载数据之后,因此不会在转储/重新加载期间强制执行检查。)
注意
PostgreSQL假定CHECK约束的条件是不可变的,也就是说,它们始终为同一输
入行提供相同的结果。 这个假设是仅在插入或更新行时,而不是在其他时间检
59
数据定义
查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关键词。
提示
在大部分数据库中多数列应该被标记为非空。
5.4.3. 唯一约束
60
数据定义
唯一约束保证\在一列中或者一组列中保存的数据在表中所有行间是唯一的。写成一个列约
束的语法是:
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-tree索引。只覆盖某些
行的唯一性限制不能被写为一个唯一约束,但可以通过创建一个唯一的部分索引来强制这种
限制。
通常,如果表中有超过一行在约束所包括列上的值相同,将会违反唯一约束。但是在这种比
较中,两个空值被认为是不同的。这意味着即便存在一个唯一约束,也可以存储多个在至少
一个被约束列中包含空值的行。这种行为符合SQL标准,但我们听说一些其他SQL数据库可能
不遵循这个规则。所以在开发需要可移植的应用时应注意这一点。
5.4.4. 主键
一个主键约束表示可以用作表中行的唯一标识符的一个列或者一组列。这要求那些值都是唯
一的并且非空。因此,下面的两个表定义接受相同的数据:
61
数据定义
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-tree索引。并且会强制这些
列被标记为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
);
现在就不可能创建包含不存在于产品表中的product_no值(非空)的订单。
62
数据定义
我们说在这种情况下,订单表是引用表而产品表是被引用表。相应地,也有引用和被引用列
的说法。
我们也可以把上述命令简写为:
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 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允许我们处理这种情况。直观上,我们有几种选项:
• 不允许删除一个被引用的产品
• 同时也删除引用产品的订单
63
数据定义
• 其他?
为了说明这些,让我们在上面的多对多关系例子中实现下面的策略:当某人希望移除一个仍
然被一个订单引用(通过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 NULL和SET DEFAULT。这些将导致在被引用行被删除后,引
用行中的引用列被置为空值或它们的默认值。注意这些并不会是我们免于遵守任何约束。例
如,如果一个动作指定了SET DEFAULT,但是默认值不满足外键约束,操作将会失败。
与ON DELETE相似,同样有ON UPDATE可以用在一个被引用列被修改(更新)的情况,可选的
动作相同。在这种情况下,CASCADE意味着被引用列的更新值应该被复制到引用行中。
正常情况下,如果一个引用行的任意一个引用列都为空,则它不需要满足外键约束。如果在
外键定义中加入了MATCH FULL,一个引用行只有在它的所有引用列为空时才不需要满足外键
约束(因此空和非空值的混合肯定会导致MATCH FULL约束失败)。如果不希望引用行能够避
开外键约束,将引用行声明为NOT NULL。
一个外键所引用的列必须是一个主键或者被唯一约束所限制。这意味着被引用列总是拥有一
个索引(位于主键或唯一约束之下的索引),因此在其上进行的一个引用行是否匹配的检查
将会很高效。由于从被引用表中DELETE一行或者UPDATE一个被引用列将要求对引用表进行扫
描以得到匹配旧值的行,在引用列上建立合适的索引也会大有益处。由于这种做法并不是必
须的,而且创建索引也有很多种选择,所以外键约束的定义并不会自动在引用列上创建索
引。
更多关于更新和删除数据的信息请见第 6 章。外键约束的语法描述请参考CREATE TABLE。
5.4.6. 排他约束
排他约束保证如果将任何两行的指定列或表达式使用指定操作符进行比较,至少其中一个操
作符比较将会返回否或空值。语法是:
CREATE TABLE circles (
c circle,
64
数据定义
EXCLUDE USING gist (c WITH &&)
);
详见CREATE TABLE … CONSTRAINT … EXCLUDE。
增加一个排他约束将在约束声明所指定的类型上自动创建索引。
5.5. 系统列
每一个表都拥有一些由系统隐式定义的system columns。因此,这些列的名字不能像用户定
义的列一样使用(注意这种限制与名称是否为关键词没有关系,即便用引号限定一个名称也
无法绕过这种限制)。 事实上用户不需要关心这些列,只需要知道它们存在即可。
tableoid
包含这一行的表的OID。该列是特别为从继承层次(见第 5.10 节)中选择的查询而准
备,因为如果没有它将很难知道一行来自于哪个表。tableoid可以与pg_class的oid列进
行连接来获得表的名称。
xmin
插入该行版本的事务身份(事务ID)。一个行版本是一个行的一个特别版本,对一个逻
辑行的每一次更新都将创建一个新的行版本。
cmin
插入事务中的命令标识符(从0开始)。
xmax
删除事务的身份(事务ID),对于未删除的行版本为0。对于一个可见的行版本,该列值
也可能为非零。这通常表示删除事务还没有提交,或者一个删除尝试被回滚。
cmax
删除事务中的命令标识符,或者为0。
ctid
行版本在其表中的物理位置。注意尽管ctid可以被用来非常快速地定位行版本,但是一
个行的ctid会在被更新或者被VACUUM FULL移动时改变。因此,ctid不能作为一个长期行
标识符。 应使用主键来标识逻辑行。
事务标识符也是32位量。在一个历时长久的数据库中事务ID同样会绕回。但如果采取适当的
维护过程,这不会是一个致命的问题,详见第 24 章。但是,长期(超过10亿个事务)依赖
事务ID的唯一性是不明智的。
命令标识符也是32位量。这对一个事务中包含的SQL命令设置了一个硬极限: 232(40亿)。
在实践中,该限制并不是问题 — 注意该限制只是针对SQL命令的数目而不是被处理的行数。
同样,只有真正 修改了数据库内容的命令才会消耗一个命令标识符。
5.6. 修改表
当我们已经创建了一个表并意识到犯了一个错误或者应用需求发生改变时,我们可以移除表
并重新创建它。但如果表中已经被填充数据或者被其他数据库对象引用(例如有一个外键约
束),这种做法就显得很不方便。因此,PostgreSQL提供了一族命令来对已有的表进行修
改。注意这和修改表中所包含的数据是不同的,这里要做的是对表的定义或者说结构进行修
改。
65
数据定义
利用这些命令,我们可以:
• 增加列
• 移除列
• 增加约束
• 移除约束
• 修改默认值
• 修改列数据类型
• 重命名列
• 重命名表
所有这些动作都由ALTER TABLE命令执行,其参考页面中包含更详细的信息。
5.6.1. 增加列
要增加一个列,可以使用这样的命令:
ALTER TABLE products ADD COLUMN description text;
新列将被默认值所填充(如果没有指定DEFAULT子句,则会填充空值)。
提示
从 PostgreSQL 11开始,添加一个具有常量默认值的列不再意味着在执
行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 节。
66
数据定义
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
表名将会对此有所帮助,其他接口也会提供方法来查看表的细节。因此命令是:
ALTER TABLE products DROP CONSTRAINT some_name;
(如果处理的是自动生成的约束名称,如KaTeX parse error: Undefined control sequence: \l at position 4425: …ATABASE CTc Tc \̲l̲ ̲DOMAIN U U \dD+…user", public
第一个元素说明一个和当前用户同名的模式会被搜索。如果不存在这个模式,该项将被忽
略。第二个元素指向我们已经见过的公共模式。
搜索路径中的第一个模式是创建新对象的默认存储位置。这就是默认情况下对象会被创建在
公共模式中的原因。当对象在任何其他没有模式限定的环境中被引用(表修改、数据修改或
查询命令)时,搜索路径将被遍历直到一个匹配对象被找到。因此,在默认配置中,任何非
限定访问将只能指向公共模式。
要把新模式放在搜索路径中,我们可以使用:
SET search_path TO myschema,public;
(我们在这里省略了
u
s
e
r
,因为我们并不立即需要它)。然后我们可以删除该表而无需使用方案进行限定:
D
R
O
P
T
A
B
L
E
m
y
t
a
b
l
e
;
同样,由于
m
y
s
c
h
e
m
a
是路径中的第一个元素,新对象会被默认创建在其中。我们也可以这样写:
S
E
T
s
e
a
r
c
h
p
a
t
h
T
O
m
y
s
c
h
e
m
a
;
这样我们在没有显式限定时再也不必去访问公共模式了。公共模式没有什么特别之处,它只是默认存在而已,它也可以被删除。其他操作模式搜索路径的方法请见第
9.25
节。搜索路径对于数据类型名称、函数名称和操作符名称的作用与表名一样。数据类型和函数名称可以使用和表名完全相同的限定方式。如果我们需要在一个表达式中写一个限定的操作符名称,我们必须写成一种特殊的形式:
O
P
E
R
A
T
O
R
(
s
c
h
e
m
a
.
o
p
e
r
a
t
o
r
)
这是为了避免句法歧义。例如:
S
E
L
E
C
T
3
O
P
E
R
A
T
O
R
(
p
g
c
a
t
a
l
o
g
.
+
)
4
;
实际上我们通常都会依赖于搜索路径来查找操作符,因此没有必要去写如此“丑陋”的东西。
5.9.4.
模式和权限默认情况下,用户不能访问不属于他们的方案中的任何对象。要允许这种行为,模式的拥有者必须在该模式上授予
U
S
A
G
E
权限。为了允许用户使用方案中的对象,可能还需要根据对象授予额外的权限。一个用户也可以被允许在其他某人的模式中创建对象。要允许这种行为,模式上的
C
R
E
A
T
E
权限必须被授予。注意在默认情况下,所有人都拥有在
p
u
b
l
i
c
模式上的
C
R
E
A
T
E
和
U
S
A
G
E
权限。
80
数据定义这使得用户能够连接到一个给定数据库并在它的
p
u
b
l
i
c
模式中创建对象。回收这一特权的使用模式调用:
R
E
V
O
K
E
C
R
E
A
T
E
O
N
S
C
H
E
M
A
p
u
b
l
i
c
F
R
O
M
P
U
B
L
I
C
;
(第一个“
p
u
b
l
i
c
”是方案,第二个“
p
u
b
l
i
c
”指的是“每一个用户”。第一种是一个标识符,第二种是一个关键词,所以两者的大小写不同。请回想第
4.1.1
节中的指导方针。)
5.9.5.
系统目录模式除
p
u
b
l
i
c
和用户创建的模式之外,每一个数据库还包括一个
p
g
c
a
t
a
l
o
g
模式,它包含了系统表和所有内建的数据类型、函数以及操作符。
p
g
c
a
t
a
l
o
g
总是搜索路径的一个有效部分。如果没有在路径中显式地包括该模式,它将在路径中的模式之前被搜索。这保证了内建的名称总是能被找到。然而,如果我们希望用用户定义的名称重载内建的名称,可以显式的将
p
g
c
a
t
a
l
o
g
放在搜索路径的末尾。由于系统表名称以
p
g
开
头,最好还是避免使用这样的名称,以避免和未来新版本中可能出现的系统表名发生冲突。系统表将继续采用以
p
g
开
头的方式,这样它们不会与非限制的用户表名称冲突。
5.9.6.
使用模式模式能够以多种方式组织数据
.
s
e
c
u
r
e
s
c
h
e
m
a
u
s
a
g
e
p
a
t
t
e
r
n
防止不受信任的用户更改其他用户查询的行为。当数据库不使用安全模式使用方式时,希望安全地查询该数据库的用户将在每个会话开始时采取保护操作。具体的说,他们将通过设置
s
e
a
r
c
h
p
a
t
h
到空字符串或在其它情况下从
s
e
a
r
c
h
p
a
t
h
中删除非超级用户可写的模式来开始每个会话。默认配置可以很容易的支持一些使用模式。•将普通用户约束在其私有的方案中。要实现这一点,发出
R
E
V
O
K
E
C
R
E
A
T
E
O
N
S
C
H
E
M
A
p
u
b
l
i
c
F
R
O
M
P
U
B
L
I
C
,并且为每一个用户创建一个用其用户名命名的方案。回想一下以
user,因为我们并不立即需要它)。然后我们可以删除该表而无需使 用方案进行限定: DROP TABLE mytable; 同样,由于myschema是路径中的第一个元素,新对象会被默认创建在其中。 我们也可以这样写: SET search_path TO myschema; 这样我们在没有显式限定时再也不必去访问公共模式了。公共模式没有什么特别之处,它只 是默认存在而已,它也可以被删除。 其他操作模式搜索路径的方法请见第 9.25 节。 搜索路径对于数据类型名称、函数名称和操作符名称的作用与表名一样。数据类型和函数名 称可以使用和表名完全相同的限定方式。如果我们需要在一个表达式中写一个限定的操作符 名称,我们必须写成一种特殊的形式: OPERATOR(schema.operator) 这是为了避免句法歧义。例如: SELECT 3 OPERATOR(pg_catalog.+) 4; 实际上我们通常都会依赖于搜索路径来查找操作符,因此没有必要去写如此“丑陋”的东 西。 5.9.4. 模式和权限 默认情况下,用户不能访问不属于他们的方案中的任何对象。要允许这种行为,模式的拥有 者必须在该模式上授予USAGE权限。为了允许用户使用方案中的对象,可能还需要根据对象 授予额外的权限。 一个用户也可以被允许在其他某人的模式中创建对象。要允许这种行为,模式上的CREATE权 限必须被授予。注意在默认情况下,所有人都拥有在public模式上的CREATE和USAGE权限。 80 数据定义 这使得用户能够连接到一个给定数据库并在它的public模式中创建对象。回收这一特权的使 用模式调用: REVOKE CREATE ON SCHEMA public FROM PUBLIC; (第一个“public”是方案,第二个“public”指的是“每一个用户”。第一种是一个标识 符,第二种是一个关键词,所以两者的大小写不同。请回想第 4.1.1 节中的指导方针。) 5.9.5. 系统目录模式 除public和用户创建的模式之外,每一个数据库还包括一个pg_catalog模式,它包含了系统 表和所有内建的数据类型、函数以及操作符。pg_catalog总是搜索路径的一个有效部分。如 果没有在路径中显式地包括该模式,它将在路径中的模式之前被搜索。这保证了内建的名称 总是能被找到。然而,如果我们希望用用户定义的名称重载内建的名称,可以显式的 将pg_catalog放在搜索路径的末尾。 由于系统表名称以pg_开头,最好还是避免使用这样的名称,以避免和未来新版本中 可能出 现的系统表名发生冲突。系统表将继续采用以pg_开头的方式,这样它们不会 与非限制的用 户表名称冲突。 5.9.6. 使用模式 模式能够以多种方式组织数据.secure schema usage pattern防止不受信任的用户更改其他 用户查询的行为。 当数据库不使用安全模式使用方式时,希望安全地查询该数据库的用户 将在每个会话开始时采取保护操作。 具体的说,他们将通过设置search_path到空字符串或 在其它情况下从search_path中删除非超级用户可写的模式来开始每个会话。 默认配置可以 很容易的支持一些使用模式。 • 将普通用户约束在其私有的方案中。要实现这一点,发出REVOKE CREATE ON SCHEMA public FROM PUBLIC,并且为每一个用户创建一 个用其用户名命名的方案。 回想一下以
user,因为我们并不立即需要它)。然后我们可以删除该表而无需使用方案进行限定:DROPTABLEmytable;同样,由于myschema是路径中的第一个元素,新对象会被默认创建在其中。我们也可以这样写:SETsearchpathTOmyschema;这样我们在没有显式限定时再也不必去访问公共模式了。公共模式没有什么特别之处,它只是默认存在而已,它也可以被删除。其他操作模式搜索路径的方法请见第9.25节。搜索路径对于数据类型名称、函数名称和操作符名称的作用与表名一样。数据类型和函数名称可以使用和表名完全相同的限定方式。如果我们需要在一个表达式中写一个限定的操作符名称,我们必须写成一种特殊的形式:OPERATOR(schema.operator)这是为了避免句法歧义。例如:SELECT3OPERATOR(pgcatalog.+)4;实际上我们通常都会依赖于搜索路径来查找操作符,因此没有必要去写如此“丑陋”的东西。5.9.4.模式和权限默认情况下,用户不能访问不属于他们的方案中的任何对象。要允许这种行为,模式的拥有者必须在该模式上授予USAGE权限。为了允许用户使用方案中的对象,可能还需要根据对象授予额外的权限。一个用户也可以被允许在其他某人的模式中创建对象。要允许这种行为,模式上的CREATE权限必须被授予。注意在默认情况下,所有人都拥有在public模式上的CREATE和USAGE权限。80数据定义这使得用户能够连接到一个给定数据库并在它的public模式中创建对象。回收这一特权的使用模式调用:REVOKECREATEONSCHEMApublicFROMPUBLIC;(第一个“public”是方案,第二个“public”指的是“每一个用户”。第一种是一个标识符,第二种是一个关键词,所以两者的大小写不同。请回想第4.1.1节中的指导方针。)5.9.5.系统目录模式除public和用户创建的模式之外,每一个数据库还包括一个pgcatalog模式,它包含了系统表和所有内建的数据类型、函数以及操作符。pgcatalog总是搜索路径的一个有效部分。如果没有在路径中显式地包括该模式,它将在路径中的模式之前被搜索。这保证了内建的名称总是能被找到。然而,如果我们希望用用户定义的名称重载内建的名称,可以显式的将pgcatalog放在搜索路径的末尾。由于系统表名称以pg开头,最好还是避免使用这样的名称,以避免和未来新版本中可能出现的系统表名发生冲突。系统表将继续采用以pg开头的方式,这样它们不会与非限制的用户表名称冲突。5.9.6.使用模式模式能够以多种方式组织数据.secureschemausagepattern防止不受信任的用户更改其他用户查询的行为。当数据库不使用安全模式使用方式时,希望安全地查询该数据库的用户将在每个会话开始时采取保护操作。具体的说,他们将通过设置searchpath到空字符串或在其它情况下从searchpath中删除非超级用户可写的模式来开始每个会话。默认配置可以很容易的支持一些使用模式。•将普通用户约束在其私有的方案中。要实现这一点,发出REVOKECREATEONSCHEMApublicFROMPUBLIC,并且为每一个用户创建一个用其用户名命名的方案。回想一下以user开头的默认搜索路径,该路径解析为
用户名。 因此,如果每个用户都有单独的模式,则默认情况下他们访问自己的模
式。 在不受信任的用户已经登录的数据库中采用此模式后,请考虑审计名字类似于模
式pg_catalog中的对象的公共模式。 此方式是一种安全模式的使用方式,除非不受信任
的用户是数据库所有者或拥有CREATEROLE权限,在这种情况下没有安全模式使用方式存
在。
• 从默认搜索路径中删除公共模式,通过修改postgresql.conf或通过发出ALTER ROLE ALL
SET search_path =“$user”。 每一个都保留在公共模式中创建对象的能力,但是只有符合
资格的名称才会选择这些对象。 虽然符合资格的表引用是可以的,但是要调用公共模式
中的函数will be unsafe or unreliable。 如果在公共模式中创建函数或扩展,请改用第
一个方式。 否则,与第一个模式一样,这是安全的,除非不受信任的用户是数据库所有
者或拥有CREATEROLE权限。
• 保持默认。所有用户都隐式地访问公共模式。这模拟了方案根本不可用的情况,可以用于
从无模式感知的世界平滑过渡。 但是,这绝不是一个安全的模式。只有当数据库仅有单
个用户或者少数相互信任的用户时,才可以接受。
对于任何一种模式,为了安装共享的应用(所有人都要用其中的表,第三方提供的额外函
数,等等),可把它们放在单独的方案中。记住授予适当的特权以允许其他用户访问它们。
然后用户可以通过以方案名限定名称的方式来引用这些额外的对象,或者他们可以把额外的
方案放在自己的搜索路径中。
5.9.7. 可移植性
在SQL标准中,在由不同用户拥有的同一个模式中的对象是不存在的。此外,某些实现不允
许创建与拥有者名称不同名的模式。事实上,在那些仅实现了标准中基本模式支持的数据库
81
数据定义
中,模式和用户的概念是等同的。因此,很多用户认为限定名称实际上是
由user_name.table_name组成的。如果我们为每一个用户都创建了一个模式,PostgreSQL实
际也是这样认为的。
同样,在SQL标准中也没有public模式的概念。为了最大限度的与标准一致,我们不应使用
(甚至是删除)public模式。
当然,某些SQL数据库系统可能根本没有实现方案,或者提供允许跨数据库访问的名字空
间。如果需要使用这样一些系统,最好不要使用方案。
5.10. 继承
PostgreSQL实现了表继承,这对数据库设计者来说是一种有用的工具(SQL:1999及其后的版
本定义了一种类型继承特性,但和这里介绍的继承有很大的不同)。
让我们从一个例子开始:假设我们要为城市建立一个数据模型。每一个州有很多城市,但是
只有一个首府。我们希望能够快速地检索任何特定州的首府城市。这可以通过创建两个表来
实现:一个用于州首府,另一个用于不是首府的城市。然而,当我们想要查看一个城市的数
据(不管它是不是一个首府)时会发生什么?继承特性将有助于解决这个问题。我们可以
将capitals表定义为继承自cities表:
CREATE TABLE cities (
name text,
population float,
altitude int -# in feet
);
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);
在这种情况下,capitals表继承了它的父表cities的所有列。州首府还有一个额外的
列state用来表示它所属的州。
在PostgreSQL中,一个表可以从0个或者多个其他表继承,而对一个表的查询则可以引用一
个表的所有行或者该表的所有行加上它所有的后代表。默认情况是后一种行为。例如,下面
的查询将查找所有海拔高于500尺的城市的名称,包括州首府:
SELECT name, altitude
FROM cities
WHERE altitude > 500;
对于来自PostgreSQL教程(见第 2.1 节)的例子数据,它将返回:
name | altitude
-#-#-#-#-#-±#-#-#-#-#
Las Vegas | 2174
Mariposa | 1953
Madison | 845
在另一方面,下面的查询将找到海拔超过500尺且不是州首府的所有城市:
SELECT name, altitude
FROM ONLY cities
82
数据定义
WHERE altitude > 500;
name | altitude
-#-#-#-#-#-±#-#-#-#-#
Las Vegas | 2174
Mariposa | 1953
这里的ONLY关键词指示查询只被应用于cities上,而其他在继承层次中位于cities之下的其
他表都不会被该查询涉及。很多我们已经讨论过的命令(如SELECT、UPDATE和DELETE)都支
持ONLY关键词。
我们也可以在表名后写上一个来显式地将后代表包括在查询范围内:
SELECT name, altitude
FROM cities
WHERE altitude > 500;
写*不是必需的,因为这种行为总是默认的。不过,为了兼容可以修改默认值的较老版本,
现在仍然支持这种语法。
在某些情况下,我们可能希望知道一个特定行来自于哪个表。每个表中的系统列tableoid可
以告诉我们行来自于哪个表:
SELECT c.tableoid, c.name, c.altitude
FROM cities c
WHERE c.altitude > 500;
将会返回:
tableoid | name | altitude
-#-#-#-#-#±#-#-#-#-#-±#-#-#-#-#
139793 | Las Vegas | 2174
139793 | Mariposa | 1953
139798 | Madison | 845
(如果重新生成这个结果,可能会得到不同的OID数字。)通过与pg_class进行连接可以看
到实际的表名:
SELECT p.relname, c.name, c.altitude
FROM cities c, pg_class p
WHERE c.altitude > 500 AND c.tableoid = p.oid;
将会返回:
relname | name | altitude
-#-#-#-#-#±#-#-#-#-#-±#-#-#-#-#
cities | Las Vegas | 2174
cities | Mariposa | 1953
capitals | Madison | 845
另一种得到同样效果的方法是使用regclass别名类型, 它将象征性地打印出表的 OID:
SELECT c.tableoid::regclass, c.name, c.altitude
FROM cities c
83
数据定义
WHERE c.altitude > 500;
继承不会自动地将来自INSERT或COPY命令的数据传播到继承层次中的其他表中。在我们的例
子中,下面的INSERT语句将会失败:
INSERT INTO cities (name, population, altitude, state)
VALUES (‘Albany’, NULL, NULL, ‘NY’);
我们也许希望数据能被以某种方式被引入到capitals表中,但是这不会发生:INSERT总是向
指定的表中插入。在某些情况下,可以通过使用一个规则(见第 40 章)来将插入动作重定
向。但是这对上面的情况并没有帮助,因为cities表根本就不包含state列,因而这个命令
将在触发规则之前就被拒绝。
父表上的所有检查约束和非空约束都将自动被它的后代所继承,除非显式地指定了NO
INHERIT子句。其他类型的约束(唯一、主键和外键约束)则不会被继承。
一个表可以从超过一个的父表继承,在这种情况下它拥有父表们所定义的列的并集。任何定
义在子表上的列也会被加入到其中。如果在这个集合中出现重名列,那么这些列将被“合
并”,这样在子表中只会有一个这样的列。重名列能被合并的前提是这些列必须具有相同的
数据类型,否则会导致错误。可继承的检查约束和非空约束会以类似的方式被合并。例如,
如果合并成一个合并列的任一列定义被标记为非空,则该合并列会被标记为非空。如果检查
约束的名称相同,则他们会被合并,但如果它们的条件不同则合并会失败。
表继承通常是在子表被创建时建立,使用CREATE TABLE语句的INHERITS子句。一个已经被创
建的表也可以另外一种方式增加一个新的父亲关系,使用ALTER TABLE的INHERIT变体。要这
样做,新的子表必须已经包括和父表相同名称和数据类型的列。子表还必须包括和父表相同
的检查约束和检查表达式。相似地,一个继承链接也可以使用ALTER TABLE的 NO INHERIT变
体从一个子表中移除。动态增加和移除继承链接可以用于实现表划分(见第 5.11 节)。
一种创建一个未来将被用做子女的新表的方法是在CREATE TABLE中使用LIKE子句。这将创建
一个和源表具有相同列的新表。如果源表上定义有任何CHECK约束,LIKE的INCLUDING
CONSTRAINTS选项可以用来让新的子表也包含和父表相同的约束。
当有任何一个子表存在时,父表不能被删除。当子表的列或者检查约束继承于父表时,它们
也不能被删除或修改。如果希望移除一个表和它的所有后代,一种简单的方法是使
用CASCADE选项删除父表(见第 5.14 节)。
ALTER TABLE将会把列的数据定义或检查约束上的任何变化沿着继承层次向下传播。同样,
删除被其他表依赖的列只能使用CASCADE选项。ALTER TABLE对于重名列的合并和拒绝遵循
与CREATE TABLE同样的规则。
继承的查询仅在附表上执行访问权限检查。例如,在cities表上授予UPDATE权限也隐含着通
过cities访问时在capitals表中更新行的权限。 这保留了数据(也)在父表中的样子。但
是如果没有额外的授权,则不能直接更新capitals表。 此规则的两个例外是TRUNCATE 和
LOCK TABLE,总是检查子表的权限,不管它们是直接处理还是通过在父表上执行的那些命令
递归处理。
以类似的方式,父表的行安全性策略(见第 5.8 节)适用于继承查询期间来自于子表的
行。 只有当子表在查询中被明确提到时,其策略(如果有)才会被应用,在那种情况下,
附着在其父表上的任何策略都会被忽略。
外部表(见第 5.12 节)也可以是继承层次 中的一部分,即可以作为父表也可以作为子表,
就像常规表一样。如果 一个外部表是继承层次的一部分,那么任何不被该外部表支持的操
作也 不被整个层次所支持。
5.10.1. 警告
注意并非所有的SQL命令都能工作在继承层次上。用于数据查询、数据修改或模式修改(例
如SELECT、UPDATE、DELETE、大部分ALTER TABLE的变体,但INSERT或ALTER TABLE …
84
数据定义
RENAME不在此列)的命令会默认将子表包含在内并且支持ONLY记号来排除子表。负责数据库
维护和调整的命令(如REINDEX、VACUUM)只工作在独立的、物理的表上并且不支持在继承
层次上的递归。每个命令相应的行为请参见它们的参考页(SQL 命令)。
继承特性的一个严肃的限制是索引(包括唯一约束)和外键约束值应用在单个表上而非它们
的继承子女。在外键约束的引用端和被引用端都是这样。因此,按照上面的例子:
• 如果我们声明cities.name为UNIQUE或者PRIMARY KEY,这将不会阻止capitals表中拥有
和cities中城市同名的行。而且这些重复的行将会默认显示在cities的查询中。事实
上,capitals在默认情况下是根本不能拥有唯一约束的,并且因此能够包含多个同名的
行。我们可以为capitals增加一个唯一约束,但这无法阻止相对于cities的重复。
• 相似地,如果我们指定cities.name REFERENCES某个其他表,该约束不会自动地传播
到capitals。在此种情况下,我们可以变通地在capitals上手工创建一个相同
的REFERENCES约束。
• 指定另一个表的列REFERENCES cities(name)将允许其他表包含城市名称,但不会包含首
府名称。这对于这个例子不是一个好的变通方案。
某些未为继承层次结构实现的功能是为声明性分区实现的。在决定使用旧继承进行分区是否
对应用程序有用时,需要非常小心。
5.11. 表分区
PostgreSQL支持基本的表划分。本小节介绍为何以及怎样把划分实现为数据库设计的一部
分。
5.11.1. 概述
划分指的是将逻辑上的一个大表分成一些小的物理上的片。划分有很多益处:
• 在某些情况下查询性能能够显著提升,特别是当那些访问压力大的行在一个分区或者少数
几个分区时。划分可以取代索引的主导列、减小索引尺寸以及使索引中访问压力大的部分
更有可能被放在内存中。
• 当查询或更新访问一个分区的大部分行时,可以通过该分区上的一个顺序扫描来取代分散
到整个表上的索引和随机访问,这样可以改善性能。
• 如果批量操作的需求是在分区设计时就规划好的,则批量装载和删除可以通过增加或者去
除分区来完成。执行ALTER TABLE DETACH PARTITION或者使用DROP TABLE删除一个分区远
快于批量操作。这些命令也完全避免了批量DELETE导致的VACUUM开销。
• 很少使用的数据可以被迁移到便宜且较慢的存储介质上。
当一个表非常大时,划分所带来的好处是非常值得的。一个表何种情况下会从划分获益取决
于应用,一个经验法则是当表的尺寸超过了数据库服务器物理内存时,划分会为表带来好
处。
PostgreSQL对下列分区形式提供了内建支持:
范围划分
表被根据一个关键列或一组列划分为“范围”,不同的分区的范围之间没有重叠。例
如,我们可以根据日期范围划分,或者根据特定业务对象的标识符划分。
列表划分
通过显式地列出每一个分区中出现的键值来划分表。
85
数据定义
哈希分区
通过为每个分区指定模数和余数来对表进行分区。每个分区所持有的行都满足:分区键
的值除以为其指定的模数将产生为其指定的余数。
如果你的应用需要使用上面所列之外的分区形式,可以使用诸如继承和UNION ALL视图之类
的替代方法。这些方法很灵活,但是却缺少内建声明式分区的一些性能优势。
5.11.2. 声明式划分
PostgreSQL提供了一种方法指定如何把一个表划分成称为分区的片段。被划分的表被称作分
区表。这种说明由分区方法以及要被用作分区键的列或者表达式列表组成。
所有被插入到分区表的行将被基于分区键的值路由到分区中。每个分区都有一个由其分区边
界定义的数据子集。当前支持的分区方法是范围、列表以及哈希。
分区本身也可能被定义为分区表,这种用法被称为子分区。分区可以有自己的与其他分区不
同的索引、约束以及默认值。创建分区表及分区的更多细节请见CREATE TABLE。
无法把一个常规表转换成分区表,反之亦然。不过,可以把一个包含数据的常规表或者分区
表作为分区加入到另一个分区表,或者从分区表中移走一个分区并且把它变成一个独立的
表。有关ATTACH PARTITION和DETACH PARTITION子命令的内容请见ALTER TABLE。
个体分区在内部以继承的方式链接到分区表,不过无法对声明式分区表或其分区使用继承的
某些一般特性(下文讨论)。例如,分区不能有除其所属分区表之外的父表,一个常规表也
不能从分区表继承使得后者成为其父表。这意味着分区表及其分区不会参与到与常规表的继
承关系中。由于分区表及其分区组成的分区层次仍然是一种继承层次,所有第 5.10 节中所
述的继承的普通规则也适用,不过有一些例外,尤其是:
• 分区表的CHECK约束和NOT NULL约束总是会被其所有的分区所继承。不允许在分区表上创
建标记为NO INHERIT的CHECK约束。
• 只要分区表中不存在分区,则支持使用ONLY仅在分区表上增加或者删除约束。一旦分区存
在,那样做就会导致错误,因为当分区存在时是不支持仅在分区表上增加或删除约束的。
不过,分区表本身上的约束可以被增加(如果它们不出现在父表中)和删除。
• 由于分区表并不直接拥有任何数据,尝试在分区表上使用TRUNCATE ONLY将总是返回错
误。
• 分区不能有在父表中不存在的列。在使用CREATE TABLE创建分区时不能指定列,在事后使
用ALTER TABLE时也不能为分区增加列。只有当表的列正好匹配父表时,才能使用ALTER
TABLE … ATTACH PARTITION将它作为分区加入。
• 如果NOT NULL约束在父表中存在,那么就不能删除分区的列上的对应的NOT NULL约束。
分区也可以是外部表,不过它们有一些普通表没有的限制,详情请见CREATE FOREIGN
TABLE。
更新行的分区键可能导致它满足另一个不同的分区的分区边界,进而被移动到那个分区中。
5.11.2.1. 例子
假定我们正在为一个大型的冰激凌公司构建数据库。该公司每天测量最高温度以及每个区域
的冰激凌销售情况。概念上,我们想要一个这样的表:
CREATE TABLE measurement (
city_id int not null,
logdate date not null,
peaktemp int,
unitsales int
86
数据定义
);
我们知道大部分查询只会访问上周的、上月的或者上季度的数据,因为这个表的主要用途是
为管理层准备在线报告。为了减少需要被存放的旧数据量,我们决定只保留最近3年的数
据。在每个月的开始我们将去除掉最早的那个月的数据。在这种情况下我们可以使用分区技
术来帮助我们满足对measurement表的所有不同需求。
要在这种情况下使用声明式分区,可采用下面的步骤:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。