PostgreSQL 高级特性

PostgreSQL 高级特性

WITH查询

是PostgreSQL支持的高级SQL特性之一,这一特性常称CTE(Common Table Expressions),WITH查询在复杂查询中定义一个辅助语句(可理解成在一个查询中定义的临时表),这一特性常用于复杂查询或递归查询应用场景。

WITH t as(
SELECT generate_series(1,3)
)
SELECT * FROM t;
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;     

递归查询使用CTE

使用RECURSIVE属性可以引用自己的输出

# x从1开始,union加1后的值,循环直到x小于5结束,之后计算x值的总和
WITH recursive t (x) as(
	SELECT 1
  UNION
  SELECT x+1
  FROM t
  WHERE x<5
)
SELECT sum(x) FROM t; 

WITH recursive t (x) as(
  SELECT 1
  UNION
  SELECT x+1
  FROM t
  WHERE x<5
)
SELECT * FROM t; 
# 创建表
CREATE TABLE test_area(id int4,name varchar(32),fatherid int4);

# 插入数据
INSERT INTO test_area VALUES(1,'中国',0);
INSERT INTO test_area VALUES(2,'辽宁',1);
INSERT INTO test_area VALUES(3,'山东',1);
INSERT INTO test_area VALUES(4,'沈阳',2);
INSERT INTO test_area VALUES(5,'大连',2);
INSERT INTO test_area VALUES(6,'济南',3);
INSERT INTO test_area VALUES(7,'和平区',4);
INSERT INTO test_area VALUES(8,'沈河区',4);
# 查询ID为7以及以上的所有父节点
WITH RECURSIVE r AS(
	SELECT * FROM test_area WHERE id = 7
  UNION ALL
  SELECT test_area.* FROM test_area,r WHERE test_area.id =r.fatherid
)
SELECT * FROM r ORDER BY id;

# 
WITH RECURSIVE r AS(
	SELECT * FROM test_area WHERE id = 7
  UNION ALL
  SELECT test_area.* FROM test_area,r WHERE test_area.id =r.fatherid
)
SELECT string_agg(name,'') FROM (SELECT name FROM r ORDER BY id) n;

# 查找当前节点以及其下的所有子节点
WITH RECURSIVE r AS(
	SELECT * FROM test_area WHERE id = 4
  UNION ALL
  SELECT test_area.* FROM test_area,r WHERE test_area.fatherid =r.id
)
SELECT * FROM r ORDER BY id;

批量插入

INSERT INTO…SELECT…

通过表数据或函数批量插入

#创建表
CREATE TABLE tbl_batch1(user_id int8,user_name text);

#插入数据
INSERT INTO tbl_batch1(user_id,user_name) 
SELECT user_id,user_name FROM user_ini;

# 创建表
CREATE TABLE tbl_batch2(id int4,info text);

# 插入数据
INSERT INTO tbl_batch2(id,info)
SELECT generate_series(1,5),'batch2';

INSERT INTO VALUES(),(),…()

CREATE TABLE tbl_batch3(id int4,info text);

INSERT INTO tbl_batch3(id,info) VALUES(1,'a'),(2,'b'),(3,'c');

SELECT * FROM tbl_batch3;

这种批量插入方式,相比一条SQL插入一条数据饿方式能减少数据库的交互,减少数据库WAL(Write-Ahead Logging)日志的生成,提升插入效率

COPY或\COPY元命令

# 创建表
CREATE TABLE tbl_batch4(
	id int4,
  info text,
  create_time timestamp(6) with time zone default clock_timestamp()
);

# 插入数据
INSERT INTO tbl_batch4(id,info)SELECT n,n||'_batch4' FROM generate_series(1,10000000) n;
# 进入postgres的bin目录
cd /Library/PostgreSQL/13/bin

# psql连接数据库
./psql -h 192.168.1.41 -p 5432 gis postgres

# 开启时间
\timing

# 客户端导出数据
\copy public.tbl_batch4 TO '/Users/sungang/Documents/data/scripts/tbl_batch4.txt';

# 清空表
TRUNCATE TABLE public.tbl_batch4;

# 客户端导入数据
\copy public.tbl_batch4 FROM '/Users/sungang/Documents/data/scripts/tbl_batch4.txt';

# 查看表大小
\dt+ tbl_batch4

# 查看指定索引大小
\di+ tbl_batch4_pkey

RETURNING返回修改的数据

返回DML修改的数据

  • INSERT语句后接RETURNING属性返回插入的数据
  • UPDATE语句后接RETURNING属性返回更新后的新值
  • DELETE语句后接RETURNING属性返回删除的数据

不需要额外的SQL获取这些值

RETURNING返回插入的数据

# 创建表
CREATE TABLE test_r1(id serial,flag char(1));

