多表查询SQL 语句优化
数据多个表关联查询时,由于表之间的连接关系(内连接、外连接、交叉连接),导致数据库服务器常常从几万条甚至更多的数据记录中查找符合条件的记录,如果sql查询语句设计不好查询的复杂度就会直线上升,甚至是指数级上升,导致查询时间长甚至失去相应,这里讲两种从sql语句优化查询的方法。
1.把外连接变成交叉连接或内连接(对不起,在写改良例子的时候发现这种方法并不能改善查询的速度,这是一个错误):
我们建三个表分别是:temple、bonze、和woodfish。
temple表示庙宇,它的字段有:
temple_id int
temple_name varchar(50)
location varchar(50)
build_date datetime
temple_id是主键,设为自增;temple_name是庙宇名;location是位置,就是寺庙的地址;build_date是建庙时间。
bonze表示和尚,它的字段有(出家人四大皆空,这些字段够了,呵呵):
bonze_id int
temple_id int
bonze_name varchar(50)
register_time datetime
bonze_id是主键,设为自增;temple_id表示这个和尚属于哪个寺庙,对应寺庙的temple_id,对于没有寺庙的流浪和尚这里暂时不考虑(写这篇文章主要是告诉一些大寺庙,他们可以采用软件管理的方式来管理寺庙,如果他们看到了我这篇文章可能会找我开发一个开发软件也未定,至于流浪和尚是不太可能的了,所以不考虑他们,呵呵,罪过、罪过);bonze_name是和尚名字,和尚本没有名字就叫法号吧;register_time是注册时间,现在做个和尚挺不容易的,难度差不多赶上高考上榜了,所以要一个注册时间。
woodfish表示木鱼,呵呵,木鱼的英文实在不会写,只好直译了,哪位兄弟姐妹知道了告诉我一下。木鱼的字段有:
woodfish_id int Unchecked
bonze_id int Unchecked
woodfish_num varchar(50) Checked
woodfish_id是主键,设为自增;bonze_id表示这个木鱼属于那个和尚,不属于任何和尚的木鱼我们也暂时不考虑,那些房子仓库就可以了,不用编号什么的;woodfish_num表示木鱼编号。
我们建立表的关系图如下(微软的SqlServerManagement应该提供可以把关系图转换成图片的功能,我还要photoshop来截图):

