PostgreSQL ¶
起步 ¶
简介 ¶
开源:可以基于任何目的使用、修改甚至发布
先进:高度兼容SQL标准,支持事务的ACID原则
功能丰富:基本数据类型、数组、范围类型、JSON、XML、几何类型;完整性约束;索引;复杂查询;先进的查询优化器;分区表;异步、同步、逻辑复制;安全认证;全文检索
可扩展性:存储过程(C、PL/pgSQL、Perl、Python、Java等);FDW;自定义类型和索引;存储引擎
安装 ¶
下载源码
https://www.postgresql.org/download/,postgresql-14.5.tar.gz上传至Linux服务器
解压源码并编译安装
bash1tar -zxvf postgresql-14.5.tar.gz #解压 2yum install readline-devel #如果执行config脚本报readline错,安装readline依赖包 3./configure #依赖树构建,默认设置 4make #编译 5make install #安装,默认安装在 /usr/local/pgsql安装后的设置
bash1#共享库 设置 2LD_LIBRARY_PATH=/usr/local/pgsql/lib 3export LD_LIBRARY_PATH 4 5#环境变量,在/etc/profile文件或者~/.bash_profile文件末尾添加 6PATH=/usr/local/pgsql/bin:$PATH 7export PATH启动数据库
bash1#创建用户postgres,并给定权限 2adduser postgres #创建用户 postgres 3passwd postgres #设置用户密码 4usermod -aG wheel postgres #添加用户到组,使其拥有管理员权限 5 6chown postgres /usr/local/pgsql #将文件拥有者改为创建的用户 7 8su postgres #切换用户 9 10#创建一个数据库集簇,初始化一个数据库存储区域 11initdb -D /usr/local/pgsql/data 12 13#启动postgresql 14postgres -D /usr/local/pgsql/data >logfile 2>&1 & 15 16#关闭服务器 SIGTERM:智能关闭模式;SIGINT:快速关闭模式;SIGQUIT:立即关闭模式 17kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`
认证设置 ¶
打开数据集簇中postgresql.conf配置文件
1listen_addresses = '*'打开数据集簇中pg_hba.conf客户端认证配置文件
1## 0.0.0.0/0 可以表示所有ipv4地址,::0/0表示所有 IPv6 地址
2local all all trust
3host all all 0.0.0.0/0 password
4host all all ::1/128 password
5local all all trust
6host all all 0.0.0.0/0 password
7host all all ::1/128 password配置角色等信息
1psql #本地连接pg数据库
2
3#角色相关命令
4postgres=## CREATE ROLE admin LOGIN PASSWORD '123456'
5postgres=## CREATE USER admin PASSWORD '123456'
6postgres=## DROP ROLE admin;
7postgres=## ALTER ROLE admin WITH PASSWORD '123456';
8postgres=## SELECT rolname FROM pg_roles;创建数据库 ¶
1#创建数据库
2createdb mydb
3dropdb mydb
4
5#可以直接使用交互式终端程序访问
6pgsql mydb连接 ¶
通过客户端连接,默认用户名密码都为postgres
SQL语言 ¶
单引号:表示值
双引号:表示表名、列名等数据库存在关键字;且postgresql会发生去除双引号或字段转小写的操作,因此字段名大写时,需要加双引号进行查询
空值:null是一个特殊值(缺失值),不能使用 = 判断;使用distinct可以对null值进行去重;排序时order会认为null值最大;
DDL ¶
新建和删除表
1CREATE TABLE weather (
2 city varchar(80),
3 temp_lo int, -- 最低温度
4 temp_hi int, -- 最高温度
5 prcp real -- 湿度
6);
7COMMENT ON TABLE public.weather IS '天气表';
8COMMENT ON COLUMN public.weather.city IS '最低温度';
9COMMENT ON COLUMN public.weather.temp_lo IS '最高温度';
10COMMENT ON COLUMN public.weather.temp_hi IS '湿度';
11DROP TABLE weather;
12
13
14-- 根据已存在表结果建新表
15create table employees_his as (select * from employees where 1=0);DML ¶
新增
1INSERT INTO weather VALUES ('San Francisco', 46, 50, 0.25, '1994-11-27');
2
3-- 返回新增数据
4insert into dept (dept_id, dept_name)
5values ('000001', 'DeptOne')
6returning dept_id; -- 返回新增数据的dept_id字段
7
8-- 通过通用表表达式,将新增数据数据插入备份表
9with inserts as (
10 insert into employees values (206,'William','Gietz','WGIETZ','515.123.8181','2002-06-07','AC_ACCOUNT',8800.00,NULL,205,110)
11 returning *
12)
13insert into employees_his select * from inserts; -- 过查询插入数据更新
1UPDATE weather
2 SET temp_hi = temp_hi - 2, temp_lo = temp_lo - 2
3 WHERE date > '1994-11-28';
4
5
6-- 将更新之前的数据插入备份表
7-- 因为在一个语句中,所有的操作都在一个事务中,所以主查询中的 employees 是修改之前的状态
8with updates as (
9 update employees set salary = salary + 500 where employee_id = 206
10 RETURNING * -- 这里返回的是更新后的数据
11)
12insert into employees_his select * from employees where employee_id = 206;
13
14
15-- 将更新后的数据插入备份表
16with updates as (
17 update employees set salary = salary + 500 where employee_id = 206
18 RETURNING * -- 这里返回的是更新后的数据
19)
20insert into employees_his select * from updates;删除
1DELETE FROM weather WHERE city = 'Hayward';
2
3-- 连表删除,删除emp1中 emp存在的数据
4delete from emp1
5using emp WHERE emp1.employee_id = emp.employee_id;
6
7-- 利用通用表表达式备份删除的数据
8with deles as (
9 delete from employees where employee_id='206'
10 returning * -- 返回被删除的所有数据
11)
12insert into employees_his select * from deles; -- 过查询插入数据查询
1-- 查询pgsql版本
2select version() as "数据库版本"
3
4
5-- 基本查询 = != <> < > in not-in between
6select first_name,last_name from employees where employee_id = 100;
7
8
9-- 模糊查询 %任意多个字符 _任意一个字符
10select first_name,last_name from employees where first_name like 'Sa' || '%';
11
12
13-- 模糊查询 正则 ~ 大小写相关 ~* 大小写无关
14select first_name,last_name from employees where first_name ~ '^S.*n$';
15
16
17-- 多个查询条件 and or not 优先级:not>and>or
18select first_name,last_name from employees where employee_id >100 and first_name='Neena';
19
20
21-- 排序,默认为acs; asc 升序 desc 降序
22select first_name,last_name from employees order by first_name asc,last_name asc;
23
24
25-- 限制limit
26-- 查询前5条记录
27select first_name,last_name from employees order by first_name limit 5;
28select first_name,last_name from employees order by first_name fetch first 5 rows only;
29-- 查询第11到15的记录
30select first_name,last_name from employees order by first_name limit 5 offset 10;
31select first_name,last_name from employees order by first_name offset 10 rows fetch first 5 rows only;
32
33-- 分组聚合 count() avg() sum() string_agg(field,'',order by)等
34select manager_id,avg(salary) from employees group by manager_id having avg(salary)>9000;
35-- 分组多个字段时,汇总; 小计、合计、总计
36-- cube = grouping sets ((job_id,"year"),"year",job_id)
37-- rollup = grouping sets ((job_id,"year"),"year")
38-- grouping(job_id) 可以查看哪些数据是基于job_id汇总的
39-- grouping sets 自定义分组集
40select job_id ,extract(year from hire_date) as "year",count(1),grouping(job_id) from employees group by grouping sets ((job_id,"year"),"year",job_id);
41
42
43-- 内连接
44select e.first_name ,d.department_name from employees e join departments d ON e.department_id=d.department_id;
45
46
47-- 外连接 left join 、 right join 、 full join
48select e.first_name ,d.department_name from employees e left join departments d ON e.department_id=d.department_id;
49
50
51-- 交叉连接 会构造一张很大的表 cross join
52select concat(t1,'*',t2) from generate_series(1,9) t1 ,generate_series(1,9) t2;
53
54
55-- exists/not exists 将每行数据带入exists 后查询中匹配,能查到数据则返回
56select * from employees e where exists (select 1 from departments where department_id=e.employee_id)
57
58
59-- 当两个结果集很大时,all效率总是高于distinct
60-- 并集 union all 不去重、union distinct 去重,默认
61select * from (values (1),(2),(3)) as t1 union select * from (values (1)) t2;
62
63-- 交集 intersect all 不去重、intersect distinct 去重
64select * from (values (1),(2),(3)) as t1 intersect select * from (values (1)) t2;
65
66-- 差集 except all 不去重 、 except distinct 去重
67select * from (values (1),(2),(3)) as t1 except select * from (values (1)) t2;
68
69
70-- 通用表表达式 with temp as ( select ...) select * from temp
71with temp as (select 1) select * from temp;
72
73-- 递归通用表表达式 需要关键词recursive;组织架构图展示
74with recursive tb as (
75 select employee_id as empid,concat(first_name,last_name) as name,concat(first_name,last_name) as path from employees where manager_id is null -- 初始条件
76 union all
77 select employee_id as empid,concat(first_name,last_name) as name,concat(tb.path,'/',first_name,last_name) from employees join tb on manager_id=tb.empid -- 递归条件
78) select * from tb常用函数 ¶
条件表达式
1-- case 等值条件 如果不写else,未匹配到case的会返回null值
2select
3 first_name,last_name ,
4 case
5 department_id
6 when 90 then '管理'
7 when 60 then '开发'
8 else '其他'
9 end as "部门"
10from
11 employees e
12
13-- case 逻辑条件
14select
15 first_name,
16 case
17 when salary < 5000 then '低收入'
18 when salary between 5000 and 10000 then '中收入'
19 else '高收入'
20 end
21
22from
23 employees e
24
25-- case 行转列;count会忽略null值,利用case进行转列
26select
27 count(case department_id when 10 then 1 end) dept10_count,
28 count(case department_id when 20 then 1 end) dept10_count,
29 count(case department_id when 30 then 1 end) dept10_count
30from
31 employees e
32
33
34-- nullif(a,b) 如果a=b,返回null,1/null 值就为null
35select 1/nullif(0,0)
36
37-- coalesce(a,b,..) 返回第一个不为空的值
38select coalesce(null,'as')数学函数
| 操作符 | 描述 | 例子 | 结果 |
|---|---|---|---|
+ | 加 | 2 + 3 | 5 |
- | 减 | 2 - 3 | -1 |
* | 乘 | 2 * 3 | 6 |
/ | 除(整数除法将截断结果) | 4 / 2 | 2 |
% | 模(求余) | 5 % 4 | 1 |
^ | 幂(指数运算) | 2.0 ^ 3.0 | 8 |
| ` | /` | 平方根 | ` |
| ` | /` | 立方根 | |
! | 阶乘 | 5 ! | 120 |
!! | 阶乘(前缀操作符) | !! 5 | 120 |
@ | 绝对值 | @ -5.0 | 5 |
& | 二进制 AND | 91 & 15 | 11 |
| ` | ` | 二进制 OR | `32 |
# | 二进制 XOR | 17 ## 5 | 20 |
~ | 二进制 NOT | ~1 | -2 |
<< | 二进制左移 | 1 << 4 | 16 |
>> | 二进制右移 | 8 >> 2 | 2 |
1-- 绝对值
2select abs(-100);
3
4-- 取整 ceil向上找最近的整数、floor向下找最近的整数、round向零找最近的整数
5select ceil(2.3);
6
7-- 乘方和开方 power(a,b)a的b次方、sqrt(a)a的平方根、cbrt(a)a的立方根
8select sqrt(4);
9
10-- 返回参数正负数,可能返回值1、0、-1
11select sign(-5)
12
13-- 随机数 返回一个0-1之间的双精度随机数
14select random();字符函数
1-- 字符串拼接
2select 'a' || 'b' || 'c';
3select concat('a','b','c');
4select concat_ws('_','a','b','c'); -- 指定 _ 分隔符连接
5
6-- 获取字符串长度 length 按字符计算、bit_length 按比特计算、octet_length 按字节数计算
7select length('abc');
8
9-- 大小写转换 upper 转换大写、lower 转换小写、initcap 首字母大写
10select upper('abc');
11
12-- 字串查找与替换
13select substring('abcd',1,2); -- 从位置1开始,查找2个 ab
14select left('abcd',2); -- 从左边开始查找2个 ab
15select right('abcd',2); -- 从右边开始第2个到最后2个 cd
16select replace('abcd','a','d'); -- 替换,dbcd
17select translate('abcd','ac','1'); -- 对应替换 a替换为1,c直接删除 1bd
18
19-- 截断
20select trim(' abcd '); -- 去除前后空串
21select trim(both 'as' from 'abcdas' ); -- 两端截断包含 'a'或's' bcd
22
23-- 字符串格式化
24select format('hello %s','world'); -- %s占位符 hello world
25
26-- 返回md5值
27select md5('asd');
28
29-- 反转字符串
30select reverse('abcd'); -- dcba日期函数
1-- 获取当前日期、时间、时间戳
2select current_date,current_time,current_timestamp;
3
4-- 当前时间加上一年 interval为时间段修饰词
5select current_date + interval '1 year';
6
7-- 获取时间戳的年份
8select date_part('year',current_timestamp);转换函数
1-- 将'123' 转换为整型 123
2select cast('123' as integer);
3
4-- 将 '2022-06-12' 转换为日期
5select cast('2022-06-12' as date);
6
7-- 转换为日期
8select to_date('2022/06/25','YYYY/MM/DD');
9
10-- 转化为时间
11select to_timestamp('2022/06/25','YYYY/MM/DD');
12
13-- 转化为字符串
14select to_char(current_date,'YYYY-MM-DD');高级特性 ¶
视图 ¶
视图(View)本质上是一个存储在数据库中的查询语句。视图本身不包含数据,也被称为 虚拟表
视图的好处
- 替代复杂查询,减少复杂性。将复杂的查询语句定义为视图,然后使用视图进行查询,可以隐藏具体的实现
- 提供一致性接口,实现业务规则。在视图的定义中增加业务逻辑,对外提供统一的接口;当底层表结构发生变化时,只需要修改视图接口,而不需要修改外部应用,可以简化代码的维护并减少错误
- 控制对于表的访问,提高安全性。通过视图为用户提供数据访问,而不是直接访问表;同时可以限制允许访问某些敏感信息
创建视图
1create view view_name as query;
2
3#实例
4create view myview as
5 select city, temp_lo, temp_hi, prcp
6 from weather
7 where date>'2022-01-12';修改视图
1#修改视图定义中的查询
2#PostgreSQL只支持追加视图定义中的字段,不支持减少字段或者修改字段的名称或顺序
3create or replace view view_name as query;
4
5#PostgreSQL 提供了 ALTER VIEW 语句修改视图的属性
6#修改视图名称
7alter view empdetailsview rename to empinfovie;删除视图
1#删除视图
2#if exists可以避免删除一个不存在的视图时产生错误
3#cascade 表示级联删除依赖于该视图的对象
4#restrict 表示如果存在依赖对象则提示错误信息,这是默认值
5drop view [ if exists ] name [ cascade | restrict ];
6
7#实例
8drop view myview;可更新视图
如果一个视图满足以下条件:
- 视图定义的 FROM 子句中只包含一个表或者可更新视图
- 视图定义的最顶层查询语句中不包含以下子句:GROUP BY、HAVING、LIMIT、OFFSET、DISTINCT、WITH、UNION、INTERSECT 以及 EXCEPT
- SELECT 列表中不包含窗口函数、集合函数或者聚合函数
那么该视图被称为可更新视图(updatable view),意味着我们可以对其执行 INSERT、 UPDATE 以及 DELETE 语句,PostgreSQL 会将这些操作转换为对底层表的操作
外键 ¶
1CREATE TABLE cities (
2 city varchar(80) primary key,
3 location point
4);
5
6CREATE TABLE weather (
7 city varchar(80) references cities(city),
8 temp_lo int,
9 temp_hi int,
10 prcp real,
11 date date
12);事务 ¶
数据库中的事务具有原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)以及持久性(Durability),也就是 ACID 属性
- 原子性:保证事务中的操作要么全部成功,要么全部失败,不会只成功一部分
- 一致性:确保了数据修改的有效性,并且遵循一定的业务规则;数据库还必须保证满足完整性约束
- 隔离性:决定了并发事务之间的可见性和相互影响程度
- 持久性:确保已经提交的事务必须永久生效,即使发生断电、系统崩溃等故障,数据库都不会丢失数据。对于 PostgreSQL 而言,使用的是预写式日志(WAL)的机制实现事务的持久性
事务语句
1begin;
2update cities set location = '(24,24)';
3savepoint my_savepoint; -- 回滚结点
4update cities set location = '(48,48)';
5rollback to my_savepoint; -- 会使回滚结点之后的操作回滚
6update weather set prcp = 14;
7commit;事务隔离级别
数据库如果不进行隔离控制可能出现以下情况
- 脏读(dirty read):事务A读取了事务B未提交的修改
- 不可重复读(nonrepeatable read):事务A多次读取同一条数据,可能不一致(被其他事务修改了)
- 幻读(phantom read):事务A按某个条件查询数据,再次查询时数据数量发生了变化(被其他事务修改了)
- 更新丢失(lost update):第一类:当两个事务更新相同的数据时,事务A被提交,事务B被撤销,那么第一个事务的更新也会被撤销;第二类:当两个事务同时读取某一记录,然后分别进行修改提交;就会造成先提交的事务的修改丢失
为了控制上述情况,SQL标准定义了 4 种不同的事务隔离级别
- Read Uncommitted(读未提交):最低的隔离级别,实际上就是不隔离,任何事务都可 以看到其他事务未提交的修改
- Read Committed(读已提交):一个事务只能看到其他事务已经提交的数据,解决了脏 读问题,但是存在不可重复读、幻读和第二类更新丢失问题
- Repeated Read(可重复读):一个事务对于某个数据的读取结果不变,即使其他事务 对该数据进行了修改并提交
- Serializable(可串行化):最高的隔离级别,事务串行化执行,没有并发
1-- 查看pgsql默认事务隔离级别
2show transaction_isolation
3
4
5-- 更改事务隔离级别
6begin;
7SET TRANSACTION ISOLATION LEVEL { SERIALIZABLE | REPEATABLE READ | READ
8COMMITTED | READ UNCOMMITTED };窗口函数 ¶
窗口函数不是将一组数据汇总为单个结果,而是针对每一行数据,基于和它相关的一组数 据计算出一个结果;聚合函数通常也可以作为窗口函数,区别在于后者包含了 OVER 关键字
窗口函数定义
1window_function ( expression, ... ) OVER (
2 PARTITION BY ...
3 ORDER BY ...
4 frame_clause
5)
6-- window_function 是窗口函数的名称
7-- expression 是函数参数,有些函数不需要参数
8-- OVER 子句包含三个选项:分区(PARTITION BY)、排序(ORDER BY)以及窗口大小(frame_clause)分区选项(PARTITION BY)
PARTITION BY 选项用于定义分区,作用类似于 GROUP BY 的分组。如果指定了分区选项, 窗口函数将会分别针对每个分区单独进行分析
排序选项(ORDER BY)
ORDER BY 选项用于指定分区内的排序方式,通常用于数据的排名分析
窗口选项(frame_clause)
frame_clause 选项用于在当前分区内指定一个计算窗口。指定了窗口之后,分析函数不再基 于分区进行计算,而是基于窗口内的数据进行计算
窗口大小的常用选项如下:
1{ ROWS | RANGE } frame_start
2{ ROWS | RANGE } BETWEEN frame_start AND frame_endframe_start 用于定义窗口的起始位置,可以指定以下内容之一
- unbounded preceding,窗口从分区的第一行开始,默认值
- N preceding,窗口从当前行之前的第 N 行或者数值开始
- current row,窗口从当前行开始
frame_end 用于定义窗口的结束位置,可以指定以下内容之一
- current row,窗口到当前行结束,默认值
- N following,窗口到当前行之后的第 N 行或者数值结束
- unbounded following,窗口到分区的最后一行结束
1-- 查询每个工作每年总共人数
2select
3 job_id,
4 extract(year from hire_date),
5 count(1) over (
6 partition by job_id -- 根据工作分组
7 order by extract(year from hire_date) -- 根据年份排序
8 rows between unbounded preceding and current row -- 定义计算窗口,第一行到当前行
9 )
10from
11 employees
12
13
14-- 排名窗口函数 查询员工在各自部门薪资排行
15select department_id,concat(first_name,last_name) as name,
16 row_number() over w1 as "row_number", -- 1-10 每行分配序号,连续
17 rank() over w1 as "rank", -- 1-8 8 10 相同的会跳跃
18 dense_rank() over w1 as "dense_rank", -- 1-8 8 9 10 相同的数据,后续依旧连续
19 percent_rank() over w1 as "percent_rank" -- 以百分比的形式显示每行数据在其分区中的名次
20from employees
21window w1 as (partition by department_id order by salary desc);
22
23
24-- 取值窗口函数
25select department_id,concat(first_name,last_name) as name,
26 first_value(salary) over w1 "first_value", -- 返回窗口内第一行的数据
27 last_value(salary) over w1 as "last_value", -- 返回窗口内最后一行的数据
28 nth_value(salary,2) over w1 as "nth_value", -- 返回窗口内第 N 行的数据
29 lag(salary) over w1 as "lag", -- 返回分区中当前行之前的第 N 行的数据
30 lead(salary) over w1 as "lead" -- 返回分区中当前行之后第 N 行的数据
31from employees
32window w1 as (partition by department_id order by salary desc); -- 这里定义的窗口可以在前面语句中直接使用
33
34-- 使用lag函数实现环比分析,各个部门根据年排序,取得本期汇总薪水和上期汇总薪水
35select
36 department_id,
37 "year" ,
38 salary,
39 lag(salary, 1) over (partition by department_id order by "year") as "presal"
40from
41 (selectdepartment_id,extract(year from hire_date) as "year",sum(salary) as "salary"
42 from employees group by 1, 2) as t索引与优化 ¶
索引(Index)可以用于提高数据库的查询性能;但是索引也需要进行读写,同时还会占用 更多的存储空间
postgresql默认使用B-树索引,B-树是一个自平衡树(self-balancing tree),按照顺序存储数据,支持对数时间复杂度(O(logN) 的搜索、插入、删除和顺序访问
实例
1-- 创建test表
2CREATE TABLE test ( id integer, name text);
3-- 模拟生成10000000条数据
4INSERT INTO test SELECT v,'val:'||v FROM generate_series(1, 10000000) v;
5
6-- 查看执行计划,需要花费5秒
7explain analyze SELECT name FROM test WHERE id = 10000;
8/*
9 * Gather (cost=1000 rows=1 width=11) (actual time=5410 rows=1 loops=1)
10 * ....
11 * Execution Time: 1981.174 ms
12 */
13
14-- 在id字段上创建索引
15create index test_id_index on test (id);
16
17-- 再次查看执行计划,走了索引且执行之间仅为 1.4 ms
18explain analyze SELECT name FROM test WHERE id = 10000;
19/*
20 * Index Scan using test_id_index on test (cost=0.56 rows=1 width=12) (actual time=1.0 rows=1 loops=1)
21 * Execution Time: 1.405 ms
22 */创建索引
唯一索引,唯一索引可以用于实现唯一约束,PostgreSQL 目前只支持 B-树类型的唯一索引
sql1create unique index index_name on table_name (column_name [asc | desc] [nulls first | nulls last]); 2 3-- 实例 4create unique index test_index on test (id desc);多列索引,对于多列索引,应该将最常作为查询条件使用的字段放在左边
sql1-- 对于多列索引,需要注意 如果第一列没在查询条件中 不会走索引 2create [unique] index index_name on table_name [using method] (column1 [asc | desc] [nulls first | nulls last], ...); 3 4-- 实例 5create unique index test_index on test (id desc,name);函数索引,函数索引,也叫表达式索引,是指基于某个函数或者表达式的值创建的索引
sql1create [unique] index index_name on table_name (expression); 2 3-- 实例,查询时条件为 upper(name)='ASD' 会走索引 4create unique index test_index on test (upper(name));部分索引,只针对表中部分数据行创建的索引,通过一个 WHERE 子句指定需要索引的行
sql1create index index_name on table_name (column_name) where expression; 2 3-- 实例,对于finished字段不为true的数据,在order_id上创建索引 4create index orders_unfinished_index on orders (order_id) where finished is not true;覆盖索引,pgsql默认创建索引,索引和表存储在两个地方,覆盖索引可以将数据和索引放在一起
sql1-- 实例,使用btree索引创建a,b列索引,并将c列数据包括进来, 2create unique index idx_t_ab on t using btree (a, b) include (c);
查看索引
PostgreSQL 提供了一个关于索引的视图 pg_indexes,可以用于查看索引的信息
1select * from pg_indexes where tablename = 'test';维护索引
索引重建的情况
- 索引已损坏,并且不再包含有效数据
- 索引已变得“膨胀”,即它包含许多空页面或近空页面
- 更改了索引的存储参数(例如 fillfactor),并希望确保所做的更改已完全生效
- 使用
CONCURRENTLY选项构建索引失败,留下“无效”索引
1-- 重命名索引
2alter index index_name rename to new_name;
3
4-- 移动索引到其他表空间
5alter index index_name set tablespace tablespace_name;
6
7-- 重建索引数据,支持不同级别的索引重建
8reindex [ ( verbose ) ] { index | table | schema | database | system } index_name;
9
10-- 实例 重建id_unique索引
11reindex index id_unique删除索引
1-- CASCADE 表示级联删除其他依赖该索引的对象
2-- RESTRICT 表示如果存在依赖于该索引的对象,将会拒绝删除操作。默认为 RESTRICT
3drop index index_name [ cascade | restrict ];
4
5-- 实例
6drop index test_id_index;存储过程 ¶
概述 ¶
除了标准 SQL 语句之外,PostgreSQL 还支持使用各种过程语言(例如 PL/pgSQL、C、PL/Tcl、PL/Python、PL/Perl、PL/Java 等 )创建复杂的过程和函数,称为存储过程(Stored Procedure)和自定义函数(User-Defined Function)。存储过程支持许多过程元素,例如控制结构、循环和复杂的计算
存储过程的好处:
- 减少应用和数据库之间的网络传输
- 提高应用的性能。因为自定义函数和存储过程进行了预编译并存储在数据库服务器中
- 可重用性。存储过程和函数的功能可以被多个应用同时使用
存储过程的缺点:
- 导致软件开发缓慢,因为存储过程需要单独学习
- 不易进行版本管理和代码调试
- 不同数据库管理系统之间无法移植,语法存在较大的差异
PostgreSQL 默认支持的存储过程为PL/pgSQL
PL/pgSQL结构 ¶
PL/pgSQL 是一种块状语言,因此存储过程和函数以代码块的形式进行组织;定义如下
1-- 所有的语句都使用分号(;)结束,END 之后的分号表示代码块结束
2
3[ <<label>> ] -- 可选的代码块标签,可用于退出语句或限定变量名称
4[ DECLARE -- 可选的声明部分,用于定义变量
5 declarations ]
6BEGIN -- BEGIN 和 END 之间是代码主体,也就是主要的功能代码
7 statements
8END [ label ];简单示例
1do $$ -- $$ 替换'
2declare
3 name text; -- 定义了变量name,类型为text
4begin
5 name := 'pl/pgsql'; -- 给变量赋值
6 raise notice 'hello %!', name; -- 输出通知信息
7end $$;以上为匿名代码块,使用do可以直接执行;
raise notice用于输出通知消息
$$ 可以用于替换',这样避免了代码块中使用引号需要转义
嵌套子块
PL/pgSQL 支持代码块的嵌套,被嵌套的代码块被称为子块(subblock),包含子块的代码块被称为外部块(subblock);子块中可以定义与外部块重名的变量,而且在子块内拥有更高的优先级;示例如下:
1do $$
2<< outerblock >>
3declare
4 quantity integer := 30;
5begin
6 raise notice 'quantity here is %', quantity; -- prints 30
7 quantity := 50;
8 --
9 -- 创建一个子块
10 --
11 declare
12 quantity integer := 80;
13 begin
14 raise notice 'quantity here is %', quantity; -- prints 80
15 raise notice 'outer quantity here is %', outerblock.quantity; -- prints 50
16 end;
17
18 raise notice 'quantity here is %', quantity; -- prints 50
19end;
20$$;变量与赋值 ¶
PL/pgSQL 支持定义变量和常量
基本变量
变量总是属于某个数据类型,变量的值可以在运行时被修改
变量声明:
1-- variable_name 是变量的名称
2-- data_type 是变量的类型,可以是任何 SQL 数据类型
3-- 指定了 NOT NULL,必须使用后面的表达式为变量指定初始值
4-- DEFAULT 指定变量初始值
5variable_name data_type [ NOT NULL ] [ { DEFAULT | := | = } expression ];变量定义示例如下:
1user_id integer;
2quantity numeric(5) DEFAULT 0;
3url varchar := 'http://mysite.com';行变量
基本的 SQL 数据类型之外,PL/pgSQL 还支持基于表的字段或行或者其他变量定义变量:
1-- myrow 是一个行类型的变量,可以存储查询语句返回的数据行(数据行的结构要和 tablename相同)
2-- myfield 的数据类型取决于 tablename.columnname 字段的定义
3-- amount 和 quantity 的类型一致
4
5myrow tablename%ROWTYPE;
6myfield tablename.columnname%TYPE;
7amount quantity%TYPE;记录类型变量
记录类型的变量没有预定义的结构,只有当变量被赋值时才确定,而且可以在运行时被改变;记录类型的变量可以用于任意查询语句或者 FOR 循环变量
1arow RECORD;变量别名
1-- newname 和 oldname 代表了相同的对象
2newname ALIAS FOR oldname;常量
在定义变量时指定了 CONSTANT 关键字,意味着定义的是常量。常量的值需要在声明时初始化,并且不能修改
示例:
1-- 定义常量PI
2do $$
3declare
4 PI constant numeric := 3.14159265;
5 radius numeric;
6begin
7 radius := 1.0;
8 raise notice 'the area is %', PI * radius * radius;
9end $$;控制结构 ¶
IF语句
PL/pgSQL 提供了三种形式的 IF 语句
- IF … THEN … END IF
- IF … THEN … ELSE … END IF
- IF … THEN … ELSIF … THEN … ELSE … END IF
IF 语句定义:
1-- boolean-expression为真,执行Then后语句
2IF boolean-expression THEN
3 statements
4END IF;示例
1do $$
2declare
3 a integer default 65;
4begin
5 if a>80 then
6 raise notice '优秀';
7 elsif a>60 then
8 raise notice '良好';
9 else
10 raise notice '不及格';
11 end if;
12end; $$
13-- 输出:良好CASE 语句
除了 IF 语句之外,PostgreSQL 还提供了 CASE 语句,同样可以根据不同的条件执行不同的分支语句。CASE 语句分为两种:简单 CASE 语句和搜索 CASE 语句。
简单 CASE 语句的结构如下:
1case search-expression
2 when expression [, expression [ ... ]] then
3 statements
4 [ when expression [, expression [ ... ]] then
5 statements ... ]
6 [ else statements ]
7end case;执行流程:首先,计算 search-expression 的值;然后依次和 WHEN 中的表达式进行等值比较;如果找到了相等的值,执行相应的 statements;后续的分支不再进行判断;如果没有匹配的值,执行 ELSE语句;如果此时没有 ELSE,将会抛出 CASE_NOT_FOUND 异常
示例:
1do $$
2declare
3 sorce integer := 65;
4begin
5 case
6 when sorce >= 80 then
7 raise notice '优秀';
8 when (sorce >=60 and sorce < 80) then
9 raise notice '良好';
10 else
11 raise notice '不及格';
12 end case;
13end; $$循环语句
PostgreSQL 提供 4 种循环执行命令的语句:LOOP、WHILE、FOR 和 FOREACH 循环,以 及循环控制的 EXIT 和 CONTINUE 语句
exit 用于退出循环
1exit [ label ] [ when boolean-expression ];continue 用于跳入下次循环
1continue [ label ] [ when boolean-expression ];LOOP 用于定义一个无限循环语句:
1-- 一般需要使用 EXIT 或者 RETURN 语句退出循环
2-- label 可以用于 EXIT 或者 CONTINUE 语句退出或者跳到执行的嵌套循环中
3[ <<label>> ]
4loop
5 statements
6end loop [ label ];示例:
1do $$
2declare
3 i integer:=0;
4begin
5 loop
6 raise notice 'Loop: %', i;
7 exit when i = 5; -- 当 i=5 时退出循环
8 i := i + 1;
9 end loop;
10end; $$WHILE 循环的语法:
1[ <<label>> ]
2WHILE boolean-expression LOOP
3 statements
4END LOOP [ label ];示例:
1do $$
2declare
3 i integer:=0;
4begin
5 while i <= 5 loop
6 raise notice 'Loop: %', i;
7 i := i + 1;
8 end loop;
9end; $$FOR 循环可以用于遍历一个整数范围或者查询结果集,遍历整数范围的语法如下:
1[ <<label>> ]
2for name in [ reverse ] expression .. expression [ by expression ] loop
3 statements
4end loop [ label ];示例:
1-- 变量 i 不需要提前定义
2-- 输出结果为 0 2 4
3do $$
4begin
5 for i in 0..5 by 2 loop -- 步长为2
6 raise notice 'Loop: %', i;
7 end loop;
8end; $$遍历查询结果集的 FOR 循环如下:
1[ <<label>> ]
2for target in query loop
3 statements
4end loop [ label ];示例:
1-- 注意这里的dept行变量需要提前定义
2do $$
3declare
4 dept record;
5begin
6 for dept in (select * from sys_dept) loop
7 raise notice 'Loop: %,%', dept.dept_id, dept.dept_name;
8 end loop;
9end; $$游标 ¶
PL/pgSQL 游标允许封装一个查询,然后每次处理结果集中的一条记录
游标可以将大结果集拆分成许多小的记录,避免内存溢出
可以定义一个返回游标引用的函数,然后调用程序可以基于这个引用处理返回的结果集
游标使用步骤
- 声明游标变量
- 打开游标
- 从游标中获取结果
- 判断是否存在更多结果。如果存在,执行第 3 步;否则,执行第 5 步
- 关闭游标
声明游标变量,游标变量总是refcursor类型
1-- scroll,那么游标可以反向滚动
2-- no scroll,那么反向取的动作会被拒绝
3-- 如果二者都没有被指定,那么能否进行反向取就取决于查询
4-- 如果指定了arguments, 那么它是一个逗号分隔的name datatype对的列表, 它们定义在给定查询中要被参数值替换的名称
5name [ [ no ] scroll ] cursor [ ( arguments ) ] for query;示例:
1do $$
2declare
3 rec_code record;
4 cur_code cursor for select * from sys_code limit 200;
5begin
6 -- 打开游标
7 open cur_code;
8 loop
9 -- 获取游标中下一行的记录
10 fetch cur_code into rec_code;
11 -- 没找到记录退出循环
12 exit when not found;
13 raise notice 'Loop : % %' ,rec_code.code_label,rec_code.code_value;
14 end loop;
15
16 -- 关闭游标
17 close cur_code;
18end;$$错误处理 ¶
报告错误和信息
PL/pgSQL 提供了 RAISE 语句,用于打印消息或者抛出错误:
1-- 不同的 level 代表了错误的不同严重级别,默认level为exception
2-- 包括:debug、log、notice、info、warning、exception
3-- format 是一个用于提供信息内容的字符串
4-- 可以使用%占位符,接收参数,%% 表示输出 % 本身
5raise level format;示例:
1do $$
2begin
3 raise debug 'This is a debug text.';
4 raise info 'This is an information.';
5 raise log 'This is a log.';
6 raise warning 'This is a warning at %', now();
7 raise notice 'This is a notice %%';
8end $$;
9-- 并非所有的消息都会打印到客户端和服务器日志中
10-- 可以通过配置参数 client_min_messages 和 log_min_messages 进行设置捕获异常
默认情况下,PL/pgSQL 遇到错误时会终止代码执行,同时撤销事务。我们也可以在代码块中使用 exception捕获错误并继续事务
1[ <<label>> ]
2[ declare
3 declarations ]
4begin
5 statements
6exception
7 when condition [ or condition ... ] then handler_statements
8 [ when condition [ or condition ... ] then handler_statements
9 ... ]
10end;示例:
1do $$
2declare
3 i integer := 1;
4begin
5 i := i / 0;
6exception
7 when division_by_zero then
8 raise notice '除零错误!';
9 when others then
10 raise notice '其他错误!';
11end $$;自定义函数 ¶
使用create function创建自定义函数:
1-- create 表示创建函数
2-- or replace 表示替换函数定义
3-- name 是函数名;括号内是参数,多个参数使用逗号分隔
4-- argmode 可以是 in(输入)、out(输出)、inout(输入输出)或者 variadic(数量可变),默认为 in
5-- argname 是参数名称;argtype 是参数的类型;default_expr是参数的默认值
6-- rettype 是返回数据的类型
7-- as 后面是函数的定义,和上文中的匿名块相同
8-- language 指定函数实现的语言,也可以是其他过程语言
9
10create [ or replace ] function
11 name ( [ [ argmode ] [ argname ] argtype [ { default | = } default_expr ]
12[, ...] ] )
13 returns rettype
14as $$
15declare
16 declarations
17begin
18 statements;
19 ...
20end; $$
21language plpgsql;示例:
1-- 新建函数
2create or replace function get_count(creator_id integer)
3returns integer
4as $$
5declare
6 ln_count integer;
7begin
8 select count(*) into ln_count
9 from sys_code
10where creator = cast(creator_id as varchar);
11 return ln_count;
12end; $$
13language plpgsql
14
15-- 调用函数
16select get_count(1)
17
18-- 删除函数
19drop function get_count(integer);PL/pgSQL 函数支持重载(Overloading),也就是相同的函数名具有不同的函数参数
存储过程 ¶
PostgreSQL 11 增加了存储过程,使用 create procedure语句创建:
1create [ or replace ] procedure
2 name ( [ [ argmode ] [ argname ] argtype [ { default | = } default_expr ]
3[, ...] ] )
4as $$
5declare
6 declarations
7begin
8 statements;
9 ...
10end; $$
11language plpgsql;存储过程的定义和函数主要的区别在于没有返回值,其他内容都类似;存储过程的调用使用call语句
示例:
1-- 新建存储过程
2create or replace procedure update_emp(
3 p_empid in integer,
4 p_salary in numeric,
5 p_phone in varchar)
6as $$
7begin
8 update employees
9 set salary = p_salary,
10 phone_number = p_phone
11 where employee_id = p_empid;
12end; $$
13language plpgsql;
14
15-- 调用存储过程
16call update_emp(100, 25000, '515.123.4560')
17
18-- 删除存储过程
19drop procedure update_emp(integer,numeric,varchar);事务管理
在存储过程内部,可以使用 COMMIT 或者 ROLLBACK 语句提交或者回滚事务
1-- 新建测试表
2create table test(a int);
3
4-- 创建存储过程
5create procedure transaction_test() as $$
6begin
7 for i in 0..9 loop
8 insert into test (a) values (i);
9 if i % 2 = 0 then
10 commit;
11 else
12 rollback;
13 end if;
14 end loop;
15end
16$$
17language plpgsql;
18
19-- 调用存储过程
20call transaction_test();
21
22-- 查看结果,只有偶数被存入了表中
23select * from test;
24
25-- 删除测试表
26drop table test触发器 ¶
概述 ¶
PostgreSQL 触发器(trigger)是一种特殊的函数,当某个数据变更事件(INSERT、UPDATE、DELETE 或者 TRUNCATE 语句)或者数据库事件(DDL 语句)发生时自动执行,而不是由用户或者应用程序进行调用
触发器分类:
数据变更触发器(DML 触发器):基于某个表或者视图数据变更的触发器;
支持两种级别的触发方式,
行级(row-level)触发器和语句级(statement-level)触发器,两者的区别在于触发的时机和触发次数事件触发器(DDL 触发器):基于数据库事件的触发器
触发器可以在事件发生之前(BEFORE)或者之后(AFTER)触发。如果在事件之前触发,它可以跳过针对当前行的修改,甚至修改被更新或插入的数据;如果在事件之后触发,触发器可以获得所有的变更结果
管理触发器 ¶
创建
创建PostgreSQL触发器分为两步
使用 CREATE FUNCTION 语句创建一个触发器函数
plsql1create [ or replace ] function trigger_function () 2 returns trigger 3as $$ 4declare 5 declarations 6begin 7 statements; 8 ... 9end; $$ 10language plpgsql;触发器函数与普通函数的区别在于它没有参数,并且返回类型为 trigger
使用 CREATE TRIGGER 语句将该函数与表进行关联
plsql1-- event 可以是 insert、update、delete 或者 truncate 2-- 触发器可以在事件之前(before)或者之后(after)触发 3-- instead of 只能用于替代视图上的 insert、update 或者 delete 操作 4-- for each row 表示行级触发器 5-- for each statement 表示语句级触发器 6-- when 用于指定一个额外的触发条件,满足条件才会真正支持触发器函数 7create trigger trigger_name 8{before | after | instead of} {event [or ...]} 9 on table_name 10 [for [each] {row | statement}] 11 [when ( condition ) ] 12 execute function trigger_function;
在触发器函数的内部,系统自动创建了许多特殊的变量:
- NEW :类型为 RECORD,代表了行级触发器 INSERT、UPDATE 操作之后的新数据行。对于 DELETE 操作或者语句级触发器而言,该变量为 null
- OLD:类型为 RECORD,代表了行级触发器 UPDATE、DELETE 操作之前的旧数据行。对于 INSERT 操作或者语句级触发器而言,该变量为 null
- TG_NAME:触发器的名称
- TG_WHEN:触发的时机,例如 BEFORE、AFTER 或者 INSTEAD OF
- TG_LEVEL:触发器的级别,ROW 或者 STATEMENT
- TG_OP:触发的操作,INSERT、UPDATE、DELETE 或者 TRUNCATE
- TG_RELID:触发器所在表的 oid
- TG_TABLE_NAME:触发器所在表的名称
- TG_TABLE_SCHEMA:触发器所在表的模式
- TG_NARGS:创建触发器时传递给触发器函数的参数个数
- TG_ARGV[]:创建触发器时传递给触发器函数的具体参数,下标从 0 开始。非法的下标(小于 0 或者大于等于 tg_nargs)将会返回空值
修改
postgreSQL提供了 alter trigger 语句,用于修改触发器
1-- 暂时只能修改触发器名称
2alter trigger name on table_name rename to new_name;禁用
默认创建的触发器处于启用状态;可以使用语句禁用或者启用某个触发器、某个表上的所有触发器或用户触发器(不包括内部生成的约束触发器)
1alter table table_name {enable | disable} trigger {trigger_name | all | user};删除
删除触发器时,先删除触发器再删除触发器函数
1-- if exists 存在才删除,避免报错
2-- cascade 表示级联删除依赖于该触发器的对象
3-- restrict 表示如果存在依赖于该触发器的对象返回错误,默认为restrict
4drop trigger [if exists] trigger_name on table_name [restrict | cascade];示例
1-- 创建两张 test 表
2create table test_a(a varchar(20),b varchar(20));
3create table test_b(a varchar(20),b varchar(20));
4
5-- 创建触发器函数,实现插入时将数据同样插入test_b表
6create or replace function track_test()
7returns trigger as $$
8begin
9 if tg_op = 'INSERT' then
10 insert into test_b values ( new.a,new.b);
11 end if;
12 return new;
13end; $$
14language plpgsql;
15
16-- 创建触发器将触发器函数和表相关联,实现触发器函数track_test和表test_a关联,触发时机为增删改之前
17create trigger trg_track_test
18 before insert or update or delete
19 on test_a
20 for each row
21 execute function track_test();
22
23-- 测试,向test_a表插入数据,然后查看test_b表
24insert into test_a values ('a','b');
25select * from test_a;
26select * from test_b;
27
28-- 删除触发器、触发器函数
29drop trigger if exists trg_track_test on test_a;
30drop function if exists track_test;
31
32-- 删除测试表
33drop table test_a;
34drop table test_b;服务器配置 ¶
通过配置文件影响参数 ¶
设置这些参数最基本的方法是编辑postgresql.conf文件, 它通常被保存在数据目录中(当数据库集簇目录被初始化时,一个默认的拷贝将会被安装在那里
除postgresql.conf之外,PostgreSQL 数据目录还包含一个文件postgresql.auto.conf,它是自动编辑,保存了通过ALTER SYSTEM命令提供的设置。 postgresql.auto.conf中的设置会覆盖postgresql.conf中的设置
通过SQL影响参数 ¶
ALTER SYSTEM命令提供了一种改变全局默认值
ALTER DATABASE命令允许针对一个数据库覆盖其全局设置
ALTER ROLE命令允许用用户指定的值来覆盖全局设置和数据库设置
SHOW命令允许察看所有参数的当前值。对应的函数是 current_setting(setting_name text)
SET命令允许修改对于一个会话可以本地设置的参数的当前值, 它对其他会话没有影响。对应的函数是 set_config(setting_name, new_value, is_local)