# 插入数据
INSERT INTO test_r1(flag) VALUES ('a') RETURNING *;

INSERT INTO test_r1(flag) VALUES ('b') RETURNING id;

RETURNING返回更新后数据

SELECT * FROM test_r1 WHERE id=1;

UPDATE test_r1 SET flag='p' WHERE id=1 RETURNING *;

RETURNING返回删除的数据

DELETE FROM test_r1 WHERE id=2 RETURNING *;

UPSERT

特性是指INSERT…ON CONFLICT UPDATE,用来解决在数据插入过程中数据冲突的情况

# 创建表
CREATE TABLE user_logins(user_name text primary key,
                         login_cnt int4,
                         last_login_time timestamp(0) without time zone
                        );
                        
# 插入数据
INSERT INTO user_logins(user_name,login_cnt) VALUES('francs',1);

# 当产生冲突的时候解决问题
# 设置规则:当用户名数据冲突时将登陆次数login_cnt值加一,同时更新最近登陆时间last_login_time
INSERT INTO user_logins(user_name,login_cnt) VALUES ('matiler',1),('francs',1)
ON CONFLICT(user_name) DO UPDATE SET login_cnt=user_logins.login_cnt+EXCLUDED.login_cnt,last_login_time=now();

# 当冲突时啥也不做
INSERT INTO user_logins(user_name,login_cnt)
VALUES ('tutu',1),('francs',1) ON CONFLICT (user_name) DO NOTHING;

数据抽样

当表数量比较大时,随机查询表中一定数量纪录的操作很常见

ORDER BY random()

这种方式在功能上能满足随机返回指定行数据,但性能很低 ;这种方式进行了全表扫描和排序,效率非常低

SELECT * FROM user_ini ORDER BY random() LIMIT 1;

# 执行计划
EXPLAIN ANALYZE SELECT * FROM user_ini ORDER BY random() LIMIT 1;

SYSTEM抽样方式

SYSTEM抽样方式为随机抽取表上数据块上的数据,理论上被抽样表的每个数据块被检索的概率是一样的,SYSTEM抽样方式给予数据块级别,后接抽样参数,被选中的块上的所有数据将被检索

  • 创建测试表
CREATE TABLE test_sample(
  id int4,
  message text,
  create_time timestamp(6) without time zone default clock_timestamp()
);

# 插入150万条数据
INSERT INTO test_sample(id,message)
SELECT n,md5(random()::text) FROM generate_series(1,1500000) n;

# 抽样因子设置成0.01,返回 1500000*0.01%=150条
EXPLAIN ANALYZE SELECT * FROM test_sample TABLESAMPLE SYSTEM(0.01);

# 查看表占用的数据块数量
SELECT relname,relpages FROM pg_class WHERE relname='test_sample';

# 查看抽样数据的ctid
# citd 是表的隐藏列,括号的第一位表示逻辑数据块编号,第二位表示逻辑块上的数据的逻辑编号
SELECT ctid,* FROM test_sample TABLESAMPLE SYSTEM(0.01);

SYSTEM 抽样方式返回的数据以数据块为单位,被抽样的块上的所有数据被检索。

BERNOULLI抽样方式

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

EXPLAIN ANALYZE SELECT * FROM test_sample TABLESAMPLE BERNOULLI (0.01);

SELECT count(*) FROM test_sample TABLESAMPLE BERNOULLI(0.01);

BERNOULLI抽样对于数据行级别,数据位于不同的数据块上

SELECT ctid,* FROM test_sample TABLESAMPLE BERNOULLI(0.01);

SYSTEM抽样方式基于数据块级别,随机抽取表数据块上的记录,因此这种方式抽取的记录的随机性不是很好,但返回的数据以数据块为单位,抽样性能很高,适用于抽样效率优先的场景,例如抽样大小为上百GB的日志表;而BERNOULLI抽样方式基于数据行,相比SYSTEM抽样方式所抽样的数据随机性更好,但性能相比SYSTEM差很多,适用于抽样随机性优先的场景。读者可根据实际应用场景选择抽样方式。

聚合函数

常用聚合函数有avg()、sum()、min()、max()、count()

string_agg函数

简单地说string agg雨数能将结果集某个字段的所有行连接成字符串,并用指定delimiter分隔符分隔, expression表示要处理的字符类型数据;参数的类型为(text, text)或 (bytea, bytea), 函数返回的类型同输人参数类型一致, bytea 属于二进制类型,使用情况不 多。首先创建测试表并插人以下数据:

CREATE TABLE city (country character varying(64),city character varying(64)); INSERT INTO city VALUES ('中国','台北');

INSERT INTO city VALUES('中国','香港');

INSERT INTO city VALUES ('中国','上海');

