聚合函数按照一些共同的标准将多行的结果分组。例如统计一张表的行数,或者计算某些数值列的平均数。
如果要在PostgreSQL里面输入Valentine's Day,可以用双撇号转义,如Valentine''s Day
最简单的聚合函数是count()。min()返回最小值,max()返回最大值。
%是LIKE搜索中的通配符。
SELECT count(title) FROM events WHERE title like '%Day%';
查找最早开始时间和最晚结束时间。
select min(starts),max(ends) from events e INNER JOIN venues v on e.venue_id = v.venue_id where v.name = 'Crystal Ballroom';
分组
使用GROUP BY对行进行归类,然后执行一些聚合函数(如count())。
SELECT venue_id,count(*) FROM events GROUP BY venue_id;
GROUP BY有自己的过滤关键字: HAVING。HAVING和WHERE子句类似,只不过HAVING可以用聚合函数作为过滤条件,而WHERE不能。
SELECT venue_id,count(*) FROM events GROUP BY venue_id HAVING count(*)>=2 AND venue_id is not null;
窗口函数
窗口函数类似于GROUP BY查询,允许对多行执行聚合函数。所不同的是,窗口函数允许使用内置的聚合函数,而不要求将每个字段分组成单行。
select venue_id,count(*) over(PARTITION BY venue_id) from events order by venue_id;
select venue_id,count(*) from events group by venue_id order by venue_id;
PARTITION BY和GROUP BY类似,但是PARTITION BY不会在select属性列表之后,再对结果分组(从而将结果合并成较少的行),而是像其他所有字段一样返回分组的值(根据分组变量进行计算,但其他方面就是另一个属性)。或者按SQL的表达方式,它超越(OVER)结果集的分区(PARTITION),返回聚合函数的结果。
事务
事务保障了关系数据库的一致性。事务的准则是要么全部成功,要么全部失败。事务确保一组命令中的每一条命令都执行。如果过程中间发生了任何失败,所有的命令都将回滚。
PostgreSQL的事务遵循ACID,代表原子性(atomic,所有的操作都成功或者都不做),一致性(consistent,数据始终处于完整的状态),隔离性(isolated,事务相互之间不感染),以及持久性(durable,即使在服务器崩溃之后,提交的事务都是安全的)。
可以将任何事务的命令至于BEGIN TRANSACTION块内。
BEGIN TRANSACTION;
DELETE FROM events;
ROLLBACK; //验证原子性
SELECT * FROM events;
如果要修改两个表,并希望它们保持同步,事务就很有用。最典型的例子是一个银行借记/贷记系统,其中钱从一个账户转移到另一个账户:
BEGIN TRANSACTION;
UPDATE account SET total=total+5000.5 where account_id=1337;
UPDATE account SET total=total-5000.5 where account_id=45887;
END;
在psql中执行的每条命令都隐式地包裹在事务中。如果你执行一条命令,如DELETE FROM account where total< 20;数据组在删除的中途崩溃,不会被迫接受半张表。当重新启动数据库服务器的时候,该命令将回滚。
存储过程
使用存储过程可以避免将数千行数据发送到客户端应用程序,但也让应用程序代码与该数据库绑定,因此不应该轻易决定使用存储过程。
创建一个过程(FUNCTION),简化了向event表插入记录的工作,无需venue_id,就可以插入在某个场地举行的活动。如果场地不存在,会先创建,并在新的事件中引用它。最后函数会返回一个布尔值,表明添加新场地是否成功。
add_event.sql
CREATE OR REPLACE FUNCTION add_event(i_title text,i_starts timestamp,i_ends timestamp,venue text,postal varchar(9),country char(2))
RETURNS boolean AS $$
DECLARE
did_insert boolean := false;
found_count integer;
the_venue_id integer;
BEGIN
SELECT venue_id INTO the_venue_id
FROM venues v
WHERE v.postal_code =postal AND v.country_code=country AND v.name ILIKE venue LIMIT 1;
IF the_venue_id IS NULL THEN
INSERT INTO venues(name,postal_code,country_code)
VALUES(venus,postal,country)
RETURNING venue_id INTO the_venue_id;
did_insert :=true;
END IF;
--Note:not an"error",as in some programming languages RAISE NITICE 'Venus found %',the_venue_id;
INSERT INTO events(i_title,i_starts,i_ends,i_venue_id)
VALUES(title,starts,ends,venue_id);
RETURN did_insert;
END;
$$LANGUAGE plpgsql;
SELECT add_event('House Party','2012-05-03 23:00','2012-05-04 02:00','Run''s House','97205','us');
我们所写的存储过程中使用的语言是plpgsql(Procedural Language/PostgreSQL)。
可以在服务器端执行 createlang testdb --list,查看数据库中安装的语言,createlang命令也可以添加新的语言。
除了PL/pgSQL,PostgreSQL还支持三种更核心的语言Tcl、Perl和Python。还有Ruby、Jave、PHP、Scheme等拓展模块。
触发器
当插入或更新的操作发生时,触发器会自动调用存储过程。它允许数据库在数据变化的时候,强制执行一些必要的操作。
创建一个新函数,当活动信息event更新的时候,都会记录相信的日志。
首先创建一个logs表记录活动信息的变化,这里没有必要使用主键,因为只是日志。
create table logs(
event_id integer,
old_title varchar(255),
old_starts timestamp,
old_ends timestamp,
logged_at timestamp default current_timestamp
);
接下来,创建一个函数,将更新前的数据写入日志。
testdb/log_event.sql
<pre name="code" class="sql">CREATE OR REPLACE FUNCTION log_event() RETURNS trigger as $$
DECLARE
BEGIN
INSERT INTO logs(event_id,old_title,old_starts,old_ends)
values(OLD.event_id,OLD.title,OLD.starts,OLD.ends);
RAISE NOTICE 'someone just changed event #%',OLD.event_id;
RETURN new;
end;
$$LANGUAGE plpgsql;
最后,创建触发器,可以在任意行更新后记录相应变更。
create trigger log_events
after update on events
for each row execute procedure log_event();
现在更新一个事件做测试
update events
set ends='2012-05-04 01:00:00'
where title='LARP CLUB';
NOTICE: someone just changed event #1
而且原来的结束时间计入了日志。
SELECT event_id,old_title,old_ends,logged_at FROM logs;
触发器还可以在更新之前以及插入之前或者之后创建。
视图
VIEW可以使复杂查询的结果用起来像其他任何表一样。
与存储过程不同,VIEW不是执行的函数,而是查询的别名。
postgres/holiday_view_1.sql
CREATE VIEW holidays AS
SELECT event_id AS holiday_id,title AS name,starts AS date
FROM events
WHERE title LIKE '%Day%' AND venue_id IS not NULL;
可以像查询其他任何表一样查询holidays,其后面是普通的events表。
下面是查询节日视图holidays。
SELECT name,to_char(date, 'Month DD,YYYY') AS date
FROM holidays
WHERE date >= '2012-02-14';
视图是强大的工具,可以以一种简单的方式访问复杂查询的数据。
如果想在视图中添加一个新列,那么只能修改底层的表。
修改events表,使它有一组相关的颜色。
ALTER TABLE events ADD colors text ARRAY;
因为holidays有与events相关联的颜色组,所以需要修改视图的查询,以包含colors数组。
CREATE OR REPLACE VIEW holidays AS
SELECT event_id AS holiday_id,title AS name,starts AS date,colors
FROM events
WHERE title LIKE '%Day%' AND venue_id IS not NULL;
现在为选定的日子设置一个颜色字符串数组。
UPDATE holidays set colors = '{"red","green"}' where name = 'Christmas Day';
规则
规则是对如何修改解析过的查询树的描述。
postgres每次运行一条SQL语句,它将语句解析成查询树(抽象语法树)。
树的枝和叶是运算符和值,在执行前,树会被遍历、删减,并以其他方式修改。这棵树可以被postgres规则重新,然后发送到查询规划器(它也是以某种方式重写这棵树,以达到优化性能运行效果),最后会把最终的 命令发送到执行器。
诸如holidays这样的视图其实就是一条规则。
用EXPLAIN命令查看holidays视图的执行计划
(Filter是where的子句,Output为列的列表)
mydb=# EXPLAIN VERBOSE SELECT * FROM holidays;
QUERY PLAN
-------------------------------------------------------------------------------
Seq Scan on public.events (cost=0.00..17.75 rows=25 width=76)
Output: events.event_id, events.title, events.starts, events.colors
Filter: ((events.venue_id IS NOT NULL) AND (events.title ~~ '%Day%'::text))
如果对定义holidays视图的查询语句执行EXPLAIN VERBOSE,并和上面的视图比较,功能上是等价的。
EXPLAIN VERBOSE
SELECT event_id AS holiday_id,title AS name,starts AS date,colors
FROM events
WHERE title LIKE '%Day%' AND venue_id IS not NULL;
QUERY PLAN
-------------------------------------------------------------------------------
Seq Scan on public.events (cost=0.00..17.75 rows=25 width=76)
Output: event_id, title, starts, colors
Filter: ((events.venue_id IS NOT NULL) AND (events.title ~~ '%Day%'::text))
所以为了允许更新holidays视图,需要定义一条规则,告诉PostgreSQL在update的时候做什么操作。
规则将捕捉对holidays视图的更新,从OLD与NEW的伪关系取值,并在events上执行更新。
NEW看做是包含即将更新的值的表,而OLD则是包含查询的值。
postgres/create_rule.sql
CREATE RULE update_holidays AS ON UPDATE TO holidays DO INSTEAD
UPDATE events
SET title = NEW.name,
starts = NEW.date,
colors = NEW.colors
WHERE title = OLD.name;
有了这条规则,就可以直接更新holidays。
UPDATE holidays set colors = '{"red","green"}' where name = 'Christmas Day';