1、视图html
假设天气记录和城市为止的组合列表对咱们的应用有用,但咱们又不想每次须要使用它时都敲入整个查询。咱们能够在该查询上建立一个视图,这会给该查询一个名字,咱们能够像使用一个普通表同样来使用它:java
CREATE VIEW myview AS SELECT city, temp_lo, temp_hi, prcp, date, location FROM weather, cities WHERE city = name; SELECT * FROM myview;
对视图的使用是成就一个好的SQL数据库设计的关键方面。视图容许用户经过始终如一的接口封装表的结构细节,这样能够避免表结构随着应用的进化而改变。数据库
视图几乎能够用在任何可使用表的地方。在其余视图基础上建立视图也并很多见。api
2、外键并发
新的表定义以下:数据库设计
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 );
如今尝试插入一个非法的记录:函数
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、事务oop
事务是全部数据库系统的基础概念。事务最重要的一点是它将多个步骤捆绑成了一个单一的、要么全完成要么全不完成的操做。步骤之间的中间状态对于其余并发事务是不可见的,而且若是有某些错误发生致使事务不能完成,则其中任何一个步骤都不会对数据库形成影响。post
例如,考虑一个保存着多个客户帐户余额和支行总存款额的银行数据库。假设咱们但愿记录一笔从Alice的帐户到Bob的帐户的额度为100.00美圆的转帐。在最大程度地简化后,涉及到的SQL命令是:spa
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 COMMIT;
若是,在事务执行中咱们并不想提交(或许是咱们注意到Alice的余额不足),咱们能够发出ROLLBACK命令而不是COMMIT命令,这样全部目前的更新将会被取消。
PostgreSQL实际上将每个SQL语句都做为一个事务来执行。若是咱们没有发出BEGIN命令,则每一个独立的语句都会被加上一个隐式的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、窗口函数
一个窗口函数在一系列与当前行有某种关联的表行上执行一种计算。这与一个汇集函数所完成的计算有可比之处。可是与一般的汇集函数不一样的是,使用窗口函数并不会致使行被分组成为一个单独的输出行--行保留它们独立的标识。在这些现象背后,窗口函数能够访问的不只仅是查询结果的当前行。
下面是一个例子用于展现如何将每个员工的薪水与他/她所在部门的平均薪水进行比较:
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一样也能够被忽略,在这种状况下只会产生一个包含全部行的分区。
这里有一个与窗口函数相关的重要概念:对于每一行,在它的分区中的行集被称为它的窗口帧。 不少(但不是所有)窗口函数只做用在窗口帧中的行上,而不是整个分区。默认状况下,若是使用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 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);
5、继承
继承是面向对象数据库中的概念。它展现了数据库设计的新的可能性。
让咱们建立两个表:表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) ); 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
之下的其余表。