介绍 使用公用表达式可以让语句更加清晰简练. 除此之外,根据微软对CTE好处的描述,可以归结为四点:

  • 可以定义递归公用表表达式(CTE)
  • 当不需要将结果集作为视图被多个地方引用时,CTE可以使其更加简洁
  • GROUP BY语句可以直接作用于子查询所得的标量列
  • 可以在一个语句中多次引用公用表表达式(CTE)

其他: 窗口函数联机分析处理: rank max avg 等函数: https://zhuanlan.zhihu.com/p/143561453

1.复杂查询CTE

  1. 定义一个简单的cte
  2. postgres=# with t as (select generate_series(1,3)) select * from t;
  3. generate_series
  4. -----------------
  5. 1
  6. 2
  7. 3

2.递归查询CTE

  1. 使用recursive属性引用自己的输出,从而实现递归,例如从1加到100
  2. postgres=# with recursive t (x) as (select 1 union select x+1 from t where x<100) select sum(x) from t;
  3. sum
  4. ------
  5. 5050
  6. (1 row)
  7. 相关案例:
  8. #说明:当给定一个id时能给它完整的地名,例如当id=7时,地名时:中国辽宁沈阳和平区,当id=5时,地名是:中国辽宁大连,建表语句如下:
  9. create table test_ared(id int4,name varchar(32),fatherid int4);
  10. insert into test_ared values (1,'中国',0);
  11. insert into test_ared values (2,'辽宁',1);
  12. insert into test_ared values (3,'山东',1);
  13. insert into test_ared values (4,'沈阳',2);
  14. insert into test_ared values (5,'大连',2);
  15. insert into test_ared values (6,'济南',3);
  16. insert into test_ared values (7,'和平区',4);
  17. insert into test_ared values (8,'沈河区',4);
  18. #使用postgresqlwith查询索引ID7以及以上的所有父节点:
  19. with recursive a as (select test_ared.* from test_ared where id=7 union all select test_ared.* from test_ared,a where test_ared.id=a.fatherid) select * from a order by a.id;
  20. #将输出结果name字段合并成“中国辽宁沈阳和平区”,这里使用string_agg函数:
  21. with recursive a as (select test_ared.* from test_ared where id=7 union all select test_ared.* from test_ared,a where test_ared.id=a.fatherid) select string_agg(name,'') from (select name from a order by id)n;
  22. #以上是查询当前节点的所有父节点,现在查询当前节点以及其下的子节点,如查找沈阳市及管辖的区:
  23. with recursive a as (select test_ared.* from test_ared where id=4 union all select test_ared.* from test_ared,a where test_ared.fatherid=a.id) select * from a order by a.id;

3.批量插入

  1. 使用insert intoselect…进行批量插入:
  2. · 通过表数据批量插入:
  3. #例如创建一张表结构和user_ini相同的表并插入user_ini表的全量数据:
  4. create table user_ini(user_id int8,user_name text);
  5. insert into user_ini(user_id,user_name) values(1,'张三'),(2,'李四');
  6. #创建表结构相同的表,并进行数据批量插入:
  7. create table tb1_batch1 (user_id int4,user_name text);
  8. insert into tb1_batch1(user_id,user_name) select user_id,user_name from user_ini;
  9. · 通过generate_series函数实现批量插入数据:
  10. #创建表结构:
  11. create table tb1_batch2(id int4,info text);
  12. #进行批量数据的插入:
  13. insert into tb1_batch2(id,info) select generate_series(1,5),'batch2';
  14. 使用insert into values(),()…()方式进行批量插入:
  15. #创建一张表:
  16. create table tb1_batch3(id int4,info text);
  17. #进行插入数据:
  18. insert into tb1_batch3(id,info) values (1,'a'),(2,'b'),(3,'c');
  19. 通过cppy\copy元命令进行插入数据:
  20. #说明:通过copy拷贝数据效率会高一些:
  21. #创建一张表,并插入数据:
  22. create table tb1_batch4(id int4,info text);
  23. insert into tb1_batch4(id,info) select n,n||'_test' from generate_series(1,10)n;
  24. #导出数据:
  25. copy tb1_batch4 to '/tmp/tb1_batch4';
  26. #导入数据:
  27. truncate tb1_batch4;
  28. copy tb1_batch4 from '/tmp/tb1_batch4';

