多表查询
前面我们讲解的mysql表的查询都是对一张表进行查询,在实际开发中这远远不够。下面我们用一个简单的公司管理系统,有三张表EMP,DEPT,SALGRADE来演示如何进行多表查询
查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的J
按照部门号升序而雇员的工资降序排序
mysql> select * from EMP order by deptno, sal desc;
使用年薪进行排序
mysql> select ename, sal*12+ifnull(comm,0) as ' 年薪 ' from EMP order by 年薪 desc;
1、分页查询 select 字段 from 表名 where 条件 limit 起始位置,记录条数
按雇员的empno号升序取数,每页显示3条记录。请分别显示第一页,第二页,第三页
显示工资最高的员工的名字和工作岗位
mysql> select ename, job from EMP where sal = (select max(sal) from EMP);
显示工资高于平均工资的员工信息
mysql> select ename, sal from EMP where sal>(select avg(sal) from EMP);
显示每个部门的平均工资和最高工资
mysql> select deptno, format(avg(sal), 2) , max(sal) from EMP group by deptno;
显示平均工资低于 2000 的部门号和它的平均工资
mysql> select deptno, avg(sal) as avg_sal from EMP group by deptno having avg_sal<2000;
显示每种岗位的雇员总数,平均工资
mysql> select job,count(*), format(avg(sal),2) from EMP group by job;
2、自连接
自连接是指在同一张表连接查询。显示员工FORD的上级领导的姓名。这个没有用多表,用的是子查询:mysql> select ename from EMP where empno=(select mgr from EMP where ename='FORD');
3、子查询
子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询
4、单行子查询
返回一行记录的子查询。显示SMITH同一部门的员工 mysql> select * from EMP WHERE deptno = (select deptno from EMP where ename='smith');
5、多行子查询
返回多行记录的子查询,使用关键字in。如何查询和10号部门的工作相同的雇员的名字,岗位,工资,部门号,但是不包含10自己的 mysql> select * from EMP where job in ( select distinct job from EMP where deptno=10) and deptno <> 10;
在多行子查询中使用all操作符。显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号:
mysql> select ename, sal, deptno from EMP where sal > all(select sal from EMP where deptno=30);
在多行子查询中使用any操作符。显示工资比部门30的任意员工的工资高的员工的姓名、工资和部门号:
mysql> select ename, sal, deptno from EMP where sal > any(select sal from EMP where deptno=30);
6、多列子查询
单行子查询是指子查询只返回单列,单行数据,多行子查询是指返回单列多行数据,都是针对单列而言的,而多列
子查询则是指查询返回多个列数据的子查询语句。
7、在from子句中使用子查询
子查询语句出现在from子句中。这里要用到数据查询的技巧,把一个子查询当做一个临时表使用。
示例:
如何显示高于自己部门平均工资的员工的信息
-- 获取各个部门的平均工资,将其看作临时表
mysql> select ename, deptno, sal, asal from EMP,
-> (select avg(sal) asal, deptno dt from EMP group by deptno) tmp
-> where EMP.sal > tmp.asal and EMP.deptno=tmp.dt;
-> where EMP.sal > tmp.asal and EMP.deptno=tmp.dt;
查找每个部门工资最高的人的详细资料
mysql> select EMP.ename, EMP.sal, EMP.deptno, ms from EMP,
-> (select max(sal) ms, deptno from EMP group by deptno) tmp
-> where EMP.deptno=tmp.deptno and EMP.sal=tmp.ms;
显示每个部门的信息(部门名,编号,地址)和人员数量。
方法1:使用多表
mysql> select dname, DEPT.deptno, loc,count(*) '部门人数' from EMP, DEPT
-> where EMP.deptno=DEPT.deptno
-> group by DEPT.deptno;
方法2:使用子查询
-- 1. 对EMP表进行人员统计 select count(*), deptno from EMP group by deptno;
-- 2. 将上面的表看作临时表
mysql> select DEPT.deptno, dname, mycnt, loc from DEPT,
-> (select count(*) mycnt, deptno from EMP group by deptno) tmp
-> where DEPT.deptno=tmp.deptno;
-> (select count(*) mycnt, deptno from EMP group by deptno) tmp
-> where DEPT.deptno=tmp.deptno;8、自我复制( 蠕虫复制 )
面使用了多表和子查询两种方式进行查询,到底哪个效率高呢?我们需要弄大量数据来进行测试。可以使用自我
复制创建海量数据。
示例:
把EMP表的数据快速变成160000
mysql> create table tmp like EMP; -- 为了做测试,创建tmp表
mysql> insert into tmp select * from EMP;--将EMP表的数据插入到tmp
mysql> alter table tmp drop primary key; -- 删除主键属性
mysql> insert into tmp select * from tmp; -- 自我复制
mysql> select count(*) from tmp; -- 快速的创建了20多万条记录
删除表中的的重复复记录
创建一张表: mysql> create table tt(id int, name varchar(20))
要求:重复的数据只能有一份
思路:
1. 创建一张空表tmp_tt,空表的结构和tt一样
mysql> create table tmp_tt like tt;
2. 将tt表进行distinct,把数据导入空表tmp_tt
mysql> insert into tmp_tt select distinct * from tt;
3. 删除tt表
mysql> drop table tt;
4. 将tmp_tt改名成tt
mysql> alter table tmp_tt rename tt;
9、合并查询
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all
union 该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
示例: 将工资大于25000和职位是MANAGER的人找出来
mysql> select ename, sal, job from EMP where sal>2500 union
-> select ename, sal, job from EMP where job='MANAGER';--去掉了重复记录
union all 该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行。
mysql> select ename, sal, job from EMP where sal>2500 union all
-> select ename, sal, job from EMP where job='MANAGER';
10、外键
外键用于定义主表和从表之间的关系:外键约束主要定义在从表上,主表则必须是有主键约束或unique约束。当
定义外键后,要求外键列数据必须在主表的主键列存在或为null。语法:foreign key (字段名) references 主表(列)
11、综合案例
有一个商店的数据,记录客户及购物情况,有以下三个表组成:
1. 商品goods(商品编号goods_ id,商品名goods_ name, 单价unitprice, 商品类别category, 供应商provider)
2. 客户customer(客户号customer_ id,姓名name,住址address,邮箱email,性别sex,身份证card_id)
3. 购买purchase(购买订单号order_ id,客户号customer_ id,商品号goods_ id,购买数量nums)
要求:
每个表的主外键
客户的姓名不能为空值
邮箱不能重复
客户的性别(男,女)
-- 创建goods表
create table goods(
goods_id int unsigned primary key auto_increment,
goods_name varchar(100) not null default '',
unitprice decimal(10,2) not null default 0.0,
category smallint not null default 0,
provider varchar(100) not null default ''
);
--创建customer
create table customer(
customer_id int unsigned primary key auto_increment,
name varchar(50) not null default '',
address varchar(100) not null default '',
email varchar(60) not null unique,
sex enum('男','女') not null default '男',
card_id varchar(20) not null unique
);
--创建purchase
create table purchase (
order_id varchar(30) not null primary key,
customer_id int unsigned,
goods_id int unsigned,
nums int not null default 0,
foreign key(customer_id) references customer(customer_id),
foreign key(goods_id) references goods(goods_id);
表的内连和外连 1、内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开
发过程中使用的最多的连接查询。
语法:
语法: select 字段 from 表 1 inner join 表 2 on 连接条件 and 其他条件;
说明:前面学习的都是内连接
举例说明:
显示 SMITH 的名字和部门名称
-- 用前面的写法
mysql> select ename, dname from EMP, DEPT where EMP.deptno=DEPT.deptno and ename='SMITH';
-- 用标准的内连接写法
mysql> select ename, dname from EMP inner join DEPT on EMP.deptno=DEPT.deptno and ename='SMITH';
2、 外连接
外连接的分类: 分为左外连接和右外连接
左外连接:如果联合查询,左侧的表完全显示我们就说是左外连接
语法: select 字段名 from 表名 1 left join 表名 2 on 连接条件
右外连接:如果联合查询,右侧的表完全显示我们就说是右外连接。
基本语法: select 字段 from 表名 1 right join 表名 2 on 连接条件;
示例: 对 stu 表和 exam 表联合查询,把所有的成绩都显示出来,即使这个成绩没有学生与它对应,也要显示出来。
mysql> select * from stu right join exam on stu.id=exam.id
索引
1、作用:提高数据库的性能,索引是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行正确
的‘create index’,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插
入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以他的价值,在于提高一个海量数
据的检索速度。
2、常见索引分为:
主键索引(primary key),唯一索引(unique),普通索引(index),全文索引(fulltext)--解决中子文索引问题。
3、索引的基本原理
索引的说明: (1). 占用磁盘空间
(2). 当添加一条记录,除了添加到表中,还要维护二叉树,速度有影响,但不大。
(3). 当我们添加一个索引,不能够解决所有查询问题,需要分别给字段建立索引;例如 select * from EMP where ename='abcdef';
(4). 索引是以空间换时间
4、创建索引
(1). 创建主索引
第一种方式:create table user1(id int primary key, name varchar(30));
-- 在创建表的时候,直接在字段名后指定 primary key
第二种方式:create table user2(id int, name varchar(30), primary key(id));
-- 在创建表的最后,指定某列或某几列为主键索引
第三种方式:
create table user3(id int, name varchar(30));
alter table user3 add primary key(id); -- 创建表以后再添加主键
主键索引的特点:
1. 一个表中,最多有一个主键索引,当然可以使符合主键
2. 主键索引的效率高(主键不可重复)
3. 创建主键索引的列,它的值不能为null,且不能重复
4. 主键索引的列基本上是int
(2).唯一键索引
第一种方式:create table user4(id int primary key, name varchar(30) unique);
-- 在表定义时,在某列后直接指定unique唯一属性。
第二种方式: create table user5(id int primary key, name varchar(30), unique(name));
-- 创建表时,在表的后面指定某列或某几列为unique
第三种方式:
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);
唯一索引的特点:
1. 一个表中,可以有多个唯一索引
2. 查询效率高
3. 如果在某一列建立唯一索引,必须保证这列不能有重复数据
4. 如果一个唯一索引上指定not null,等价于主键索引
(3).普通索引的创建
第一种方式
mysql> create table user8(id int primary key,
-> name varchar(20),
-> email varchar(30),
-> index(name) --在表的定义最后,指定某列为索引
-> );
第二种方式
mysql> create table user9(id int primary key, name varchar(20), email varchar(30));
mysql> alter table user9 add index(name); --创建完表以后指定某列为普通索引
第三种方式
mysql> create table user10(id int primary key, name varchar(20), email varchar(30));-- 创建一个索引名为 idx_name 的索引
mysql> create index idx_name on user10(name);
普通索引的特点:
1. 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
2. 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
(4). 全文索引的创建
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求 |
表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。 |
如果对中文进行全文检索,可以使用sphinx的中文版(coreseek)。 |
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
)engine=MyISAM;
添加数据
INSERT INTO articles (title,body) VALUES ('MySQL Tutorial','DBMS stands for DataBase ...'),('How To Use MySQL Well','After you went through a ...'),('Optimizing MySQL','In this tutorial we will show ...'),('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),('MySQL vs. YourSQL','In the following database comparison ...'),('MySQL Security','When configured properly, MySQL ...');
查询有没有 database 数据:
如果使用如下查询方式,虽然查询出数据,但是没有使用到全文索引
mysql> select * from articles where body like '%database%';
5、查询索引
方法1:show keys from 表名
方法2: show index from 表名;
方法3: desc 表名; -- 但是信息比较简略
6、删除索引
方法1: 删除主键: alter table 表名 drop primary;
方法2: 其他索引的删除:alter table 表名 drop index 索引名;--索引名就是show keys from 表名中的 Key_name 字段
mysql> alter table user10 drop index idx_name;
方法3 : drop index 索引名 on 表名
<mysql> drop index name on user8;
7、索引的原则
比较平凡作为查询条件的字段应该创建索引唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
select * from EMP where sex=' 男 ';
更新非常频繁的字段不适合作创建索引
不会出现在 where 子句中的字段不该创建索引
事物
事务就是一组 dml 语句组成,这些语句在逻辑上存在相关性,这一组 dml 语句要么全部成功,要么全部失败,是一
个整体。 MySQL 提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据时不相同的。
事务理论的深度理解参见推荐书籍:《数据库系统概念》的第 14 章。
银行转账的例子更需要考虑事务的问题。
create table account(
id int primary key,
name varchar(50) not null default '',
balance decimal(10, 2) not null default 0.0
);
基本使用:
开始一个事务 start transaction;
做一个保存点 savepoint 保存点名;
进行各种操作
如果需要,可以回到保存点 rollback to 保存点名
示例:
mysql> start transaction; -- 开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> savepoint aa; -- 设置保存点 aa
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(1, ' 张三 ', 10); -- 添加一条记录
Query OK, 1 row affected (0.00 sec)
mysql> savepoint bb; -- 设置保存点 bb
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values(2, ' 李四 ', 10000); -- 再添加一条记录
Query OK, 1 row affected (0.00 sec)
mysql> select * from account; -- 两条记录都在了
mysql> rollback to bb; -- 发现后来添加这一条记录是误操作。所以回滚到 bb 状态
Query OK, 0 rows affected (0.01 sec)
mysql> select * from account; -- 第二条记录没有了
事务操作注意点:
(1).如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback( 前提是还没有提交 )
(2). 如果一个事务被提交了,则不可以回退( commit )
(3). 可以选择回退到哪个保存点
(4).InnoDB 支持事务, MyISAM 不支持事务。
(5).开始事务可以使 start transaction 也可以是 set autocommit = 0;
1、 事务的隔离级别
MySQL 表被多个线程或者客户端开启各自事务操作数据库中的数据时, MySQL 提供了一种机制,可以让不同的
事务在操作数据时,具有隔离性。从而保证数据的一致性。如果不考虑隔离性,可能会引发一下问题:
脏读
不可重复读
幻读
事务的隔离级别有几种:
2、设置事务的隔离级别
语法: set session transaction isolation level read uncommitted;
查看当前的隔离级别: mysql> select @@tx_isolation;
脏读:一个客户端(事务)会读取到另外一个客户端(事务)没有提交的修改数据。
不可重复读:同一个查询在同一个事务中多次进行,由于其他提交事务所做的修改或删除,每次返回不同的结果
集,此时发生不可重复读。
幻读:同一个查询在同一个事务中多次进行,由于其他提交事务所做的插入操作,每次返回不同的结果集,此时发
生幻读。可串行化,如下图所示:
注意: mysql默认的隔离级别是可重复读,一般情况下不要修改
3、事务的ACID特性
(1). 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
(2). 一致性(Consistency):事务必须使数据库从一个一致性状态变到另外一个一致性状态。
(3). 隔离性(Isolation):事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据
所干扰,多个并发事务之间要相互隔离。
(4). 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中的数据的修改就是永久性的,接下来即使数据库发生故障也
不应该对其有任何影响
视图
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表,基表的数据变化也会影响到视图
1、创建视图 create view 视图名 as select语句;
示例:mysql> create view v_ename_dname as
-> select ename, dname
-> from EMP, DEPT where EMP.deptno=DEPT.deptno;
mysql> select * from v_ename_dname;
2、修改了视图,对基表数据有影响
mysql> update v_ename_dname set dname='sales' where ename='CLARK';
mysql> select * from EMP where ename='CLARK';
3、修改了基表,对视图有影响
mysql> update EMP set deptno=20 where ename='JAMES'; -- 修改基表
4、删除视图 drop view 视图名;
5、视图和表的区别
(1). 表要占用磁盘空间,视图不需要
(2). 视图不能添加索引
(3). 使用视图可以简化查询
(4). 视图可以提高安全性