图1 数据关系图
相当简单的一个数据库。我们假设有些寺庙可以没有和尚,这就是荒庙,看过武侠小说的兄弟们应该可以了解的。和尚可以没有木鱼,可能太穷了,买不起,也可能太懒了,懒得敲。当然有些和尚也可以拥有几个木鱼,可能准备收徒弟的时候送个木鱼做见面礼什么的。
现在我们插入一些数据。一下数据仅为测试使用,并无意冒犯任何寺庙和庙里的高僧。
temple的数据为:
| temple_id | temple_name | location | build_time |
| 1 | 少林寺 | 河南省登封市嵩山 | |
| 2 | 大杰寺 | 五龙山 | |
| 3 | 法源寺 | 宣武门外教子胡同南端东侧 | |
| 4 | 广济寺 | 阜成门内大街东口 | |
| 5 | 碧云寺 | 香山东麓 | |
表1 寺庙信息
bonze的数据为:
| bonze_id | temple_id | bonze_name | register_time |
| 1 | 1 | 悟空 | |
| 2 | 1 | 悟静 | |
| 3 | 1 | 悟能 | |
| 4 | 1 | 悟性 | |
| 5 | 1 | 悟戒 | |
| 6 | 2 | 觉原 | |
| 7 | 2 | 觉情 | |
| 8 | 2 | 觉静 | |
| 9 | 3 | 一噌 | |
| 10 | 5 | 八戒 | |
表2 和尚信息
woodfish的数据为:
| woodfish | bonze_id | woodfish_num |
| 1 | 1 | 1111 |
| 2 | 1 | 2222 |
| 3 | 1 | 3333 |
| 4 | 2 | 4444 |
| 5 | 6 | 5555 |
| 6 | 8 | 6666 |
| 7 | 8 | 7777 |
| 8 | 10 | 8888 |
| 9 | 10 | 9999 |
| 10 | 10 | 1010 |
| 11 | 4 | 11111 |
| 12 | 4 | 1212 |
| 13 | 4 | 1313 |
好了,假设现在我们要查询某一个或几个寺庙的详细信息,即寺庙信息、和尚信息和木鱼信息。对于没有和尚的寺庙要列一条记录,和尚和木鱼的所有字段显示为空;对于没有木鱼的和尚也列一条记录,木鱼信息显示为空。
如果我们直接用内连接查询,对于没有和尚的寺庙的信息或者没有木鱼的和尚的信息就查不出来,因为数据库服务器先对所有表做交叉连接,然后在找到符合条件的数据。如果和尚信息为空,这个寺庙信息交叉后就是空的,和尚和木鱼也是同样的道理。
这时我们就要用到外连接,外连接分左连接、右连接、和全外(ALL)连接,左连接就是左边的表作为重点,把左边的表的信息全列出来,对于右边的表用空(整数用0)来补;右连接重点刚好相反,全外连接就是没有主次之分的。两边的表信息都能列举出来。
现在我们要用到的就是左连接。
左连接后的搜索记录总数可以通过以下SQL语句来检测:
SELECT temple.temple_id, temple.temple_name, temple.location, temple.build_date, bonze.bonze_id, bonze.temple_id AS Expr1, bonze.bonze_name,
bonze.register_time, woodfish.woodfish_id, woodfish.bonze_id AS Expr2, woodfish.woodfish_num
FROM temple LEFT OUTER JOIN
bonze ON temple.temple_id = bonze.temple_id LEFT OUTER JOIN
woodfish ON bonze.bonze_id = woodfish.bonze_id
总共有18条记录
改成内连接后搜索记录总数可以通过以下三个SQL语句查询结果联合得到:
SELECT temple.temple_id, temple.temple_name, temple.location, temple.build_date, bonze.bonze_id, bonze.temple_id AS Expr1, bonze.bonze_name,
bonze.register_time, woodfish.woodfish_id, woodfish.bonze_id AS Expr2, woodfish.woodfish_num
FROM temple INNER JOIN
bonze ON temple.temple_id = bonze.temple_id INNER JOIN
woodfish ON bonze.bonze_id = woodfish.bonze_id
寺庙既有和尚和尚又有木鱼的记录有13条记录
SELECT temple.temple_id, temple.temple_name, temple.location, temple.build_date, bonze.bonze_id, bonze.temple_id AS Expr1, bonze.bonze_name,
bonze.register_time, 0 AS woodfish_id, 0 AS Expr2, 0 AS woodfish_num
FROM temple INNER JOIN
bonze ON temple.temple_id = bonze.temple_id
WHERE (bonze.bonze_id NOT IN
(SELECT bonze_id
FROM woodfish))
寺庙的和尚没有木鱼有4条记录
SELECT temple_id, temple_name, location, build_date, 0 AS bonze_id, 0 AS Expr1, NULL AS bonze_name, NULL AS register_time, 0 AS woodfish_id,
0 AS Expr2, 0 AS woodfish_num
FROM temple
WHERE (temple_id NOT IN
(SELECT temple_id
FROM bonze))
寺庙即没有和尚又没有木鱼的记录有1条
以上证明把外连接改成内连接并不能改善查询速度。
2.把内连接的where子句换成子表查询(去掉where子句):
改良前
SELECT DISTINCT
student.card_id, student.name, student.student_num, [order].order_id, [order].order_since, [order].persist_time, lesson.endure_time, lesson.allow_late,
lab.name AS labname, bench.num, bench.bench_ip
FROM student INNER JOIN
[order] ON student.student_id = [order].student_id INNER JOIN
lesson ON [order].lesson_id = lesson.lesson_id INNER JOIN
bench ON [order].bench_id = bench.bench_id INNER JOIN
lab ON bench.lab_id = lab.lab_id
WHERE (NOT ([order].order_id IN
(SELECT order_id
FROM history))) AND (student.student_num = @card_id) AND (lab.name = @labname) AND ([order].status = 0) AND ([order].editor_type = 0)
ORDER BY [order].order_since
改良后
SELECT DISTINCT
student1.card_id, student1.name, student1.student_num, order1.order_id, order1.order_since, order1.persist_time, lesson.endure_time,
lesson.allow_late, lab1.name AS labname, bench.num, bench.bench_ip
FROM (SELECT student_id, card_id, name, student_num
FROM student
WHERE (student_num = 'card_id233')) AS student1 INNER JOIN
(SELECT order_id, bench_id, lesson_id, student_id, order_since, persist_time
FROM [order]
WHERE (NOT (order_id IN
(SELECT order_id
FROM history))) AND (status = 0) AND (editor_type = 0)) AS order1 ON student1.student_id = order1.student_id INNER JOIN
lesson ON order1.lesson_id = lesson.lesson_id INNER JOIN
bench ON order1.bench_id = bench.bench_id INNER JOIN
(SELECT lab_id, name
FROM lab
WHERE (name = 'labname23')) AS lab1 ON bench.lab_id = lab1.lab_id
ORDER BY order1.order_since