数据库相关概念
在这一部分,我们先来讲解三个概念:数据库、数据库管理系统、SQL。数据库是一种数据管理软件系统。 全称:DBMS——数据库管理系。数据库
所以我们需要独立的、专用的数据存储技术,这就是数据库的出现。
- 存储数据的仓库,数据是有组织的进行存储
- 英文:DataBase(DB)
数据库管理系统
- 操纵和管理数据库的大型软件
- 英文:DataBase Management System (简称DBMS)
SQL(结构化查询语言)
- 操作关系型数据库的编程语言
- 定义了一套操作所有关系型数据库统一标准
- 英文:Structured Query Language (SQL)
常见的关系型数据库管理系统
- Oracle:大型的收费数据库,Oracle公司产品,价格昂贵。
- MySQL:开源免费的中小型数据库,后来Sun公司收购了MySQL,而Oracle又收购了Sun公司。
- 目前Oracle推出了收费版本的MySQL,也提供了免费的社区版本。SQL Server:Microsoft 公司推出的收费的中型数据库,C#、.net等语言常用。
- PostgreSQL:开源免费的中小型数据库。
- DB2:IBM公司的大型收费数据库产品。
- SQLLite:嵌入式的微型数据库。Android内置的数据库采用的就是该数据库。
- MariaDB:开源免费的中小型数据库。是MySQL数据库的另外一个分支、另外一个衍生产品,与
- MySQL数据库有很好的兼容性。
而不论我们使用的是上面的哪一个关系型数据库,最终在操作时,都是使用SQL语言来进行统一操作,因为我们前面讲到SQL语言,是操作关系型数据库的 统一标准 。所以即使我们现在学习的是MySQL, 假如我们以后到了公司,使用的是别的关系型数据库,如:Oracle、DB2、SQLServer,也完全不用 担心,因为操作的方式都是一致的。数据库的操作是遵循的标准的SQL(结构化查询语言),所有的数据库都必须支持它。
Java开发里,使用的主流数据库就是MySQL数据库,大小满足中小企业应用开发,性能可靠,免费。
数据库的基本概念
数据库服务器:MySQL本身就是一个数据库Server,数据存放在它当中由它进行管理,外部通过网络端口向他发送请求,然后它也通过网络端口向客户端发送结果。
客户端:
1、MySQL自己本身自带一个客户端;这个客户端是命令行的;
2、图形化客户端程序;不同的公司开发了很多不同的图形化客户端程序。常用的:Navicat。Sqlyog。DataGrip。Toad
3、我们以后书写的应用程序也需要用到数据库的数据,那么也是以客户端的形式存在。
主要学习数据库的SQL语句,完成对数据的CRUD(增删查改)操作。
数据库
MySQL只是一个数据库管理系统,它里面才是数据库。每一个数据库管理系统当中会自带一些原生的数据库,不能去操作,它们装的是服务器自己的信息数据。我们也可以创建自己的数据库。
数据表
数据库里装的是数据表,每一个数据表都是一个Excel的表格。
每一个实体类就对应一张数据表,表里面的每一列就是该类的一个属性,每一行就是该类的一个对象。
主键:(PK,Primary Key)
是每张表里的一个特殊列,它不是实体里需要展示或外部操作的数据,而是专用于数据库里表示表表面的行唯一性的表示。主键要求非空、唯一、与业务无关。
外键:(FK,Forergn Key)
是在A表当中存放B表的主键,用来表示A的每行和B每行的关联关系。
表与表之间的关联关系
1、一对一关系
外键既可以设计在A表上,也可以设计在B表上。但是必须保证外键的唯一性。
2、一对多关系
外键必须设计在多方,且不能唯一。
3、多对多关系
增加一张关系表作为A和B的中间表,里面设计两个外键,各自对应A和B。这张表不存放任何实体数据,只存放A和B两者的关系。
主从关系
提供主键给别人充当外键的叫做主表,拥有别人主键充当自己外键的是从表。
主从关系最大的一个操作特点:如果要删除主表里的数据,必须先保证从表没有用这个数据。
关系型数据库
概念:
建立在关系模型基础上,由多张相互连接的 二维表 组成的数据库。特点:
- A. 使用表存储数据,格式统一,便于维护。
- B. 使用SQL语言操作,标准统一,使用方便。可用于复杂查询。
- C.数据存储在磁盘里,安全。
而所谓二维表,指的是由行和列组成的表,(就类似于Excel表格数据,有表头、有列、有行, 还可以通过一列关联另外一个表格中的某一列数据)。我们之前提到的MySQL、Oracle、DB2、 SQLServer这些都是属于关系型数据库,里面都是基于二维表存储数据的。简单说,基于二维表存储数据的数据库就成为关系型数据库,不是基于二维表存储数据的数据库,就是非关系型数据库。 关系型数据库是依赖于主外键来表示表与表之间的关联关系。数据模型
- MySQL是关系型数据库,是基于二维表进行数据存储的.
- 我们可以通过MySQL客户端连接数据库管理系统DBMS,然后通过DBMS操作数据库。
- 可以使用SQL语句,通过数据库管理系统操作数据库,以及操作数据库中的表结构及数据。
- 一个数据库服务器中可以创建多个数据库,一个数据库中也可以包含多张表,而一张表中又可以包含多行记录。
SQL语句
- 结构化查询语句,是数据库里专用的操作语言,数据库只认这种语言。
- 上课时主讲标准SQL,也就是所有数据库通用。但是不同的数据库在设计时也提供了自己特有的SQL语句(方言)。
- 全称 Structured Query Language,结构化查询语言。操作关系型数据库的编程语言,定义了一套操作关系型数据库统一标准 。
SQL通用语法
- SQL可以单行或多行书写,以分号结尾
- SQL语句可以使用空格/缩进来增强语句的可读性。
- 数据库的SQL语句不区分大小写,关键字建议使用大写。
- 注释:单行注释:-- 注释内容 或 #注释内容(MySQL特有);多行注释:/* 注释 */
分类:
SQL语句,根据其功能,主要分为四类:DDL、DML、DQL、DCL。
DDL(Data Definition Language):数据定义语言,用来定义数据库对象:数据库、表、列等
建库、改库、删库、建表、修改表、删除表。。。。。主要是操作数据库结构的。
DDL——操作数据库
1、查询:
- 查询所有数据库:SHOW DATABASES;
2、创建:
- 创建数据库:CREATE DATABASE 数据库名称;
- 在同一个数据库服务器中,不能创建两个名称相同的数据库,否则将会报错。可以通过if not exists 参数来解决这个问题,数据库不存在, 则创建该数据库,如果存在,则不创建。
- 创建数据库(判断,如果不存在则创建):
- CREATE DATABASE IF NOT EXISTS 数据库名称;
创建一个itheima数据库,并且指定字符集 : create database itheima default charset utf8mb4;3、删除
- 删除数据库:DROP DATABASE 数据库名称;
如果删除一个不存在的数据库,将会报错。此时,可以加上参数 if exists ,如果数据库存在,再执行删除,否则不执行删除。- 删除数据库(判断,如果存在则删除):
- DROP DATABASE IF EXISTS 数据库名称;
4、使用数据库
- 查看当前使用的数据库:SELECT DATABASE();
- 使用数据库:USE 数据库名称;
DDL——操作表
1、创建表(Create)
CREATE TABLE 表名( 字段1 字段1类型 [ COMMENT 字段1注释 ], 字段2 字段2类型 [COMMENT 字段2注释 ], 字段3 字段3类型 [COMMENT 字段3注释 ], ...... 字段n 字段n类型 [COMMENT 字段n注释 ] ) [ COMMENT 表注释 ] ;
注意: [...] 内为可选参数,最后一个字段后面没有逗号
create table tb_user( id int comment '编号', name varchar(50) comment '姓名', age int comment '年龄', gender varchar(1) comment '性别' ) comment '用户表';
2、查询表(Retrieve)
- 查询当前数据库下所有表的名称:SHOW TABLES;
- 查询指定表结构:DESC 表名称;
3、修改表(Update)
修改表名:ALTER TABLE 表名 RENAME TO 新的表名;
添加一列:ALTER TABLE 表名 ADD 列名 数据类型;
修改数据类型:ALTER TABLE 表名 MODIFY 列名 新数据类型;
修改列名和数据类型:ALTER TABLE 表名 CHANGE 列名 新列名 新数据类型;
删除列:ALTER TABLE 表名 DROP 列名;
4、删除表(Delete)
DROP TABLE 表名;
删除表时判断表是否存在:DROP TABLE IF EXISTS 表名;
命名规范:
表名必须以 t_ 开头;
普通列名使用f_开头;
主键名 pk_ 开头;
外键名 fk_ 开头。
表操作-数据类型:
MySQL 中的数据类型有很多,主要分为三类:数值类型、字符串类型、日期时间类型。常用的如下:Java MySQL
int int
float float
double double
String varchar(长度)
boolean tinyInt
LocalDate date
LocalDateTime timesnamp
约束:
非空约束——NOT NULL
唯一约束——UNIQUE
默认值——DEFAULT
主键约束——PRIMARY KEY
外键约束
约束的主要作用就是为了保证数据的完整性。数据完整性分为:
1、实体的完整性——由于一个表是一个类,那么一个实体就是表里的一行,所以实现完整性就是保证这一行的有效性。
主键约束和唯一约束。
2、域完整性——是保证表里的某一列的有效性。非空约束、默认值
3、引用约束——是保证表与表之间的关联关系的正确性。外键约束
DML(Data Manipulation Language):数据操作语言,用来对数据库里表的数据进行增删改。
往表里添加记录,修改记录,删除记录,即增删改的动作。
增
添加数据(INSERT)
1、单行数据的增加
INSERT INTO 表名 (字段名1, 字段名2, ...) VALUES (值1, 值2, ...);
注意:关键字不能错;表名、列名必须正确;列名的顺序要和值的顺序保持一致;值必须满足列的数据类型以及约束。
如果列有自动增长或者默认值,或允许为空,那么可以在语句里省略这个列。
一般在新增的时候,主键不会由我们去插入数据值,一般都设计为自动增长。不同的数据库提供了不同的算法来完成自动增长,MySQL采用的是先查询当前的最大主键值,然后自动加一。
案例: 给employee表所有的字段添加数据
insert into employee(id,workno,name,gender,age,idcard,entrydate) values(1,'1','Itcast','男',10,'123456789012345678','2000-01-01');
2、一次性增加多行记录
MySQL方言:
INSERT INTO 表名 (列1,列2,列3,......)
VALUES(值1,值2,值3,.........),
(值1,值2,值3,.........),
(值1,值2,值3,.............),
...................
删
删除数据(DELETE)
DELETE FROM 表名 [ WHERE 条件 ] ;
注意:慎用不带where条件的delete语句
特殊语句:TRUNCATE TABLE 表名;
这条语句是直接删除表里所有的数据,不能加where条件。
这条语句与不加where条件的delete语句的区别是:虽然两者都是把表里面的数据全部清除,但是:1、delete语句执行时,数据库是会书写日志记录的,数据库管理人员是可以通过日志恢复数据的;但是TRUNCATE语句执行是不写日志的,所以也就不能被恢复;2、正是因为它不写日志,所以TRUNCATE的执行效率要比DELETE语句执行的效率要高。
改
修改数据(UPDATE)
UPDATE 表名 SET 列1 = 值1 , 列2 = 值2 , .... [ WHERE 过滤条件 ] ;
注意:关键字不能写错,表名列名不能写错;列必须是在这张表里面,慎用不带where条件的update语句。
where 后面的条件判断:等于=,小于<,大于>,小于等于<=,大于等于>=,不能与<> !=,非空 is not null,空is null,逻辑与 AND,逻辑或 OR
DQL(Data Query Language):数据查询语言,用来查询数据库里表的记录(数据)
我们在讲解这部分内容的时候,会将上面的完整语法进行拆分,分为以下几个部分:基本查询(不带任何条件)条件查询(WHERE)聚合函数(count、max、min、avg、sum)分组查询(group by)排序查询(order by)分页查询(limit)SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后条件列表 ORDER BY 排序字段列表 LIMIT 分页参数
查询操作的分类:
1、投影(基础查询)
指定查询结过里显示有哪些列;
语句:SELECT 列1,列2,。。。。from 表名
意思是:从哪张表里选择投影出哪些列;由于没有行选择,那么表里所有行的这些列都会被投影出来。
当投影语句被执行的时候,就会从实体表里面根据语句的选择形成一个投影表,这是一个虚拟表,我们通常把它称为“结果表”。
查询多个字段
- SELECT 字段1, 字段2, 字段3 ... FROM 表名 ;
- SELECT * FROM 表名 ;
- 注意 : * 号代表查询所有字段,在实际开发中尽量少用(不直观、影响效率)。
字段设置别名
- SELECT 字段1 [ AS 别名1 ] , 字段2 [ AS 别名2 ] ... FROM 表名;
- SELECT 字段1 [ 别名1 ] , 字段2 [ 别名2 ] ... FROM 表名;
去除重复记录
- SELECT DISTINCT 字段列表 FROM 表名;
DISINCT去重复关键字
2、条件查询(选择)
SELECT 字段列表 FROM 表名 WHERE 条件列表 ;指定哪些行出现在结果里;
就是在select from后面加where子句,用来选择满足条件的行才能进行投影。之前在Update、Delete当中所有讲的基本选择操作,在Select里全部适用。除此之外还有特殊的条件表达。
DCL(Data ControlLanguage):数据控制语言,用来定义数据库的访问权限和安全级别,及创建用户
定义函数,书写执行过程,包括if、else、循环都可以做
练习
-- 1、查询出所有来自于‘皇家马德里’俱乐部的球员; SELECT f_name,f_club FROM t_player WHERE f_club = '皇家马德里'; -- 2、查询出所有俱乐部为'未知'的球员; SELECT f_name,f_club FROM t_player WHERE f_club = '未知'; -- 3、把俱乐部为'未知'的球员的俱乐部改为null UPDATE t_player SET f_club = NULL WHERE f_club = '未知'; SELECT f_name,f_club FROM t_player WHERE f_club IS NULL; -- 4、找到没有俱乐部的前锋 SELECT * FROM t_player WHERE f_place = '前锋' AND f_club IS NULL; -- 5、找到所有的门将,按年龄进行降序 SELECT * FROM t_player WHERE f_place = '门将' ORDER BY f_dateOfBirth ASC; -- 6、找到所有名字有'斯基'或‘科夫’结尾的俄罗斯血统球员; SELECT * FROM t_player WHERE f_name LIKE '%斯基' OR f_name LIKE '%科夫'; -- 7、显示所有派出球员参加本次欧洲杯的俱乐部; SELECT DISTINCT f_club FROM t_player WHERE f_club != '无' AND f_club IS NOT NULL; -- 8、找到来自于意甲俱乐部:尤文图斯、AC米兰、国际米兰、罗马的球员。 SELECT * FROM t_player WHERE f_club = '尤文图斯' OR f_club = 'AC米兰' OR f_club = '国际米兰' OR f_club = '罗马'; SELECT * FROM t_player WHERE f_club IN ('尤文图斯','AC米兰','国际米兰','罗马'); -- 9、找出所有的90后前锋,按年龄从大到小排序 SELECT * FROM t_player WHERE f_place = '前锋' AND f_dateOfBirth BETWEEN '1990-1-1' AND '1999-12-31' ORDER BY f_dateOfBirth ASC; -- 10、找出比你大10岁的同一天出生的球员; SELECT * FROM t_player WHERE f_dateOfBirth = '1989-11-21';
聚合函数是在SQL当中提供的专门做统计用的一组函数。
1、Count()函数:统计一共有多少行。
count(*) 计算所有满足条件的行,包括null
select count(*) from 表名
count(ALL 列名) 统计的是该列名为非空的有多少行。
注意:ALL可以不写,直接写count后面跟列名count(列名)
count (distinct 列名) 指定该列值非空的不重复值的个数。
2、sum()函数:统计某一列的数据值总和!
sum(ALL 列名) 或 sum(列名) 效果是这一列所有非null值的总和。
sum(distinct 列名) 这一列的所有非null且不重复的值的总和。
3、avg()函数:统计某一列数据值的平均值
avg(ALL 列名) 或 avg(列名) 效果是这一列所有非null值的法平均值。
avg(distinct 列名) 这一列的所有非null且不重复的值的平均值。
4、max()函数:统计某一列数据值的最大值
5、min()函数:统计某一列数据值的最小值
max和min只有一种写法min(列名) ;max(列名)的到该列的最大值和最小值。
过滤分组数据(分组查询)
SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后过滤条件 ];所谓的分组,就是将原始数据按某列或某几列拆分成多个小组,然后投影出这个小组的聚合信息。
语法:GROUP BY 列名1,列名2
语法解析:select 投影列 from 表 where 过滤条件 group by 分组列
- 先执行from,加载原生数据
- 在执行where,对原生数据表数据进行条件过滤,形成过滤表;
- 在执行group by ,将过滤表按分组列进行拆分为多个小组表,分组列值一样的数据在一个小组
- select投影,每个小组会在投影后的结果集当中形成一行记录。每行显示的列应该是该小组统一共享的信息而不是该小组某一行特有的信息。所以,select后面的投影列有个要求:必须是分组相关列或者是改组的聚合函数。
having过滤
在group by 分组列后加having:对分组后的投影数据再次进行过滤
语法解析:select 投影列 from 表 where 过滤条件1 group by 分组列 having 过滤条件2;
面试常考的题目:
where子句和having子句区别
- 执行时机不同:where是分组之前进行过滤,不满足where条件,不参与分组;而having是分组之后对结果进行过滤。
- 判断条件不同:where不能对聚合函数进行判断,而having可以。
注意事项:
- 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义。
- 执行顺序: where > 聚合函数 > having 。
- 支持多字段分组, 具体语法为 : group by columnA,columnB
排序查询
排序在日常开发中是非常常见的一个操作,有升序排序,也有降序排序。SELECT 字段列表 FROM 表名 ORDER BY 字段 1 排序方式 1 , 字段 2 排序方式 2 ;排序方式
- ASC : 升序(默认值)
- DESC: 降序
注意事项:
- 如果是升序, 可以不指定排序方式ASC ;
- 如果是多字段排序,当第一个字段值相同时,才会根据第二个字段进行排序
分页查询
分页操作在业务系统开发时,也是非常常见的一个功能,我们在网站中看到的各种各样的分页条,后台都需要借助于数据库的分页操作。SELECT 字段列表 FROM 表名 LIMIT 起始索引 , 查询记录数 ;注意事项:
- 起始索引从0开始,起始索引 = (查询页码 - 1)* 每页显示记录数。
- 分页查询是数据库的方言,不同的数据库有不同的实现,MySQL中是LIMIT。
- 如果查询的是第一页数据,起始索引可以省略,直接简写为 limit 10。
执行顺序
在讲解 DQL 语句的具体语法之前,我们已经讲解了 DQL 语句的完整语法,及编写顺序,接下来,我们要来说明的是 DQL 语句在执行时的执行顺序,也就是先执行那一部分,后执行那一部分。
子查询
子查询从概念上来说,就是在一个查询语句当中,嵌入另一个查询语句。
- 语法上,一个查询语句分为:select子句;from子句;where子句;group by子句;having 子句;order by子句
- 理论上,每一个子句内部都可以嵌入一个select语句,只不过有些情况用的很少,而常用的select子句主要出现在:select 子句里的投影列;
from 子句后面;while子句后面;having子句。
select子句嵌套
外层的select语句里企图投影一个需要单独查询数据的列的时候,那么可以在select子句里嵌入select。
它主要用在投影的数据列不是来自同一张表的情况,需要根据关联关系从另一张表里获取关联数据进行统一的展示
语法:SELECT 列1,(子查询) as 别名 from 表名
这样的子查询具备的特点:
- 这个子查询执行的结果必须是单行单列的;
- 外部查询没得到一行,就会执行一次内部的子查询,这就意味着子程序执行的次数依赖于外部查询得到的行数,我们把这种情况叫做"相关子查询"。在大数据量的情况下,效率很低。(不推荐使用,后面学习表链接)
from子句嵌套
from在一些特殊情况,我们可能从一个投影结果集里再次select。
语法:SELECT 列1,列2(子查询) from (子查询) as 表名
先执行子查询,得到一个结果集,然后再从这个结果集执行外部的查询
特点:
- 子查询结果可以是多行多列的。
- 这个子查询只执行一次,它的执行次数与外部查询结果无关。被称为非相关子查询;
- 往往这样的子查询是可以优化的解决的。
where子句和having子句嵌套
在where和having的过滤条件里,某个条件的值来自于一个子查询的结果。
语法:
a、SELECT 列1,列2(子查询) from 表 where 列=(子查询)
这种子查询要求得到的结果是单行单列,=可以使用>,<,>=,<=,<>等
b、SELECT 列1,列2(子查询) from 表 where 列 in(子查询)
这种子查询的结果可以是多行单列,这里的in也可以替换成not in;
c、ALL、ANY、EXSITS、NOT EXSITS
这几个运算符是专门给条件过滤子查询专用的。
- ALL指的是需要对子查询里所有的值都要进行比对,且结果为真
- ANY指的是需要对子查询里所有的值都要进行比对,只要有一个让结果为真就可以了。
- EXSITS和NOTEXSITS值得是存在或不存在,也就是可以和in 和 not in 进行替换
练习:
UPDATE bbc SET region = 'Middle East' WHERE region = 'Middle t'; -- 显示世界人口总和 SELECT SUM(population) AS 世界人口总和 FROM bbc; -- 列出所有的地区,每个地区只显示一次 SELECT DISTINCT region AS 地区 FROM bbc; -- 显示GDP超过所有Africa国家总和的国家 SELECT contryName AS 国家 , gdp FROM bbc WHERE gdp > (SELECT SUM(gdp) FROM bbc WHERE region = 'Africa'); -- 显示每个地区及该地区国家总数 SELECT DISTINCT region AS 地区 , COUNT(*) AS 国家总数 FROM bbc GROUP BY region; -- 显示每个地区的总人口数和总面积,以总人口排序 SELECT DISTINCT region AS 地区 , SUM(population)AS 总人数,SUM(contryArea) AS 总面积 FROM bbc GROUP BY region ORDER BY 总人数 ; -- 显示每个地区以及该地区国家总人口数不少于10000000的国家总数 SELECT DISTINCT region AS 地区 , count(*) AS 国家数量 FROM bbc WHERE population >= 10000000 GROUP BY region; -- 列出人口总数不少于100000000的地区 SELECT DISTINCT region AS 地区 , SUM(population) AS 人口总数 FROM bbc GROUP BY region HAVING 人口总数>=100000000; -- 显示欧州人口总数和总GDP SELECT SUM(population) AS 欧洲人口总数 , SUM(gdp) AS 欧洲总GDP FROM bbc WHERE region = 'Europe'; -- 显示每个地区的总人口数和总面积,以总人口排序,仅显示那些面积超过1000000的地区 SELECT DISTINCT region AS 地区, SUM(population) AS 总人口数, SUM(contryArea) AS 总面积 FROM bbc GROUP BY region HAVING 总面积 >= 1000000 ORDER BY 总人口数;
-- 获得到和‘Brazil’同属一个地区的所有国家 SELECT contryName AS 国家名 FROM bbc WHERE region = ( SELECT region AS 地区 FROM bbc WHERE contryName = 'Brazil' ); -- 给出人口多于Russia的国家名称 SELECT contryName AS 国家名 FROM bbc WHERE population > ( SELECT population AS Russia人口 FROM bbc WHERE contryName = 'Russia' ); -- 给出'India','Iran'所在地区的所有国家信息 SELECT contryName AS 国家名 FROM bbc WHERE region IN ( SELECT region AS 地区 FROM bbc WHERE contryName IN ('India', 'Iran') ); -- 给出人均gdp超过'United Kingdom'(英国)的欧州国家 SELECT contryName AS 国家名 FROM bbc WHERE region = 'Europe' AND gdp / population > ( SELECT gdp / population FROM bbc WHERE contryName = 'United Kingdom' ); -- 给出人口比'Canada'多但少于Algeria的国家的信息 SELECT contryName AS 国家名 FROM bbc WHERE population > ( SELECT population FROM bbc WHERE contryName = 'Canada' ) AND population < ( SELECT population FROM bbc WHERE contryName = 'Algeria' ); -- 给出gdp比任何欧州国家都多的国家 SELECT contryName AS 国家名 FROM bbc WHERE gdp > ALL ( SELECT MAX(gdp) FROM bbc WHERE region = 'Europe' ) AND gdp IS NOT NULL; -- 给出每个地区人口最大的国家 SELECT contryName AS 国家名, population AS 人口 FROM bbc AS a WHERE population = ( SELECT MAX(population) FROM bbc AS b WHERE a.region = b.region ); SELECT contryName AS 国家名, population AS 人口 FROM bbc WHERE population IN ( SELECT MAX(population) FROM bbc GROUP BY region ); -- 给出地区中所有国家的人口总数为0的地区 SELECT region AS 地区 FROM bbc WHERE 0 = ALL ( SELECT SUM(population) FROM bbc GROUP BY region ); SELECT region AS 地区,SUM(population) FROM bbc GROUP BY region HAVING SUM(population) = 0; -- SELECT region AS 地区 FROM bbc GROUP BY region HAVING SUM(population) = 0; -- 有些国家的人口数比她的周边国家要多三倍,请列出这些国家和地区。 SELECT contryName AS 国家名, region AS 地区 FROM bbc AS a WHERE population > ALL ( SELECT population * 4 FROM bbc AS b WHERE b.region = a.region AND b.contryName != a.contryName );
-- 给出诺贝尔奖获奖总人数 -- SELECT -- COUNT(DISTINCT winner) AS 获奖总人数 -- FROM -- nobel; -- 给出诺贝尔物理奖获奖次数 -- SELECT -- COUNT(DISTINCT yr) AS nobel物理奖获奖次数 -- FROM -- nobel -- WHERE -- `SUBJECT` = 'Physics'; -- 显示每个奖项获奖总次数 SELECT DISTINCT `SUBJECT` AS 奖项, COUNT(*) AS 总次数 FROM nobel GROUP BY `SUBJECT`; UPDATE nobel SET `SUBJECT` = 'Literature' WHERE `SUBJECT` = 'Literre'; -- 显示每个奖项第一次获得该奖的年份 SELECT DISTINCT `SUBJECT` AS 奖项, MIN(yr) AS 年份 FROM nobel GROUP BY `SUBJECT`; -- 显示每个奖项在2000年获奖的人数 SELECT DISTINCT `SUBJECT` AS 奖项, COUNT(*) AS 人数 FROM nobel WHERE yr = 2000 GROUP BY `SUBJECT`; -- 显示每个奖项不同获奖者人数 SELECT DISTINCT `SUBJECT` AS 奖项, COUNT(DISTINCT winner) AS 不同获奖者人数 FROM nobel GROUP BY `SUBJECT`; -- 显示每个奖项有多少年获奖 SELECT DISTINCT `SUBJECT` AS 奖项, COUNT(DISTINCT yr) AS 年数 FROM nobel GROUP BY `SUBJECT`; -- 显示当年有三个物理奖的年份 SELECT yr AS 有三个物理奖的年份 FROM nobel WHERE `SUBJECT` = 'Physics' GROUP BY yr HAVING COUNT(yr) = 3; -- 显示得奖超过一次的获奖者 SELECT winner AS 得奖超过一次的获奖者 FROM nobel GROUP BY winner HAVING COUNT(winner) > 1; -- 显示得到多个奖项的获奖者 SELECT winner AS 得到多个奖项的获奖者 FROM nobel GROUP BY winner HAVING COUNT(DISTINCT `SUBJECT`) > 1; -- 显示2000年及以后,有三个人获得同一奖项的年份及奖项 SELECT yr AS 年份, `SUBJECT` AS 奖项 FROM nobel WHERE yr >= 2000 GROUP BY yr, `SUBJECT` HAVING COUNT(DISTINCT winner) = 3;
DQL语句
组合查询
使用UNION运算符,这个UNION关键字的作用是把两个或两个以上的select投影结果组合在同一个结果集上。
语法:
select 列1,列2 from 表1 where 。。。group by。。。 having 。。。 order by。。。 union select 列3,列4 from 表2 where 。。。group by。。。 having 。。。 order by。。。
随着需要可以继续加UNION接着组合。
注意:
1、组合在一起的多个投影列的列数必须要保持一致;
2、最终投影列的列名以第一个select语句给定的列名为准。
表联接查询
相关子查询的效率很低,如果我们需要在同一个结果集里显示多个表的数据(根据主外键关系)是非常常见的,因此就需要表联接技术
表联接技术的模型——笛卡尔乘积
所谓笛卡尔乘积,就是把两张表的每一行进行一对一的组合,得到的结果集就是这两张表里面所有行的乘积。
使用JOIN关键字
select 列1,列2。。。 from 表1 JOIN 表2; 建议使用第一种! select 列1,列2。。。 from 表1 ,表2; 简写 (方言)
直接使用,会发现得到的结果集数据是两个表行数的乘积,里面包含了大量的对应关系不正确的连结,所以需要对数据进行进一步的筛选,即如下完整结构,加入条件。条件是依赖于两张表的关联关系:利用主外键关系进行筛选。
大多数情况下:使用表1的主键=表2的外键。
完整结构
select 列1,列2。。。 from 表1 JOIN 表2 on 表1.外键 = 表2.主键; select 列1,列2。。。 from 表1 ,表2 where 表1.外键 = 表2.主键; 简写
注意:
1、两张表联接时经常出现不同表有相同列名,所以尽量使用别名来区分,或者在设计表时养成良好的习惯。
2、表联接技术的本质:把多张表根据正确的主外键关联关系关联成一场表,这多张表的信息都在这一张表里。所以可以把它们当成单表操作,就不存在相关子查询了。
表联接的分类
内联接
上面的JOIN ON形式都是内联接。
它的概念是左右两张表,所有能够对应的上的数据才能够发生联接。
在SQL当中,它的标准语法是:表1 INNER JOIN 表2 ON 匹配条件
后面简化成:表1 JOIN 表2 ON 匹配条件
外联接
指:左右两张表某一张表中不满足对应关系的数据也要被显示出来。
它分为三种情况:
1、左外联接;
join左边的那张表如果有不匹配数据,仍然要显示在结果集里。
表1 LEFT OUTER JOIN 表2 ON 匹配条件 或 表1 LEFT JOIN 表2 ON 条件
2、右外联接;
join右边的那张表如果有不匹配数据,仍然要显示在结果集里。
表1 RIGHT OUTER JOIN 表2 ON 匹配条件 或 表1 RIGHT JOIN 表2 ON 条件
3、全外联接。
join左右两边的两张表如果有不匹配数据,都要显示在结果集里。
表1 FULL OUTER JOIN 表2 ON 匹配条件 或 表1 FULL JOIN 表2 ON 条件
但是MySQL目前不支持。
总结:
注意:
1、内联接有三种写法:
表1 INNER JOIN 表2 ON 匹配条件 表1 JOIN 表2 ON 匹配条件 FROM 表1 ,表2 WHERE 表1.外键 = 表2.主键; 简写
2、只有外联有left、right、full之分,其中left和right可以替换,只需要把表一表二交换位置。
3、full外联MySQL不支持
自联接
自联接不是一种特殊语法,只是一种特殊场景的表设计,在这种场景下,我们可能需要让一张表自己连接自己。
特殊场景是:一张表的数据需要自己关联自己,也就是说该表的设计里的外键字段其实是关联自己的关键字段。常见于:员工表(里面既有员工又有领导,领导也是员工);菜单表(多级菜单)也很常见。
在这种场景下,我们需要把这两张表区别的看待成两种表,只不过这两张表长得一样同名而已,然后来做自己联接自己,最好起别名因为是自己联自己,所以表名是一样的,避免出现分不清的情况!
练习:
-- 获取所有在圣丹尼斯比赛的门将信息 SELECT * FROM ( t_group JOIN t_team ON pk_groupid = fk_groupid ) JOIN t_player ON pk_teamid = fk_teamid WHERE f_place = '门将' AND f_groupAddress = '圣丹尼斯'; -- 第二种方法 SELECT * FROM t_group, t_team, t_player WHERE pk_groupid = fk_groupid AND pk_teamid = fk_teamid AND f_place = '门将' AND f_groupAddress = '圣丹尼斯'; -- 95后前锋在哪些城市比赛 SELECT DISTINCT f_groupAddress AS 城市 FROM t_group, t_team, t_player WHERE pk_groupid = fk_groupid AND pk_teamid = fk_teamid AND f_dateOfBirth >= '1995-1-1' AND f_place = '前锋'; -- 利物浦的球员在哪些主教练手下踢球 SELECT DISTINCT f_teamCoach AS 主教练 FROM t_team JOIN t_player ON pk_teamid = fk_teamid WHERE f_club = '利物浦'; -- 皇家马德里球迷能在巴黎看到哪些皇马球员 SELECT f_name AS 球员 FROM ( t_group JOIN t_team ON pk_groupid = fk_groupid ) JOIN t_player ON pk_teamid = fk_teamid WHERE f_groupAddress = '巴黎' AND f_club = '皇家马德里'; -- 没有俱乐部的球员分别在哪里比赛且主教练是谁 SELECT f_name AS 球员, f_groupAddress AS 比赛地点, f_teamCoach AS 教练, f_club AS 俱乐部 FROM ( t_group JOIN t_team ON pk_groupid = fk_groupid ) JOIN t_player ON pk_teamid = fk_teamid WHERE f_club = 'null'; -- 在里昂比赛的4只球队分别带了多少前锋? SELECT f_teamName AS 球队, COUNT(f_place) AS 前锋个数 FROM ( t_group JOIN t_team ON pk_groupid = fk_groupid ) JOIN t_player ON pk_teamid = fk_teamid WHERE f_groupAddress = '里昂' AND f_place = '前锋' GROUP BY f_teamName;
这是涉及到数据库表设计的一个知识点。在实际里,数据库都是由专人专职完成的。
关系型数据库设计当中提出了一些范式,需要在设计的时候遵守。
七种范式:1NF、2NF、3NF、BCNF、4NF、5NF、反范式。
所有范式在实际应用当中是不可能全部遵守的,因为最后结果可能是一个字段一张表。冗余在设计时只要不过渡,那么会给我们带来方便
设计时只用到前三个范式NF!
1NF:强调数据的原子性
总结:列不可再分,行不可重复(主键的意义)。
设计当中,我们要确保主键是唯一区分行的标识字段,随着程序的运行和业务的发展,一行的主键一旦确定就不应该发生任何变化。
经典题目:身份证能不能作为员工表的主键?不能
1、它有可能随业务发生变化的;2、不见得所有员工都有身份证,比如:外籍员工;
所以在大多情况下,我们会选择一个单独的既与业务无关,又是我们自己能控制得字段来作为主键。
特殊情况得主键:
1、主键不一定是int,只是在MySQL当中自动增长采用了int累加得方式。其他类型得:如字符串、时间日期形式;
2、主键也不一定是一个字段,可以设计成多个字段组成的联合主键。
2NF:
在满足1NF的基础上,满足非主键列必须依赖于主键列,且应该是依赖于整个主键列,而不是主键列的一部分 (非主依主)。
3NF:
满足2NF的基础上,表当中的非主键列只能依赖于主键列,不能依赖于其他的非主键列(非主独立)。
在实际场景设计里,我们除了要满足三范式,还要满足场景设计。通常的设计步骤是:
1、首先在一个场景里分析对象;(找名词找动词的方式)。
2、通过这一步,我们就可以设计出我们的类,包括类的属性和行为。
3、数据库表设计是不管行为的,它只看属性。属性有两种情况,一个是这个类自己的信息,另一个是这个类的关联对象。
4、我们要考虑每个类就是一个实体,每个实体对象理论上就应该对应一个实体表。而表的字段就是这个对象身上的普通属性,只是需要增加一个主键字段。
5、关联对象再通过主外键的关系进行设计。这时要根据关系是一对一、一对多或者多对多来设计主外键。
这个对象叫做“ORM” 对象是O: Object,关系型结构是R:Relationship,它们两者之间做映射(M:Mapping)
练习1
-- 找出没有政党的议员 SELECT mspName 议员 , party 政党 FROM msp WHERE party IS NULL; -- 列出所有的政党和领导者 SELECT partyName 政党 , leader 领导者 FROM party; -- 列出所有有领导者的政党 SELECT partyName 政党 FROM party WHERE leader IS NOT NULL; -- 列出至少有一个议员的政党 SELECT partyName 政党 ,COUNT(mspName)FROM msp JOIN party ON party = partyCode GROUP BY partyCode; -- 列出议员人数少于2个的政党 SELECT partyName 政党 ,COUNT(mspName)FROM msp JOIN party ON party = partyCode GROUP BY partyCode HAVING COUNT(mspName) <2; -- 列出所有议员名字和他们所属政党 SELECT mspName 议员 ,partyName 政党 FROM msp LEFT JOIN party ON party = partyCode; -- 列出所有政党和每个政党议员人数 SELECT partyName 政党 ,COUNT(mspName) 人数 FROM msp LEFT JOIN party ON party = partyCode GROUP BY partyName;
练习2
-- 显示电影id=1中演员名单 SELECT `NAME` 演员, m.id FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE m.id = 1; -- 显示电影'Alien'中演员名单 SELECT `NAME` 演员, title 电影 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE m.title = 'Alien'; -- 显示有'Harrison Ford'参演的电影名称 SELECT title 电影, `NAME` 演员 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE `NAME` = 'Harrison Ford'; -- 显示有'Harrison Ford'参演的电影名称,但'Harrison Ford'角色排名不是第一 SELECT title 电影, `NAME` 演员 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE `NAME` = 'Harrison Ford' AND movieord != 1; -- 显示1962年发行的电影,及该影片的主角(排名第一) SELECT title 电影, `NAME` 演员 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE yr = '1962' AND movieord = 1; -- 显示'John Travolta'每年出演的电影数量 SELECT yr, COUNT(title) 电影数量 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE `NAME` = 'John Travolta' GROUP BY yr ORDER BY 电影数量 DESC; -- 列出'Julie Andrews'参演的所有电影名称以及该片的主角 SELECT title 电影, `NAME` 主角 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE title IN ( SELECT title FROM ( movie m1 JOIN casting ON m1.id = movieid ) JOIN actor a1 ON actorid = a1.id WHERE `NAME` = 'Julie Andrews' ) AND movieord = 1; -- 列出在10部电影中出任过主角的演员 SELECT `NAME` 演员 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE movieord = 1 GROUP BY `NAME` HAVING COUNT(title) = 10; -- 按演员人数的多少依次列出1978的电影以及演员数。 SELECT title 电影, COUNT(`NAME`) 演员数量 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE yr = '1978' GROUP BY m.id ORDER BY COUNT(`NAME`) DESC; -- 列出所有和'Art Garfunkel'合作过的演员 SELECT DISTINCT `NAME` 演员 FROM ( movie m JOIN casting ON m.id = movieid ) JOIN actor a ON actorid = a.id WHERE title IN ( SELECT title FROM ( movie m1 JOIN casting ON m1.id = movieid ) JOIN actor a1 ON actorid = a1.id WHERE `NAME` = 'Art Garfunkel' ) AND `NAME` != 'Art Garfunkel';
练习3
-- 找出包含song(歌曲)'Alison'的专辑title(名称)和artist(作者) SELECT title 专辑, artist 作者 FROM album JOIN track ON albumCode = album WHERE song = 'Alison'; -- 哪个artist(作者)录制了song(歌曲)'Exodus' SELECT artist 作者 FROM album JOIN track ON albumCode = album WHERE song = 'Exodus'; -- 列出track(曲目表)中所有属于'Blur' album(专辑)的song(歌曲) SELECT song 歌曲 FROM album JOIN track ON albumCode = album WHERE title = 'Blur'; -- 显示每张album(专辑)的title(名称)和包含track曲目数 SELECT title 专辑, COUNT(song) 曲目数 FROM album JOIN track ON albumCode = album GROUP BY albumCode; -- 显示每张album(专辑)的title(名称),以及曲目名称中包含'Heart'的曲目总数 SELECT title 专辑, COUNT(song) 曲目数 FROM album JOIN track ON albumCode = album WHERE song LIKE '%Heart%' GROUP BY albumCode; -- 找出所有song(歌名)和title(专辑名)相同的曲目 SELECT song 曲目, title 专辑名 FROM album JOIN track ON albumCode = album WHERE song = title; -- 找出所有专辑名(title)和artist(作者名)相同的专辑 SELECT DISTINCT title 专辑名 FROM album WHERE title = artist; -- 找出在两张以上专辑中出现过的歌曲,同时包含出现的次数 SELECT DISTINCT song 歌曲, COUNT(song) 次数 FROM track GROUP BY song HAVING 次数 > 2; -- 同一首歌在一个专辑里出现多次 SELECT DISTINCT song 歌曲, COUNT(DISTINCT album) 次数 FROM track GROUP BY song HAVING 次数 > 2; -- 找出每首歌曲的定价低于0.5的专辑,显示专辑标题、价格和曲目总数 SELECT title 专辑, price 价格, COUNT(song) 曲目数量 FROM album JOIN track ON albumCode = album GROUP BY albumCode HAVING price / COUNT(song) < 0.5; -- 按专辑包含曲目多少从多到少依次列出专辑名称(title)和包含曲目总数 SELECT DISTINCT title 专辑, COUNT(song) 曲目数量 FROM album JOIN track ON albumCode = album GROUP BY title ORDER BY COUNT(song) DESC;