前言
这篇文章是我在查阅Postgresql13官方文档时写下的笔记,主要记录与Mysql不同的地方,可通过这篇文章快速了解Pgsql和Mysql的区别,具体学习还是要查阅官方文档。
安装
开启远程登录
su postgres 切换到postgres用户
cd ~ 进入用户目录,postgresql默认在这里
cd /13/data 进入data目录,找到postgresql.conf文件,修改listen_addresses = '*',这个值默认是localhost
找到pg_hba.conf,添加一行 host all all 0.0.0.0/0 scram-sha-256
scram-sha-265如果改成trust,会导致任何密码或无密码都能登录
字符串转义问题
字符串用单引号包围,但如果字符串含有单引号、斜杠等需要转义的字符就会比较麻烦。在postgresql中可以使用来表达字符串,用$$包围的字符串一般不需要转义。
select $$123'/\$$
查询结果:123'/\
生成列
相当于mysql的虚拟列,但postgresql目前只支持存储的生成列,不支持虚拟的生成列。
存储的生成列在写入(插入或更新)时进行计算,并像正常列一样占用存储空间。虚拟生成的列不占用任何存储空间,并且在读取时会对其进行计算。
CREATE TABLE people (
...,
height_cm numeric,
height_in numeric GENERATED ALWAYS AS (height_cm / 2.54) STORED
);
copy语句
从文件导入大批数据到数据库,使用copy语句,效率高于insert
文件复制到表
copy users from '~/data_export'
文件格式如下:
admin 123456 1
test 123465 2
test 123456 3
与列顺序一致,用tab分隔
表复制到文件
copy users to '~/data_export'
相当于导出功能,导出格式同上
表复制到压缩文件
COPY users TO PROGRAM 'gzip > ~/users_data.gz';
自增字段
postgresql不像mysql有一个自增选项,它是通过数据类型来实现自增的。需要自增时选择以下几种数据类型即可。
| 伪类型 | 存储大小 | 范围 |
|---|---|---|
| SMALLSERIAL | 2字节 | 1 到 32,767 |
| SERIAL | 4字节 | 1 到 2,147,483,647 |
| BIGSERIAL | 8字节 | 1 到 922,337,2036,854,775,807 |
自增字段重复问题
插入数据时如果我们设置了自增字段的值,postgresql并不会把自增序列+1。比如我插入第二条记录,如果我没有设置自增字段的值,那么自增序列会+1变成2,但我设置了自增字段的值,比如设置为2,自增序列就不会+1(虽然字段值是2,但此时自增序列还是1)这就导致我们下次不设置自增字段的值时,自增序列+1变成2,但2已经被上一条记录使用了。
解决办法
首先是不要给自增字段设置值了,给空值让它自增,不然也违反了我们的自增设计。但如果不小心设置了值,就用下面的办法解决
select setval('users_id_seq',(select max(id) from users))
使用这行sql设置自增序列的值
select max(id) from users
查找users表的最大id,用它作为自增序列的最新值
users_id_seq
用来指定某个自增序列,这个值可以在表的ddl语句中查看。
id bigint NOT NULL DEFAULT nextval('users_id_seq'::regclass)
id是自增字段,nextval后面就有自增序列标识符users_id_seq
分组聚合
group by
如果使用了group by语句,则只能查询group by中的列。mysql8也是这样,只不过mysql能够通过修改配置文件避免这种情况,但也只能查组内第一条记录,实际使用我们会和多表连接一起用。
grouping sets
分组集,与group by相似,但可以实现更高级的效果。以下代码使用grouping sets统计品牌销量、尺码销量、所有销量。这个需求如果使用group by需要查询多次,但现在可以在一条sql上完成。
=> SELECT * FROM items_sold;
brand | size | sales
-------+------+-------
Foo | L | 10
Foo | M | 20
Bar | M | 15
Bar | L | 5
(4 rows)
=> SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
brand | size | sum
-------+------+-----
Foo | | 30
Bar | | 20
| L | 15
| M | 35
| | 50
(5 rows)
rollup
rollup也是用来生成分组集,可以认为是grouping sets的快捷方式。
rollup(c1,c2,c3)生成以下分组集
(c1, c2, c3)
(c1, c2)
(c1)
()
rollup通常用于有层级关系的统计需求,看个例子就懂了
select y,m,d,sum(sales) from a group by rollup(y,m,d) order by y,m,d
| y | m | d | sum | 说明 |
|---|---|---|---|---|
| 2021 | 3 | 1 | 10 | |
| 2021 | 3 | 2 | 11 | |
| 2021 | 3 | 3 | 15 | |
| 2021 | 3 | 4 | 18 | |
| 2021 | 3 | 54 | 三月份销量 | |
| 2021 | 4 | 2 | 25 | |
| 2021 | 4 | 3 | 5 | |
| 2021 | 4 | 10 | 31 | |
| 2021 | 4 | 61 | 四月份销量 | |
| 2021 | 115 | 2021年销量 | ||
| 115 | 全部销量 |
grouping
grouping用于区分行是来自哪个分组,这里用上面rollup的例子改造下。
select y,m,d,grouping(y,m,d),sum(sales) from a group by rollup(y,m,d) order by y,m,d
| y | m | d | sum | grouping | 说明 |
|---|---|---|---|---|---|
| 2021 | 3 | 1 | 10 | 0 | |
| 2021 | 3 | 2 | 11 | 0 | |
| 2021 | 3 | 3 | 15 | 0 | |
| 2021 | 3 | 4 | 18 | 0 | |
| 2021 | 3 | 54 | 1 | 三月份销量 | |
| 2021 | 4 | 2 | 25 | 0 | |
| 2021 | 4 | 3 | 5 | 0 | |
| 2021 | 4 | 10 | 31 | 0 | |
| 2021 | 4 | 61 | 1 | 四月份销量 | |
| 2021 | 115 | 3 | 2021年销量 | ||
| 115 | 7 | 全部销量 |
可以看到grouping分别返回了0/1/3/7,grouping中的每一列,如果都参与了分组,那么都是0,如果某一列没有参与分组,那一列就是1。这里的0/1是二进制,二进制转十进制就得到了0/1/3/7这些数字。
比如ymd分组:000=>0
ym分组:001=>1
y分组:011=>3
cube
cube与rollup类似,只不过分组集不一样,cube会生成所有组合结果
cube (c1,c2,c3)分组集如下
(c1, c2, c3)
(c1, c2)
(c2, c3)
(c1,c3)
(c1)
(c2)
(c3)
()
over
over可以理解为分组但不聚合,利用它可以实现mysql中无法直接实现的效果,比如mysql的group by虽然可以查询其他列,但查询出来的仅仅是组内的第一条记录。而over可以查询组内每一条数据,并且可以使用聚合函数,比如sum、avg、count等。
示例
- 按照用户等级分组,计算组内余额总和。
select id,account,password,level,sum(money) over(partition by level) from users
| id | account | password | level | sum |
|---|---|---|---|---|
| 1 | xiaoming | 123456 | 1 | 189 |
| 2 | zhangsan | 123465 | 1 | 189 |
| 3 | lisi | 123456 | 2 | 70 |
| 4 | wangwu | 123465" | 3 | 99 |
可以看到,不仅统计出了余额总和,还查询出了组内每一条数据。
- 上面的语句再加一个order by,结果会有点意思
select id,account,password,level,sum(money) over(partition by level order by money) from users
| id | account | password | level | sum |
|---|---|---|---|---|
| 1 | xiaoming | 123456 | 1 | 89 |
| 2 | zhangsan | 123465 | 1 | 189 |
| 3 | lisi | 123456 | 2 | 70 |
| 4 | wangwu | 123465" | 3 | 99 |
sum列的结果,变成了每一步相加的结果的排序结果
- 按用户等级分组,统计组内余额排名
select id,account,password,level,money,rank() over(partition by level order by money desc) from users
| id | account | password | level | money | rank |
|---|---|---|---|---|---|
| 1 | xiaoming | 123456 | 1 | 100 | 1 |
| 2 | zhangsan | 123465 | 1 | 89 | 2 |
| 3 | lisi | 123456 | 2 | 70 | 1 |
| 4 | wangwu | 123465 | 3 | 99 | 1 |
- rank() 如果有余额相同的情况,会并列排名,假如并列第二名,那么第三名会取消,下一个排名从第四开始。
- dense_rank() 也可以并列排名,但不会取消下一名。像上面这种情况,第三名不会被取消。
- row_number() 不并列排名,余额相同的情况下按照先后顺序排名
- 上面几个排名方法,会把空值当成最大的,所以查询时需要注意一下。可以用where语句过滤空值
over()不加partition by的情况
select id,account,password,level,money,sum(money) over() from users
| id | account | password | level | money | sum |
|---|---|---|---|---|---|
| 1 | xiaoming | 123456 | 1 | 100 | 358 |
| 2 | zhangsan | 123465 | 1 | 89 | 358 |
| 3 | lisi | 123456 | 2 | 70 | 358 |
| 4 | wangwu | 123465 | 3 | 99 | 358 |
效果就是把整个表作为一个分组,不加partition by的时候,还是可以加order by
- 复用
over()
select sum(money) over w,avg(money) over w from users where money is not null window w as (partition by level)
window w as (partition by level)定义了over,名字是w
sum(money) over w,avg(money) over w进行了复用
表继承
表继承可以让子表继承父表的结构和部分约束(检查约束、非空约束),唯一约束、主键、外键不会被继承,表继承实际上是把面向对象的思想引入表设计中。
语法
INHERITS (父表)
CREATE TABLE cities (
name text,
population real,
elevation int -- (in ft)
);
CREATE TABLE capitals (
state char(2) UNIQUE NOT NULL
) INHERITS (cities);
数据存放在哪?
- 往子表添加数据时,数据是存放在子表的
- 往父表添加数据时,数据存放在父表,子表查不到这些数据
- 查询父表时,也能查到子表的数据,实际上他是先查子表,再跟父表合并实现的,类似多表连接,父表中并没有存放子表的数据。
- 查询子表不需要先查父表,因为数据本身就存放在子表
应用场景
- 每个表都有通用字段,把通用字段放到父表,例如is_delete、create_time、modify_time
- postgresql的分区表似乎使用表继承来实现的,后面再介绍分区表
权限控制
postgresql用户、组都是role,role如果有多个成员,那它就是组
create role prod 创建名为prod的角色
grant select,insert,update,delete on all tables in schema public to prod 授权所有表使用增删查改功能
grant select,insert,update,delete on table users to prod 授权users表使用增删查改功能
revoke select on users from prod 撤销users表的查询权限
revoke all on all tables in schema public from test 撤销所有表权限
grant usage on all sequences in schema public to prod 授权所有序列使用currval和nextval功能,否则自增字段无法工作
grant USAGE on sequence users_id_seq to prod授权users_id_seq序列使用currval和nextval功能
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT select,insert,update,delete ON TABLES TO prod 授权新建表使用增删查改功能
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT usage ON sequences TO prod 授权新建序列使用currval和nextval功能
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE SELECT ON TABLES FROM prod 撤销新建表的查询权限
ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE usage ON sequences FROM prod 撤销新建序列的使用权限
grant prod to prod_user将用户prod_user加入权限组prod,prod_user就拥有权限组的权限
用户相关
ALTER USER postgres PASSWORD 'Pg123456###'修改用户密码
DROP user username 删除用户,删除前需要撤销此用户所有权限
命令行登录
su postgres 切换到postgres用户
psql 直接登录
挖矿漏洞
postgresql有远程执行漏洞,密码太简单很有可能会被挖矿病毒入侵。我已亲身体验一番~
returning语句
语法与select类似,可用在insert、update、delete语句
update users set password='13246546' where account='sad' returning * 返回修改后的行
insert into users(account,password) values('sad','sad132')returning id 返回新插入行的id
lateral语句
lateral使子查询可以引用前面的from项目提供的列。(没有lateral,每个子查询都是独立评估的,因此不能交叉引用任何其他from项目。
SELECT * FROM foo,LATERAL(SELECT * FROM bar WHERE bar.id = foo.bar_id)ss
交集、并集、差异
select * from a except all select * from b 查询在a表中存在但在b表中不存在的记录
select * from a union all select * from b 查询a表和b表的并集,也就是返回两个表的记录
select * from a intersect all select * from b 查询a表和b表中都有的记录
上面几个sql都加了all,不加all会去除重复记录,根据自己需求来
排序
使用order by排序时,我们会发现空值永远比其他值大,很多场景下这种排序规则都不太合理,以往我的解决方法是过滤空值,空值不参与排序,现在postgresql提供了其他方法。
select * from a order by sales desc nulls first 空值最大,默认就是这种
select * from a order by sales desc nulls last 空值最小
with as
with as 用于简化复杂查询,网上有用这个语句优化查询速度的博客,所以可以考虑用这个语句来构建复杂查询。
WITH regional_sales AS (
SELECT region, SUM(amount) AS total_sales
FROM orders
GROUP BY region
), top_regions AS (
SELECT region
FROM regional_sales
WHERE total_sales > (SELECT SUM(total_sales)/10 FROM regional_sales)
)
SELECT region,
product,
SUM(quantity) AS product_units,
SUM(amount) AS product_sales
FROM orders
WHERE region IN (SELECT region FROM top_regions)
GROUP BY region, product;
还可以在with语句中修改数据
WITH moved_rows AS (
DELETE FROM products
WHERE
"date" >= '2010-10-01' AND
"date" < '2010-11-01'
RETURNING *
)
INSERT INTO products_log
SELECT * FROM moved_rows;