INSERT INTO city VALUES ('日本', '东京'); 

INSERT INTO city VALUES (' 日本','大阪');
SELECT string_agg(city,',') FROM city ;

SELECT country,string_agg(city,',') FROM city GROUP BY country;

窗口函数

聚合函数将结果集进行计算并且通常返回一行。窗口函数也是基于结果集进行计算,与聚合丽数不同的是窗口函数不会将结果集进行分组计算并输出-行,而是将计算出的结果合并到输出的结果集上,并返回多行。使用窗口函数能大幅简化SQL代码。

PostgreSQL提供内置的窗口函数,例如row_ num()、 rank()、 lag() 等,除了内置的窗口函数外,聚合函数、自定义函数后接OVER属性也可作为窗口函数。

  • OVER表示窗口函数的关键字。

  • PARTITON BY属性对查询返回的结果集进行分组,之后窗口函数处理分组的数据。

  • ORDER BY属性设定结果集的分组数据排序。

avg() OVER()

聚合函数后接OVER属性的窗口函数表示在一个查询结果集上应用聚合函数,avg()聚合函数后接OVER属性的窗口函数,此窗口函数用来计算分组后数据的平均值

创建一张成绩表并插人测试数据

CREATE TABLE score (
  id serial primary key,
  subject character varying(32),
  stu_name character varying(32),
  score numeric(3,0)
);

INSERT INTO score(subject,stu_name,score) VALUES ('Chinese','francs',70);
INSERT INTO score(subject,stu_name,score) VALUES ('Chinese','matiler',70);
INSERT INTO score(subject,stu_name,score) VALUES ('Chinese','tutu',80);
INSERT INTO score(subject,stu_name,score) VALUES ('English','francs',90);
INSERT INTO score(subject,stu_name,score) VALUES ('English','matiler',75);
INSERT INTO score(subject,stu_name,score) VALUES ('English','tutu',60);
INSERT INTO score(subject,stu_name,score) VALUES ('Math','francs',80);
INSERT INTO score(subject,stu_name,score) VALUES ('Math','matiler',99);
INSERT INTO score(subject,stu_name,score) VALUES ('Math','tutu',65);
  • 查询每名学生学习成绩并且显示课程的平均分,通常是先计算出课程的平均分,然后score表与平均分表关联查询
SELECT s.subject,s.stu_name,s.score,tmp.avgscore FROM score s LEFT JOIN (SELECT subject,avg(score) avgscore FROM score GROUP BY subject)tmp ON s.subject =tmp.subject;

# 使用窗口函数
SELECT subject,stu_name,score,avg(score) OVER (PARTITION BY subject) FROM score;

row_number()

row_number()窗口函数对结果集分组后的数据标注行号,从1开始

SELECT row_number() OVER (partition by subject ORDER BY score desc),* FROM score;

如果不指定partition属性,row_number()窗口函数现实表所有记录的行号

SELECT row_number() OVER (ORDER BY id) AS rownum ,* FROM score;

rank()

rank()窗口函数和row_number()窗口函数相似,主要区别为当组内某行字段值相同时,行号重复并且行号产生间隙

SELECT rank() OVER(PARTITION BY subject ORDER BY score),* FROM score;

dense_rank()

dense_rank()窗口函数和row_number()窗口函数相似,主要区别为当组内某行字段值相同时,行号重复,但行号产生间隙

SELECT dense_rank() OVER(PARTITION BY subject ORDER BY score),* FROM score;

lag()

获取行偏移offset那行某个字段的数据

# 查询score表并获取向上偏移一行记录的id值
SELECT lag(id,1) OVER(),* FROM score;

# 查询score表并获取向上偏移两行记录的id值,并设置默认值
SELECT lag(id,2,1000) OVER(),* FROM score;

first_value()

取结果集每一个分组的第一行数据的字段值

# score表按课程分组后取分组的第一行的分数
SELECT frist_value(score) OVER(PARTITION BY subject),* FROM score;

# score表按课程分组同时取每门课的最高分
SELECT first_value(score) OVER(PARTITION BY subject ORDER BY score desc),* FROM score;

last_value()

取结果集每一个分组的最后一行数据的字段值

# score表按课程分组后取分组的最后一行的分数
SELECT last_value(score) OVER(PARTITION BY subject),* FROM score;

nth_value()

取结果集每一个分组的指定行数据的字段值

# score表按课程分组后取分组的第二行的分数
SELECT nth_value(score,2) OVER (PARTITON BY subject),* FROM score;

窗口函数别名的使用

SELECT avg(score) OVER(r),sum(score) OVER(r),* FROM score WINDOW r as(PARTITION BY subject);

猜你喜欢

转载自blog.csdn.net/qq_36213352/article/details/117119254