在MySQL中,联接是一种对表的引用,
多表联接类型:
1.笛卡尔积(交叉联接):在MySQL中为CROSS JOIN或省略JOIN,如:
select * from course, teachcourse; -- 隐式交叉联接
或者select * from course join teachcourse
或 select * from course cross join teachcourse;
返回结果为被连接的两张表的乘积。因此当有WHERE,ON或USING条件时一般不建议使用。因为数据多太时查询会很慢。一般使用LEFT [OUTER] JOIN或者 RIGHT [OUTER] JOIN。
对于交叉联接,选择主表影响到查询的效率。如下,course,teachcourse的位置不同,所要作的全表扫描表也不同,一般将小表用于全表扫描。例:
mysql> desc select * from course, teachcourse where course.cid = teachcourse.cid;
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
| 1 | SIMPLE | course | ALL | cid_class | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | teachcourse | ref | cid | cid | 4 | testdb.course.cid | 1 | |
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
2 rows in set (0.00 sec)
mysql> desc select * from teachcourse,course where course.cid = teachcourse.cid;
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
| 1 | SIMPLE | teachcourse | ALL | cid | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | course | ref | cid_class | cid_class | 4 | testdb.teachcourse.cid | 1 | |
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
2 rows in set (0.00 sec)
2.内联接INNER JOIN:在MySQL中也叫等值联接。在MySQL中CROSS和INNER JOIN被划为一类。
在内联接的数据记录中,不会存在字段为NULL的情况。可以简单认为,内链接的结果就是在左联接(或右联接)的结果中去除存在字段为NULL的记录后得到的结果。
INNER JOIN产生的结果集是A和B的交集。(图片来源互联网)
例: select * from course, teachcourse where course.cid = teachcourse.cid; -- 隐式
等同于:select * from course inner join teachcourse on course.cid=teachcourse.cid; ---显式
主表的选择将影响到查询的效率,同交叉联接,如下:
mysql> desc select * from course, teachcourse where course.cid = teachcourse.cid;
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
| 1 | SIMPLE | course | ALL | cid_class | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | teachcourse | ref | cid | cid | 4 | testdb.course.cid | 1 | |
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------+
2 rows in set (0.00 sec)
mysql> desc select * from teachcourse,course where course.cid = teachcourse.cid;
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
| 1 | SIMPLE | teachcourse | ALL | cid | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | course | ref | cid_class | cid_class | 4 | testdb.teachcourse.cid | 1 | |
+----+-------------+-------------+------+---------------+-----------+---------+------------------------+------+-------+
2 rows in set (0.00 sec)
自然联接:在连接条件中使用等于=运算符比较被连接列的列值,但删除连接表中重复列.
3.外联接,分为左外联接和右外联接,即除返回符合条件的结果外,还要返回左表(左联接)或右表(右联接)中不符合联接条件的结果,相对应的使用NULL对应。
例:
注意到上图中,uid,period为空的项。表示在course中的记录在teachcourse中没有相应的纪录。 其余记录都是既在左表又在右表的纪录。
工作原理:
从左表读出一条,选出所有与on匹配的右表纪录(n条)进行连接,形成n条纪录(包括重复的行),如果右边没有与on条件匹配的表,那连接的字段都是null.然后继续读下一条。
如果需要找出所有在左表而不在右表的记录,可以用右表没有on匹配则显示null的规律来查找。如:
可以说,所谓的联接表就是数据库在做查询形成的中间表,注意:where条件放在on后面查询的结果是不一样的。因此,推荐在写连接查询的时候,on后面只跟连接条件,而对中间表限制的条件都写到where子句中。
如上语句:select * from course left join teachcourse on course.cid=teachcourse.cid where teachcourse.cid is null; 这里的Where子句的作用是对联接后产生的中间表进行过滤。所以并不等同select * from course left join teachcourse on course.cid=teachcourse.cid and teachcourse.cid is null;
select * from course left join teachcourse on course.cid=teachcourse.cid and teachcourse.cid is null;产生的结果也难以理解:
这两条语句的执行计划分别是:
mysql> desc select * from course left join teachcourse on course.cid=teachcourse.cid where teachcourse.cid is null;
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+--------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+--------------------+
| 1 | SIMPLE | course | ALL | NULL | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | teachcourse | ref | cid | cid | 4 | testdb.course.cid | 1 | Using where; Not exists|
+----+-------------+-------------+------+---------------+------+---------+-------------------+------+-------------------+
2 rows in set (0.00 sec)
mysql> desc select * from course left join teachcourse on course.cid=teachcourse.cid and teachcourse.cid is null;
+----+-------------+-------------+------+---------------+------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+-------+------+-------+
| 1 | SIMPLE | course | ALL | NULL | NULL | NULL | NULL | 36 | |
| 1 | SIMPLE | teachcourse | ref | cid | cid | 4 | const | 3 | |
+----+-------------+-------------+------+---------------+------+---------+-------+------+-------+
2 rows in set (0.00 sec)
mysql是不支持全外的连接的,但是可以通过左外和右外求合集来获取全外连接的查询结果。
SELECT * FROM TableA FULL OUTER JOIN TableB(图片来源互联网)
Full outer join 产生A和B的并集。但是需要注意的是,对于没有匹配的记录,则会以null做为值。
SELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name(图片来源互联网)
Left outer join 产生表A的完全集,而B表中匹配的则有值,没有匹配的则以null值取代。
SELECT * FROM TableA LEFT OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableB.id IS null(图片来源互联网)
产生在A表中有而在B表中没有的集合。
SELECT * FROM TableA FULL OUTER JOIN TableB ON TableA.name = TableB.name WHERE TableA.id IS null OR TableB.id IS null
产生A表和B表都没有出现的数据集。(图片来源互联网)
4.USING子句,如果连接的两个表连接条件的两个列具有相同的名字的话可以使用USING
语句 select * from course left join teachcourse using(cid) where teachcourse.cid is null
等同于 select * from course left join teachcourse on course.cid=teachcourse.cid where teachcourse.cid is null;
多表查询的时候,需要根据查询的情况,想好使用哪种连接方式效率更高。如果选择不当,非但不能提高查询效率,反而会带来一些逻辑错误或者性能低下。下面总结一下两表连接查询选择方式的依据:
1)查两表关联列相等的数据用内连接。
2)col_l是col_r的子集时用右外连接。
3)col_r是col_l的子集时用左外连接。
4)col_r和col_l彼此有交集但彼此互不为子集时候用全外。
5)求差操作的时候用联合查询。
多个表查询的时候,这些不同的连接类型可以写到一块。例如:
select t1.c1,t2.cx,t3.cy from tb1 t1
inner join tb2 t2 on (t1.c1=t2.c2)
inner join tb3 t3 on (t1.c1=t2.c3)
left outer join tab4 on(t2.c2=t3.c3);
where t1.x >t3.y;
6)当 MySQL 在从一个表中检索信息时,你可以提示它选择了哪一个索引。
如果 EXPLAIN 显示 MySQL 使用了可能的索引列表中错误的索引,这个特性将是很有用的。
通过指定 USE INDEX (key_list),你可以告诉 MySQL 使用可能的索引中最合适的一个索引在表中查找记录行。
可选的二选一句法 IGNORE INDEX (key_list) 可被用于告诉 MySQL 不使用特定的索引。如:
mysql> SELECT * FROM table1 USE INDEX (key1,key2)
-> WHERE key1=1 AND key2=2 AND key3=3;
mysql> SELECT * FROM table1 IGNORE INDEX (key3)
-> WHERE key1=1 AND key2=2 AND key3=3;
如何优化LEFT JOIN和RIGHT JOIN
在MySQL中,A LEFT JOIN B join_condition执行过程如下:
1) 根据表A和A依赖的所有表设置表B。
2) 根据LEFT JOIN条件中使用的所有表(除了B)设置表A。
3) LEFT JOIN条件用于确定如何从表B搜索行。(换句话说,不使用WHERE子句中的任何条件)。
4) 可以对所有标准联接进行优化,只是只有从它所依赖的所有表读取的表例外。如果出现循环依赖关系,MySQL提示出现一个错误。
5) 进行所有标准WHERE优化。
6) 如果A中有一行匹配WHERE子句,但B中没有一行匹配ON条件,则生成另一个B行,其中所有列设置为NULL。
7) 如果使用LEFT JOIN找出在某些表中不存在的行,并且进行了下面的测试:WHERE部分的col_name IS NULL,其中col_name是一个声明为 NOT NULL的列,MySQL找到匹配LEFT JOIN条件的一个行后停止(为具体的关键字组合)搜索其它行。
RIGHT JOIN的执行类似LEFT JOIN,只是表的角色反过来。
联接优化器计算表应联接的顺序。LEFT JOIN和STRAIGHT_JOIN强制的表读顺序可以帮助联接优化器更快地工作,因为检查的表交换更少。
请注意这说明如果执行下面类型的查询,MySQL进行全扫描b,因为LEFT JOIN强制它在d之前读取:
SELECT * FROM a, b LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;
在这种情况下修复时用a的相反顺序,b列于FROM子句中:
SELECT * FROM b, a LEFT JOIN c ON (c.key=a.key) LEFT JOIN d ON (d.key=a.key) WHERE b.key=d.key;
MySQL可以进行下面的LEFT JOIN优化:如果对于产生的NULL行,WHERE条件总为假,LEFT JOIN变为普通联接。
例如,在下面的查询中如果t2.column1为NULL,WHERE 子句将为false:
SELECT * FROM t1 LEFT JOIN t2 ON (column1) WHERE t2.column2=5;
因此,可以安全地将查询转换为普通联接:
SELECT * FROM t1, t2 WHERE t2.column2=5 AND t1.column1=t2.column1;
这样可以更快,因为如果可以使查询更佳,MySQL可以在表t1之前使用表t2。为了强制使用表顺序,使用STRAIGHT_JOIN。
附:联接顺序:
SELECT * FROM t1 LEFT JOIN t2 ON t1.a=t2.a LEFT JOIN t3 ON t2.b=t3.b WHERE…
顺序:a:左连接按先后顺序,先是表a和表b连接,再与表c连接。
b:先联接操作,再用where过滤。
c:上句也等同于:
SELECT * FROM t1 LEFT JOIN (t2, t3) ON (t2.a=t1.a AND t2.b=t3.b) WHERE…
也等同于:
SELECT * FROM t1 LEFT JOIN (t2 CROSS JOIN t3) ON t1.a=t2.a AND t2.b=t3.b WHERE…
例:
许多时候,在我们同时使用inner join和outer join的时候一定要对连接的顺序做慎重考虑。
附:SQL中关于联接的概念:
(一) 内联接
内联接查询操作列出与联接条件匹配的数据行,它使用比较运算符比较被联接列的列值。内联接分三种:
1)等值联接:在联接条件中使用等于号(=)运算符比较被联接列的列值,其查询结果中列出被联接表中的所有列,包括其中的重复列。
2)不等联接: 在联接条件使用除等于运算符以外的其它比较运算符比较被联接的列的列值。这些运算符包括>、>=、<=、<、!>、!<和<>。
3)自然联接:在联接条件中使用等于(=)运算符比较被联接列的列值,但它使用选择列表指出查询结果集合中所包括的列,并删除联接表中的重复列。
等值连接中不要求相等属性值的属性名相同,而自然连接要求相等属性值的属性名必须相同,即两关系只有在同名属性才能进行自然连接;等值连接不将重复属性去掉,而自然连接去掉重复属性,也可以说,自然连接是去掉重复列的等值连接。
(二) 外连接
外连接分为左外连接(LEFT OUTER JOIN或LEFT JOIN)、右外连接(RIGHT OUTER JOIN或RIGHT JOIN)和全外连接(FULL OUTER JOIN或FULL JOIN)三种。
内连接时,返回查询结果集合中的仅是符合查询条件( WHERE 搜索条件或 HAVING 条件)和连接条件的行。而采用外连接时,它返回到查询结果集合中的不仅包含符合连接条件的行,而且还包括左表(左外连接时)、右表(右外连接时)或两个边接表(全外连接)中的所有数据行。
三者的共同点是都返回符合连接条件和查询条件(即:内连接)的数据行。不同点如下:
* 左外连接还返回左表中不符合连接条件单符合查询条件的数据行。
* 右外连接还返回右表中不符合连接条件单符合查询条件的数据行。
* 全外连接还返回左表中不符合连接条件单符合查询条件的数据行,并且还返回右表中不符合连接条件单符合查询条件的数据行。全外连接实际是上左外连接和右外连接的数学合集(去掉重复),即“全外=左外union 右外”。
(三) 交叉连接(CROSS JOIN)
交叉连接不带WHERE 子句,它返回被连接的两个表所有数据行的笛卡尔积,返回到结果集合中的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。
(四) 联合连接(union join)
这是一种很少见的连接方式。oracle、mysql均不支持,其作用是:找出全外连接和内连接之间差异的所有行。这在数据分析中排错中比较常用。也可以利用数据库的集合操作来实现此功能。
语句11:联合查询(union join)例句,还没有找到能执行的sql环境。
select t.uid, c.name, c.class from course as c union join teachcourse as t on c.cid=t.cid;
语句12:语句11在db2下的等价实现。还不知道db2是否支持语句11呢!
select t.cid, c.name, c.class from course c full outer join teachcourse t on c.cid=t.cid exception select t.cid, c.name, c.class from course c inner join teachcourse t on c.cid=t.cid;
语句13:语句11在oracle下的等价实现。
select t.cid, c.name, c.class from course c full outer join teachcourse t on c.cid=t.cid minus select t.cid, c.name, c.class from course c inner join teachcourse t on c.cid=t.cid;
无论哪种连接都不能对text、ntext和image数据类型列进行直接连接,但可以对这三种列进行间接连接。
关于sql查询的基本原理:
* 单表查询:根据where条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的);然后根据select的选择列选择相应的列进行返回最终结果。
* 两表连接查询:对两表求积(笛卡尔积)并用on条件和连接类型进行过滤形成中间表;然后根据where条件过滤中间表的记录,并根据select指定的列返回查询结果。
* 多表连接查询:先对第一个和第二个表按照两表连接做查询,然后用查询结果和第三个表做连接查询,以此类推,直到所有的表都连接上为止,最终形成一个中间的结果表,然后根据where条件过滤中间表的记录,并根据select指定的列返回查询结果。
关于on条件和where条件的区别:
on条件:是过滤两个链接表笛卡尔积形成中间表的约束条件。
where条件:在有on条件的select语句中是过滤中间表的约束条件。在没有on的单表查询中,是限制物理表或者中间查询结果返回记录的约束。在两表或多表连接中是限制连接形成最终中间表的返回结果的约束。
使用的表:
create table teacher(
id int unsigned not null auto_increment,
uid int unsigned not null default 0,
name varchar(50) not null default '',
age tinyint not null default 0,
sex tinyint not null default 0,
primary key(id)
);
alter table teacher add index uid_age(uid, age);
create table course(
id int unsigned not null auto_increment,
cid int unsigned not null default 0,
name varchar(50) not null default '',
class int unsigned not null default 0,
primary key(id)
);
alter table course add index cid_class(cid, class);
create table teachcourse(
uid int unsigned not null default 0,
cid int unsigned not null default 0,
period int unsigned not null default 0
);
alter table teachcourse add foreign key(uid) references teacher(uid);
alter table teachcourse add foreign key(cid) references course(cid);