回顾
前面我们介绍了PostgreSQL的安装和对数据库的一些简单操作。我们知道,对于数据库的大部分操作都是查询操作,PostgreSQL具有丰富的数据类型,有一些特殊的数据类型操作与其他关系型数据库有所区别,这里我们来介绍一下对于这些数据类型的查询。
array查询
若Table_A和Table_B是多对多的关系,在其他关系型数据库中,我们会再创建一张表来保存Table_A与Table_B之间的关系,因为其他关系型数据库无法使用一条记录来保存这种关系。而在PostgreSQL中,则可以在Table_A和Table_B中分别使用一个数组来保存这种关系,就不需要额外的再创建一张表:在 Table_A 中字段 Column_A_B 是数组类型,Column_A_B 数组中的元素作为 Table_B 的外键;在 Table_B 中字段 Column_B_A 是数组类型,Column_B_A 数组中的元素作为 Table_A 的外键。
为了更加直观,我们举例说明。有两张表:学生表(student)和课程表(course),一个学生可以选多门课程,一门课程被多个学生选。
-- 学生表
CREATE TABLE student
(
id bigserial, --主键
name text, --姓名
courses bigint[], --所选课程
CONSTRAINT student_pk PRIMARY KEY(id)
);
-- 课程表
CREATE TABLE course
(
id bigserial, --主键
name text, --课程名称
e_name text, --课程英文名称
students bigint[], --选该课程的学生
CONSTRAINT course_pk PRIMARY KEY(id)
);
在student表和course表中分别插入几条数据:
-- student插入四条数据
INSERT INTO student
(name, courses)
VALUES
('ZhangSan', array[1,2]),
('LiSi', array[1,3]),
('WangWu', array[2,3]),
('ZhaoLiu', array[1,2,3]);
-- course插入三条数据
INSERT INTO course
(name, e_name, students)
VALUES
('语文', 'Chinese', array[1,2,4]),
('数学', 'Math', array[1,3,4]),
('英语', 'English', array[2,3,4]);
插入结果如下:
=> SELECT * FROM student;
id | name | courses
----+----------+---------
1 | ZhangSan | {1,2}
2 | LiSi | {1,3}
3 | WangWu | {2,3}
4 | ZhaoLiu | {1,2,3}
(4 rows)
=> SELECT * FROM course;
id | name | e_name | students
----+------+---------+----------
1 | 语文 | Chinese | {1,2,4}
2 | 数学 | Math | {1,3,4}
3 | 英语 | English | {2,3,4}
(3 rows)
目前在student表和course表中都只是保存了其关联的另一张表的id。那么问题来了,如果想查看其关联的表中的其他信息呢?如:想查看”ZhangSan”选了的课程名称有哪些?这就需要使用PostgreSQL中的关键词ANY了(或者使用”&&”)
SELECT s.id sid, c.id cid, s.name sname, s.courses, c.name cname, c.e_name ce_name
FROM student s
LEFT JOIN course c ON c.id = ANY(s.courses)
ORDER BY s.id;
或者:
SELECT s.id sid, c.id cid, s.name sname, s.courses, c.name cname, c.e_name ce_name
FROM student s
LEFT JOIN course c ON array[c.id] && s.courses
ORDER BY s.id;
查询结果如下:
sid | cid | sname | courses | cname | ce_name
-----+-----+----------+---------+-------+---------
1 | 1 | ZhangSan | {1,2} | 语文 | Chinese
1 | 2 | ZhangSan | {1,2} | 数学 | Math
2 | 1 | LiSi | {1,3} | 语文 | Chinese
2 | 3 | LiSi | {1,3} | 英语 | English
3 | 2 | WangWu | {2,3} | 数学 | Math
3 | 3 | WangWu | {2,3} | 英语 | English
4 | 1 | ZhaoLiu | {1,2,3} | 语文 | Chinese
4 | 2 | ZhaoLiu | {1,2,3} | 数学 | Math
4 | 3 | ZhaoLiu | {1,2,3} | 英语 | English
但这个结果好像跟我们预期的还有那么一点点不一样:我们希望的是,关联后的结果条数和student表相同(如分页查询学生所选课程),课程名称也是一个数组并且和课程的id数组对应。在这里,我们做一些改动,查询语句如下:
SELECT s.id sid, s.name sname, s.courses,
array(SELECT name FROM course WHERE id = ANY(s.courses)) cname,
array(SELECT e_name FROM course WHERE id = ANY(s.courses)) ce_name
FROM student s
ORDER BY s.id;
查询结果如下:
sid | sname | courses | cname | ce_name
-----+----------+---------+------------------+------------------------
1 | ZhangSan | {1,2} | {语文,数学} | {Chinese,Math}
2 | LiSi | {1,3} | {语文,英语} | {Chinese,English}
3 | WangWu | {2,3} | {数学,英语} | {Math,English}
4 | ZhaoLiu | {1,2,3} | {语文,数学,英语} | {Chinese,Math,English}
(4 rows)
是不是这样的结果更能让人接受?但是问题又来了:如果course表中有很多个字段,都需要像上面那样合并成一个,那岂不是要有N多个子查询?这样会不会效率很低?关于这个问题,笔者也没有什么太好的解决方案,欢迎大家提出更好的解决方案。目前的处理方式是创建一个视图,将这些信息组合的耗时操作集中处理,不会影响用户。视图的DDL如下:
CREATE VIEW course_info AS
SELECT
DISTINCT courses,
array(SELECT name FROM course WHERE id = ANY(s.courses)) cname,
array(SELECT e_name FROM course WHERE id = ANY(s.courses)) ce_name
FROM student s;
那么,在进行关联查询的时候,student表就直接和视图course_info进行关联,查询语句如下:
SELECT s.id sid, s.name sname, s.courses, ci.cname,ci.ce_name
FROM student s
LEFT JOIN course_info ci ON s.courses = ci.courses
ORDER BY s.id;
我们来看看使用视图作为关联和不使用的情况的执行效率对比:
- student表与course表关联:
EXPLAIN ANALYZE SELECT s.id sid, s.name sname, s.courses,
array(SELECT name FROM course WHERE id = ANY(s.courses)) cname,
array(SELECT e_name FROM course WHERE id = ANY(s.courses)) ce_name
FROM student s
ORDER BY s.id;
执行计划运行结果如下:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------
Sort (cost=32444.80..32446.80 rows=800 width=72) (actual time=0.114..0.115 rows=4 loops=1)
Sort Key: s.id
Sort Method: quicksort Memory: 26kB
-> Seq Scan on student s (cost=0.00..32406.22 rows=800 width=72) (actual time=0.051..0.101 rows=4 loops=1)
SubPlan 1
-> Bitmap Heap Scan on course (cost=9.59..20.24 rows=10 width=32) (actual time=0.010..0.011 rows=2 loops=4)
Recheck Cond: (id = ANY (s.courses))
-> Bitmap Index Scan on course_pk (cost=0.00..9.58 rows=10 width=0) (actual time=0.005..0.005 rows=2 loops=4)
Index Cond: (id = ANY (s.courses))
SubPlan 2
-> Bitmap Heap Scan on course course_1 (cost=9.59..20.24 rows=10 width=32) (actual time=0.005..0.005 rows=2 loops=4)
Recheck Cond: (id = ANY (s.courses))
-> Bitmap Index Scan on course_pk (cost=0.00..9.58 rows=10 width=0) (actual time=0.002..0.002 rows=2 loops=4)
Index Cond: (id = ANY (s.courses))
Total runtime: 0.168 ms
(15 rows)
- student表与course_info视图关联:
EXPLAIN ANALYZE SELECT s.id sid, s.name sname, s.courses, ci.cname,ci.ce_name
FROM student s
LEFT JOIN course_info ci ON s.courses = ci.courses
ORDER BY s.id;
执行计划运行结果如下:
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Sort (cost=40608.35..40610.35 rows=800 width=136) (actual time=0.076..0.076 rows=4 loops=1)
Sort Key: s.id
Sort Method: quicksort Memory: 26kB
-> Hash Right Join (cost=32440.22..40569.78 rows=800 width=136) (actual time=0.066..0.071 rows=4 loops=1)
Hash Cond: (s_1.courses = s.courses)
-> HashAggregate (cost=32412.22..40511.28 rows=200 width=32) (actual time=0.047..0.047 rows=4 loops=1)
-> Seq Scan on student s_1 (cost=0.00..32406.22 rows=800 width=32) (actual time=0.025..0.043 rows=4 loops=1)
SubPlan 1
-> Bitmap Heap Scan on course (cost=9.59..20.24 rows=10 width=32) (actual time=0.005..0.005 rows=2 loops=4)
Recheck Cond: (id = ANY (s_1.courses))
-> Bitmap Index Scan on course_pk (cost=0.00..9.58 rows=10 width=0) (actual time=0.002..0.002 rows=2 loops=4)
Index Cond: (id = ANY (s_1.courses))
SubPlan 2
-> Bitmap Heap Scan on course course_1 (cost=9.59..20.24 rows=10 width=32) (actual time=0.002..0.002 rows=2 loops=4)
Recheck Cond: (id = ANY (s_1.courses))
-> Bitmap Index Scan on course_pk (cost=0.00..9.58 rows=10 width=0) (actual time=0.001..0.001 rows=2 loops=4)
Index Cond: (id = ANY (s_1.courses))
-> Hash (cost=18.00..18.00 rows=800 width=72) (actual time=0.008..0.008 rows=4 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 1kB
-> Seq Scan on student s (cost=0.00..18.00 rows=800 width=72) (actual time=0.003..0.004 rows=4 loops=1)
Total runtime: 0.116 ms
我们可以看到,student表直接与course表关联查询,执行需要0.168 ms,与course_info视图进行关联查询,执行需要0.116 ms。对于关联信息越多的情况,效率提升越明显。