PostgreSQL之高级特性
1、视图
之前我们查询过城市的天气情况,假设天气记录和城市位置的组合列表对我们的应用有用,但我们又不想每次需要使用它时都敲入整个查询。我们可以在该查询上创建一个视图,这会给该查询一个名字,我们可以像使用一个普通表一样来使用它:
CREATE VIEW myview AS
SELECT city, temp_lo, temp_hi, prcp, date, location
FROM weather, cities
WHERE city = name;
SELECT * FROM myview;
对视图的使用是成就一个好的SQL数据库设计的关键方面。视图允许用户通过始终如一的接口封装表的结构细节,这样可以避免表结构随着应用的进化而改变。
视图几乎可以用在任何可以使用表的地方。
2、外键
还是之前查询城市天气的例子,考虑以下问题:我们希望确保在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
);
现在当我们往weather表中插入一条,cities中不存在的城市的天气情况:
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".
这里只是简单介绍一下。
3、事务
事务是所有数据库系统的基础概念。**事务最重要的一点是它将多个步骤捆绑成了一个单一的、要么全完成要么全不完成的操作。**步骤之间的中间状态对于其他并发事务是不可见的,并且如果有某些错误发生导致事务不能完成,则其中任何一个步骤都不会对数据库造成影响。
举个例子:
例如,考虑一个保存着多个客户账户余额和支行总存款额的银行数据库。假设我们希望记录一笔从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并未被扣款的情况。所以,我们需要一种保障,当操作中途某些错误发生时已经执行的步骤不会产生效果。将这些更新组织成一个事务就可以给我们这种保障。一个事务被称为是原子的:从其他事务的角度来看,它要么整个发生要么完全不发生。
我们同样希望能**保证一旦一个事务被数据库系统完成并认可,它就被永久地记录下来且即便其后发生崩溃也不会被丢失。**例如,如果我们正在记录Bob的一次现金提款,我们当然不希望他刚走出银行大门,对他账户的扣款就消失。一个事务型数据库保证一个事务在被报告为完成之前它所做的所有更新都被记录在持久存储(即磁盘)。
事务型数据库的另一个重要性质与原子更新的概念紧密相关:**当多个事务并发运行时,每一个都不能看到其他事务未完成的修改。**例如,如果一个事务正忙着总计所有支行的余额,它不会只包括Alice的支行的扣款而不包括Bob的支行的存款,或者反之。所以事务的全做或全不做并不只体现在它们对数据库的持久影响,也体现在它们发生时的可见性。一个事务所做的更新在它完成之前对于其他事务是不可见的,而之后所有的更新将同时变得可见。
在PostgreSQL中,开启一个事务需要将SQL命令用**BEGIN**
和**COMMIT**
命令包围起来。
因此我们的银行事务看起来会是这样:
BEGIN;
UPDATE accounts SET balance = balance - 100.00
WHERE name = 'Alice';
-- etc etc
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
是唯一的途径来重新控制一个由于错误被系统置为中断状态的事务块,而不是完全回滚它并重新启动。
4、窗口函数
窗口函数在一系列与当前行有某种关联的表行上执行一种计算。
这个先放一放,我目前不是很明白。
5、继承
继承是面向对象数据库中的概念,我觉得可以理解为Python中的继承吧。
举个例子:
我们创建俩个表:cities和capital。很明显,首都也是城市,也就是说capitals就是cities的子表。那么我们来看看建表的SQL语句:
CREATE TABLE cities (
name text,
population real,
altitude int -- (in ft)
);
CREATE TABLE capitals (
state char(2)
) INHERITS (cities);
你要是面向对象学习的好,我觉得这个代码一看就明白什么意思。capitals
的行从它的父亲cities
继承了所有列(name
、population
和altitude
)。
我为什么说它和Python很像呢?因为它也是支持多继承的,也就是说:一个表可以继承0个或者多个表。
小例子:
如果我要查询所有海拔在500米以上的城市名称(包括首都):
SELECT name, altitude
FROM cities
WHERE altitude > 500;
输出:
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
Madison | 845
(3 rows)
ONLY
那么我要是不包含首都呢?
SELECT name, altitude
FROM ONLY cities
WHERE altitude > 500;
输出:
name | altitude
-----------+----------
Las Vegas | 2174
Mariposa | 1953
(2 rows)
SELECT
、UPDATE
和DELETE
— 都支持这个ONLY
记号。