4.returning 返回修改的数据

  1. returning返回插入的数据:
  2. #创建一张表:
  3. create table test_r1(id serial,flag char(11));
  4. #插入数据,此时发现可以返回插入的数据:
  5. postgres=# insert into test_r1(flag) values('b') returning *;
  6. id | flag
  7. ----+-------------
  8. 2 | b
  9. (1 row)
  10. insert 1
  11. returning返回更新后的数据:
  12. # 例如更新一行数据:
  13. postgres=# update test_r1 set flag='c' where id=2 returning *;
  14. id | flag
  15. ----+-------------
  16. 2 | c
  17. (1 row)
  18. update 1
  19. returning返回删除的数据:
  20. # 例如删除一行数据:
  21. postgres=# delete from test_r1 where id=1 returning *;
  22. id | flag
  23. ----+-------------
  24. 1 | a
  25. (1 row)
  26. delete 1

5.upsert解决冲突

  1. 解决插入冲突问题:
  2. # 创建一张表:
  3. create table test (user_name text primary key, login_cnt int4,last_login_time timestamp(0) without time zone);
  4. # 插入一行数据:
  5. insert into test (user_name,login_cnt) values ('francs',1);
  6. # 再次重复插入,此时会发现会报错,因为设置了主键:
  7. insert into test (user_name,login_cnt) values ('matiler',1),('francs',1);
  8. ERROR: duplicate key value violates unique constraint "test_pkey"
  9. DETAIL: Key (user_name)=(francs) already exists.
  10. # 解决报错问题:
  11. # on conflict(user_name)字段,如果出现冲突,那么则执行do update set之后的动作,并且记录时间:
  12. insert into test (user_name,login_cnt) values ('matiler',1),('francs',1) on conflict(user_name) do update set login_cnt=test.login_cnt+excluded.login_cnt,last_login_time=now();
  13. #也就可以定义数据冲突后,什么也不做,只将可以插入的行进行插入,这时就需要指定do nothing属性:
  14. insert into test(user_name,login_cnt) values ('matiler',1),('francs',1) on conflict(user_name) do nothing;

6.数据抽样

6.1 system 抽样方式:

数据抽样(TABLESAMPLE)在数据处理方面经常用到, 特别是当表数据量比较大时,随机查询表中一定数量记录的操作很常见

PostgreSQL早在9.5版时就已经提供了 TABLESAMPLE数据抽样功能,9.5版前通常通过ORDER BY random()方式实现数据抽样,这种方式虽然在功能上满足随机返回指定行数据,但性能很低,如下所示:

  1. mydb=> SELECT * FROM user_ini ORDER BY random() LIMIT 1;
  2. id | user_id | user_name | create_time
  3. -------------+---------+-----------+-------------------------------
  4. 500449 | 768810 | 2TY6P4 | 2021-08-05 15:59:32.294761+08
  5. (1 row)
  6. mydb=> SELECT * FROM user_ini ORDER BY random() LIMIT 1;
  7. id | user_id | user_name | create_time
  8. -------------+---------+-----------+-------------------------------
  9. 324823 | 740720 | 07SKCU | 2021-08-05 15:59:29.913984+08 (1 row)

表user_ini数据量为100万,从100万随机取一条上述SQL的执行时间为367ms,这种方法进行了全表扫描和排序,效率非常低,当表数据量大时,性能几乎无法接受。

9.5版本以后PostgreSQL支持TABLESAMPLE数据抽样,语法: SELECT … FROM table_name TABLESAMPLE sampling_method ( argument [, …])[ REPEATABLE ( seed )]

6.2 bernoull抽样方式:

BERNOULLI抽样方式随机抽取表的数据行,并返回指定百分比数据,BERNOULLI抽样方式基于数据行级别,理论上被抽样表的每行记录被检索的概率是一样的,因此 BERNOULLI抽样方式抽取的数据相比SYSTEM抽样方式具有更好的随机性,但性能上相比SYSTEM抽样方式低很多。

BERNOULLI抽样方式,同样基于test_sample测试表。 设置抽样方式为BERNOULLI,抽样因子为0.01,如下所 示:

  1. mydb=> EXPLAIN ANALYZE SELECT * FROM test_sample TABLESAMPLE BERNOULLI (0.01);
  2. QUERY PLAN
  3. --------------------------------------------------------------------------------
  4. Sample Scan on test_sample (cost=0.00..14020.50 rows=150 width=45) (actual time=0.025..22.541 rows=152 loops=1)
  5. Sampling: bernoulli ('0.01'::real)
  6. Planning time: 0.063 ms
  7. Execution time: 22.569 ms
  8. (4 rows)