Sql Server的艺术(四) SQL多表查询
表的基本连接
SQL的一个重要特性就是能通过JOIN关键词,从多个交叉表中查询、分析数据。
连接表的目的
在关系数据库中,数据表设计的一个重要原则就是要避免冗余性。
- 减少了冗余信息,节省了数据库存储空间。
- 简化了数据修改、维护操作。
学习本节需要的数据表:
CREATE TABLE TEACHER ( ID INT IDENTITY (1,1) PRIMARY KEY , --主键,自增长 TNO INT NOT NULL, --教工号 TNAME CHAR(10) NOT NULL, --教师姓名 CNO INT NOT NULL, --课程号 SAL INT, --工资 DNAME CHAR(10) NOT NULL, --所在系 TSEX CHAR(2) NOT NULL, --性别 AGE INT NOT NULL --年龄 ) INSERT INTO dbo.TEACHER VALUES( 1,'王军',4,400,'数学','男',32) INSERT INTO dbo.TEACHER VALUES( 2,'李彤',5,6600,'生物','女',54) INSERT INTO dbo.TEACHER VALUES( 3,'王永军',1,1000,'计算机','女',45) INSERT INTO dbo.TEACHER VALUES( 4,'刘晓婧',2,8000,'计算机','女',23) INSERT INTO dbo.TEACHER VALUES( 5,'高维',8,6000,'电子工程','男',54) INSERT INTO dbo.TEACHER VALUES( 6,'李伟',7,230,'机械工程','女',23) INSERT INTO dbo.TEACHER VALUES( 7,'刘辉',3,0,'生物','女',65) INSERT INTO dbo.TEACHER VALUES( 8,'刘伟',9,500,'计算机','男',23) INSERT INTO dbo.TEACHER VALUES( 9,'刘静',12,0,'经济管理','男',45) INSERT INTO dbo.TEACHER VALUES( 10,'刘奕锴',13,70000,'计算机','女',65) INSERT INTO dbo.TEACHER VALUES( 11,'高维',14,70000,'经济管理','男',61) CREATE TABLE COURSE ( ID INT IDENTITY (1,1) PRIMARY KEY , --主键,自增长 CNO INT NOT NULL, --课程号 CNAME CHAR(30) NOT NULL, --课程名称 CTIME INT NOT NULL, --学时 SCOUNT INT NOT NULL, --容纳人数 CTEST SMALLDATETIME NOT NULL, --考试时间 ) INSERT INTO dbo.COURSE VALUES( 4,'应用数学基础',48,120,'2006-7-10') INSERT INTO dbo.COURSE VALUES( 5,'生物工程概论',32,80,'2006-7-8') INSERT INTO dbo.COURSE VALUES( 1,'计算机软件基础',32,70,'2006-7-8') INSERT INTO dbo.COURSE VALUES( 2,'计算机硬件基础',24,80,'2006-6-28') INSERT INTO dbo.COURSE VALUES( 8,'模拟电路设计',28,90,'2006-7-10') INSERT INTO dbo.COURSE VALUES( 7,'机械设计实践',48,68,'2006-7-14') INSERT INTO dbo.COURSE VALUES( 3,'生物化学',32,40,'2006-7-2') INSERT INTO dbo.COURSE VALUES( 9,'数据库设计',16,80,'2006-7-1') INSERT INTO dbo.COURSE VALUES( 6,'设计理论',28,45,'2006-6-30') INSERT INTO dbo.COURSE VALUES( 10,'计算机入门',25,150,'2006-6-29') INSERT INTO dbo.COURSE VALUES( 11,'数字电路设计基础',30,125,'2006-6-20') CREATE TABLE STUDENT ( ID INT IDENTITY(1,1) PRIMARY KEY NOT NULL, SNO CHAR(4) NOT NULL, --学号 SNAME CHAR(10) NOT NULL, --姓名 DNAME CHAR(10) NOT NULL, --系 SSEX CHAR(2) NOT NULL, --性别 CNO INT , --课程号 MARK DECIMAL(3,1), --成绩 TYPE CHAR(4) --课程类型 ) INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',4,82.5,'必修') INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',10,70,'选修') INSERT INTO dbo.STUDENT VALUES('9701','刘建国','管理工程','男',1,78.5,'选修') INSERT INTO dbo.STUDENT VALUES('9702','李春','环境工程','女',5,63,'必修') INSERT INTO dbo.STUDENT VALUES('9702','李春','环境工程','女',10,58,'选修') INSERT INTO dbo.STUDENT VALUES('9703','王天','生物','男',5,48.5,'必修') INSERT INTO dbo.STUDENT VALUES('9703','王天','生物','男',2,86,'选修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',4,76,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',1,92,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',2,89,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',9,80,'必修') INSERT INTO dbo.STUDENT VALUES('9704','李华','计算机','女',8,70,'选修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',8,79,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',1,59,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',11,52,'必修') INSERT INTO dbo.STUDENT VALUES('9705','孙庆','电子工程','男',6,68,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',13,93,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',12,88.5,'必修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',1,78,'选修') INSERT INTO dbo.STUDENT VALUES('9706','高伟','机械工程','男',10,76,'选修')数据表
1、简单的二表连接
SELECT TNAME,DNAME,CNAME,CTEST FROM dbo.TEACHER,dbo.COURSE WHERE dbo.TEACHER.CNO=dbo.COURSE.CNO --查询老师和系表的数据
根据以上SQL,可知道执行过程:
-
- 系统首先执行FROM子句,这里FROM子句列出的有两个表TEACHER和COURSE,DBMS将计算这两个表的笛卡儿积,列出这两个表中行的所有可能组合,形成一个中间表。中间表的每条记录包含了两个表中的所有行。
- 而后系统将执行WHERE子句,根据“dbo.TEACHER.CNO=dbo.COURSE.CNO”关系对中间表进行搜索,去除那些不满足该条件的记录。
- 最后系统执行SELECT语句,从执行WHERE子句后得到的中间表的每条记录中,提取TNAME,DNAME,CNAME,CTEST这4个字段的信息作为结果表。
注意:
由于两张表都存在相同的字段CNO,所以在查询的时候要指明来自哪张表,如dbo.TEACHER.CNO、dbo.COURSE.CNO,而其他具有重名的字段,也要进行同样的处理,否则会报错。
问题:
表的连接时一句关系WHERE子句来定义的,实际开发中,也一定更要用到关系连接。如果不指明连接关系呢?
SELECT TNAME,DNAME,CNAME,CTEST FROM dbo.TEACHER,dbo.COURSE
SELECT COUNT(*) AS 总条数 FROM dbo.TEACHER,dbo.COURSE --查询一共有几条数据
2、三表查询(在WHERE子句中进行夺标查询)
SELECT SNAME,dbo.STUDENT.SNAME,CNAME,CTEST,MARK,dbo.TEACHER.TNAME FROM dbo.TEACHER,dbo.COURSE,dbo.STUDENT WHERE dbo.TEACHER.CNO=dbo.COURSE.CNO AND dbo.COURSE.CNO=dbo.STUDENT.CNO
使用别名,S,T,C
SELECT SNAME,S.SNAME,CNAME,CTEST,MARK,TNAME FROM dbo.TEACHER AS T,dbo.COURSE AS C,dbo.STUDENT AS S WHERE T.CNO=C.CNO AND C.CNO=S.CNO
”dbo.TEACHER.CNO=dbo.COURSE.CNO AND dbo.COURSE.CNO=dbo.STUDENT.CNO“,含义为”只有同时存在TEACHER,STUDENT,COURSE“中CNO的信息才会作为结果显示出来。
注意:
由于学生表和老师表都有字段”DNAME“,在SELECT子句中查询DNAME字段时,我们要指定表名,否则系统会报错。
可见,创建表的基本连接,只要遵守下面的基本原则即可:
-
- FROM子句中应列出所有连接的表的表名。
- WHERE子句应定义连接的关联条件。
- 当列名为多个表共有时,要指明列的所在表,即采用”表名.字段名“的形式。
3、采用JOIN关键字建立连接
SELECT COLUMN
FROM join_table
JOIN_TYPE join_table
ON (join_condition)
说明如下:
-
-
- join_table指出参与连接操作的表名。
- JOIN_TYPE为连接类型,可分为四种:自然连接、内连接、外连接和交叉连接。
- 自然连接JOIN_TYPE的形式为NATURAL JOIN
- 内连接JOIN_TYPE的形式为INNER JOIN
- 外连接,又分为左连接,JOIN_TYPE形式为LEFT OUTER JOIN或LEFT JOIN;右连接,JOIN_TYPE的形式为RIGHT OUTER JOIN或RIGHT JOIN;全外连接,JOIN_TYPE的形式为CROSS JOIN;全外连接,JOIN_TYPE的形式为FULL OUTER JOIN或FULL JOIN。
- 交叉连接中JOIN_TYPE的形式为CROSS JOIN。
- ON(join_condition)子句指出连接条件,由被连接表中的列和比较远算符、逻辑运算符等构成。
-
4、表的连接类型
4.1、自连接
SELECT DISTINCT SNO FROM dbo.STUDENT WHERE MARK<60 --查询不及格学生学号,重复的去除 SELECT DNAME,MARK,SNAME,CNO FROM dbo.STUDENT WHERE SNO IN('9702','9703','9705') AND MARK <60 --查询三个学号中不及格学生的信息
4.2、内连接(INNER JOIN)
内连接也称为等同连接,返回的结果集是两个表中所有相匹配的数据,而舍弃不匹配的数据。查询的结果表包含的两源表行,必须满足ON子句中的搜索条件。
使用等于号(=)比较被连接列的列值,在查询结果中列出被连接表中的所有列,包括其中的重复列。图给出了典型的等值内连接示意图。
SELECT s.SNAME,s.DNAME,s.CNO,t.TNAME FROM dbo.STUDENT AS s inner join dbo.TEACHER AS t ON t.CNO = s.CNO --查询两表部分信息
使用不等连接进行查询 后面会讲到各种运算符 先给官方网站地址:http://www.runoob.com/sqlite/sqlite-operators.html
SELECT s.SNAME,s.DNAME,s.CNO,t.TNAME FROM dbo.STUDENT AS s inner join dbo.TEACHER AS t ON s.DNAME<>t.DNAME AND t.CNO = s.CNO
等价于:
SELECT s.SNAME,s.DNAME,s.CNO,t.TNAME FROM dbo.STUDENT AS s,dbo.TEACHER AS t WHERE s.DNAME<>t.DNAME AND t.CNO = s.CNO
DBMS默认多表查询按INNER JOIN来执行,除非指定OUTER JOIN。
注意:
INNER JOIN可以实现夺标查询,但是一次只能连接两张表,要连接多表,必须进行多次连接。
4.2.1、使用INNER JOIN实现多表连接
--三张表进行查询,用两个INNER JOIN
SELECT s.SNAME,s.DNAME,c.CNAME,c.CTEST,s.MARK,t.TNAME FROM dbo.TEACHER AS t INNER JOIN dbo.COURSE AS c ON c.CNO = t.CNO INNER JOIN dbo.STUDENT AS s ON s.CNO = t.CNO
4.3、外连接(OUTER JOIN)
左外连接=内连接+左边表中失配的元组
右外连接=内连接+右边表中失配的元组
全外连接=内连接+左边表中失配的元组+右边表中失配的元组
表达式示意图如:
SELECT s.SNO,s.SNAME,c.CNO,c.CNAME,c.CTEST,s.MARK FROM dbo.STUDENT AS s LEFT OUTER JOIN dbo.COURSE AS c ON c.CNO = s.CNO
在WHERE子句中用”*=“实现左外连接
SELECT s.SNO,s.SNAME,c.CNO,c.CNAME,c.CTEST,s.MARK FROM dbo.STUDENT AS s,dbo.COURSE AS c WHERE c.CNO *= s.CNO
----三种连接方式都一样,只需要改”LEFT OUTER JOIN“为”RIGHT OUTER JOIN“和”FULL OUTER JOIN“,
就不做三个演示了,可以自己对比数据的异同
4.4、交叉连接(CROSS JOIN)
除了在FROM子句中使用逗号间隔连接的表外,SQL还支持另一种被称为交叉连接的操作,它们都返回被连接的两个表所有数据行的笛卡尔积,返回到的数据行数等于第一个表中符合查询条件的数据行数乘以第二个表中符合查询条件的数据行数。惟一的不同在于,交叉连接分开列名时,使用CROSS JOIN关键字而不是逗号。
实际上,下面两个表达式是等价的。
SELECT * FROM table1, table2 SELECT * FROM table1 CROSS JOIN table2
交叉连接示意图:
SELECT s.SNO,s.SNAME,c.CNO,s.CNO,c.CNAME,c.CTEST,s.MARK FROM dbo.STUDENT AS s CROSS JOIN dbo.COURSE AS c WHERE s.CNO=c.CNO AND s.MARK>60
注意:
在使用CROSS JOIN关键字交叉连接表时,因为生成的是两个表的笛卡尔积,因而不能使用ON关键字,只能在WHERE子句中定义搜索条件。
事实上,直接使用CROSS JOIN很少得到想要的结果,但是,正如实例所示,作为查询的第一步,DBMS通常在FROM子句中,对连接的表进行CROSS JOIN,然后过滤得到的中间表。