学习时间:2020/4/17-24
使用:①廖雪峰SQL教程 ②SQL_菜鸟教程 ③《数据库系统原理》
什么是SQL?
简单地说,SQL就是访问和处理关系数据库的计算机标准语言。也就是说,无论用什么编程语言(Java、Python、C++……)编写程序,只要涉及到操作关系数据库,比如,一个电商网站需要把用户和商品信息存入数据库,或者一个手机游戏需要把用户的道具、通关信息存入数据库,都必须通过SQL来完成。
现代程序离不开关系数据库,要使用关系数据库就必须掌握SQL。
注意: 廖雪峰教程讲解如何使用SQL操作数据库,以及一种最流行的开源数据库MySQL的基本安装和使用方法。
补充: NoSQL —— 非SQL的数据库
包括MongoDB、Cassandra、Dynamo等等,它们都不是关系数据库。有很多人鼓吹现代Web程序已经无需关系数据库了,只需要使用NoSQL就可以。但事实上,SQL数据库从始至终从未被取代过!!今天,SQL数据库仍然承担了各种应用程序的核心数据存储,而NoSQL数据库作为SQL数据库的补充,两者不再是二选一的问题,而是主从关系。所以,无论使用哪种编程语言,无论是Web开发、游戏开发还是手机开发,掌握SQL,是所有软件开发人员所必须的。
数据库是一门专门管理数据的软件。应用程序不需要自己管理数据,而是通过数据库软件提供的接口来读写数据。至于数据本身如何存储到文件,那是数据库软件的事情,应用程序自己并不关心。
数据库按照数据结构来组织、存储和管理数据,实际上,数据库一共有三种模型:
1.层次模型
2.网状模型
3.关系模型
这一块为简要介绍,在1.4里会更详细的介绍~
关系数据库的关系模型是基于数学理论建立的。
案例——以学校班级为例,一个班级的学生就可以用一个表格存起来,并且定义如下:
其中,班级ID对应着另一个班级表:
通过给定一个班级名称,可以查到一条班级记录,根据班级ID,又可以查到多条学生记录,这样,二维表之间就通过ID映射建立了“一对多”关系。
如果我们把班级表分拆得细一点,例如,单独创建一个教师表:
班级表只存储教师ID:
这样,一个班级总是对应一个教师,班级表和教师表就是“一对一”关系。
对于一个关系表,除了定义每一列的名称外,还需要定义每一列的数据类型。
★通常来说,BIGINT能满足整数存储的需求,VARCHAR(N)能满足字符串存储的需求,这两种类型是使用最广泛的。
为啥要学SQL ?
SQL语句既可以查询数据库中的数据,也可以添加、更新和删除数据库中的数据,还可以对数据库进行管理和维护操作。不同的数据库,都支持SQL,这样,我们通过学习SQL这一种语言,就可以操作各种不同的数据库。
SQL的功能拓展 :
虽然SQL已经被ANSI组织定义为标准,不幸地是,各个不同的数据库对标准的SQL支持不太一致。并且,大部分数据库都在标准的SQL上做了扩展。也就是说,如果只使用标准SQL,理论上所有数据库都可以支持,但如果使用某个特定数据库的扩展SQL,换一个数据库就不能执行了。例如,Oracle把自己扩展的SQL称为PL/SQL,Microsoft把自己扩展的SQL称为T-SQL。各个数据库支持的各自扩展的功能,通常我们把它们称之为“方言”。
总的来说,SQL语言定义了这么几种操作数据库的能力:
DDL:Data Definition Language
DDL允许用户定义数据,也就是创建表、删除表、修改表结构这些操作。通常,DDL由数据库管理员执行。
DML:Data Manipulation Language
DML为用户提供添加、删除、更新数据的能力,这些是应用程序对数据库的日常操作。
DQL:Data Query Language
DQL允许用户查询数据,这也是通常最频繁的数据库日常操作。
SQL语言关键字不区分大小写!!!但是,针对不同的数据库,对于表名和列名,有的数据库区分大小写,有的数据库不区分大小写。同一个数据库,有的在Linux上区分大小写,有的在Windows上不区分大小写。
廖雪峰教程约定:
SQL关键字总是大写,以示突出,表名和列名均使用小写。
MySQL是目前 应用最广泛 的开源关系数据库。MySQL最早是由瑞典的MySQLAB公司开发,该公司在2008年被SUN公司收购,紧接着,SUN公司在2009年被Oracle公司收购,所以MySQL最终就变成了Oracle旗下的产品。
和其他关系数据库有所不同的是,MySQL本身实际上只是一个SQL接口,它的内部还包含了多种数据引擎,如——
InnoDB:不知道应该采用哪种引擎,记住总是选择InnoDB就好了。
//警告:第一次安装的时候看错了下了32位,配置完以后setup程序就卡死了…求稳的话直接走廖雪峰的这个教程。
关系数据库是建立在关系模型上的,而关系模型本质上就是若干个存储数据的二维表,可以把它们看作很多Excel表。
表的每一行称为记录(Record),记录是一个逻辑意义上的数据。
表的每一列称为字段(Column),同一个表的每一行记录都拥有相同的若干字段。
字段定义了数据类型(整型、浮点型、字符串、日期等),以及是否允许为NULL。注意NULL表示字段数据不存在。一个整型字段如果为NULL不表示它的值为0,同样的,一个字符串型字段为NULL也不表示它的值为空串’’。
通常情况下,字段应该避免允许为NULL。不允许为NULL可以简化查询条件,加快查询速度,也利于应用程序读取数据后无需判断是否为NULL。
关系数据库的表和表之间需要建立“一对多”,“多对一”和“一对一”的关系,这样才能够按照应用程序的逻辑来组织和存储数据。
在关系数据库中,关系是通过主键和外键来维护的。
一张表中的每一行数据被称为一条记录。一条记录就是由多个字段组成的。每一条记录都包含若干定义好的字段。同一个表的所有记录都有相同的字段定义。
对于关系表,有个很重要的约束:任意两条记录不能重复。即:
能够通过某个字段唯一区分出不同的记录,这个字段被称为主键。
例如,假设我们把name字段作为主键,那么通过名字小明或小红就能唯一确定一条记录。但是,这么设定,就没法存储同名的同学了,因为插入相同主键的两条记录是不被允许的。
对主键的要求,最关键的一点是:记录一旦插入到表中,主键最好不要再修改,因为主键是用来唯一定位记录的,修改了主键,会造成一系列的影响。
选取主键的一个基本原则是:不使用任何业务相关的字段作为主键。
(廖雪峰大佬)如果我们以学生的身份证号作为主键,似乎能唯一定位记录。然而,身份证号也是一种业务场景,如果身份证号升位了,或者需要变更,作为主键,不得不修改的时候,就会对业务产生严重影响。因此,身份证号、手机号、邮箱地址这些看上去可以唯一的字段,均不可用作主键。
★作为主键最好是完全业务无关的字段,我们一般把这个字段命名为id。常见的可作为id字段的类型有:
廖:我们在students表中定义的主键也是BIGINT NOT NULL AUTO_INCREMENT类型。使用BIGINT自增类型则可以最多约922亿亿条记录!
~总结:应该使用BIGINT自增或者GUID类型。
关于联合主键——
关系数据库允许通过多个字段唯一标识记录,即两个或更多的字段都设置为主键,这种主键被称为联合主键。对于联合主键,允许一列有重复,只要不是所有主键列都重复即可:
注意:没有必要的情况下,我们尽量不使用联合主键,因为它给关系表带来了复杂度的上升!!
最后补充:主键也不应该允许NULL。
关系数据库通过外键可以实现一对多、多对多和一对一的关系。外键既可以通过数据库来约束,也可以不设置约束,仅依靠应用程序的逻辑来保证。
当我们用主键唯一标识记录时,我们就可以在students表中确定任意一个学生的记录:
我们还可以在classes表中确定任意一个班级记录:
但是我们如何确定students表的一条记录,例如,id=1的小明,属于哪个班级呢 ?????????
由于一个班级可以有多个学生,在关系模型中,这两个表的关系可以称为“一对多”,即一个classes的记录可以对应多个students表的记录。为了表达这种一对多的关系,我们需要在students表中加入一列class_id,让它的值与classes表的某条记录相对应:
这样,我们就可以根据class_id这个列直接定位出一个students表的记录应该对应到classes的哪条记录。(理解!)
上述内容中: 在students表中,通过class_id的字段,可以把数据与另一张表关联起来,这种列称为外键。即class_id是外键!
外键并不是通过列名实现的。外键是通过定义外键约束实现的:
ALTER TABLE students
ADD CONSTRAINT fk_class_id #外键约束的名称
FOREIGN KEY (class_id) #指定class_id作为外键
REFERENCES classes (id); #指定了这个外键将关联到classes表的id列
通过定义外键约束,关系数据库可以保证无法插入无效的数据!即如果classes表不存在id=99的记录,students表就无法插入class_id=99的记录。
廖: 由于外键约束会降低数据库的性能,大部分互联网应用程序为了追求速度,并不设置外键约束,而是仅靠应用程序自身来保证逻辑的正确性。这种情况下,class_id仅仅是一个普通的列,只是它起到了外键的作用而已。
要删除一个外键约束,也是通过ALTER TABLE实现的:
ALTER TABLE students
DROP FOREIGN KEY fk_class_id;
删除外键约束并没有删除外键这一列!删列是通过DROP COLUMN …实现的。
引入:通过一个表的外键关联到另一个表,我们可以定义出一对多关系。有些时候,还需要定义“多对多”关系。例如,一个老师可以对应多个班级,一个班级也可以对应多个老师,因此,班级表和老师表存在多对多关系。
★实现:多对多关系实际上是通过两个一对多关系实现的,即通过一个中间表,关联两个一对多关系,就形成了多对多关系——
中间表teacher_class关联两个一对多关系:(理解!!)
对上面的中间表的解释:
一对一关系是指,一个表的记录对应到另一个表的唯一一个记录。例如,students表的每个学生可以有自己的联系方式,如果把联系方式存入另一个表contacts,我们就可以得到一个“一对一”关系。
引出疑问:为啥不给students表增加一个mobile列,这样就能合二为一了?
如果业务允许,完全可以把两个表合为一个表。但是,有些时候,如果某个学生没有手机号,那么,contacts表就不存在对应的记录。实际上,一对一关系准确地说,是contacts表一对一对应students表。
★作用——有些应用会把一个大表拆成两个一对一的表,目的是把经常读取和不经常读取的字段分开,以获得更高的性能! 例如:
把一个大的用户表分拆为用户基本信息表user_info和用户详细信息表user_profiles,大部分时候,只需要查询user_info表,并不需要查询user_profiles表,这样就提高了查询速度。
查找记录的时候,想要获得非常快的速度,就需要使用索引。
索引是关系数据库中对某一列或多个列的值进行预排序的数据结构。通过使用索引,可以让数据库系统不必扫描整个表,而是直接定位到符合条件的记录,这样就大大加快了查询速度。
例如,对于students表:
如果要经常根据score列进行查询,就可以对score列创建索引:
ALTER TABLE students
ADD INDEX idx_score (score); #创建名为idx_score,使用列score的索引
(索引名称是任意的)
引如果有多列,可以在括号里依次写上,例如:
ALTER TABLE students
ADD INDEX idx_name_score (name, score);
索引的效率取决于索引列的值是否散列,即该列的值如果越互不相同,那么索引效率越高。反过来,如果记录的列存在大量相同的值,例如gender列,大约一半的记录值是M,另一半是F,因此,对该列创建索引就没有意义!
注意使用索引的代价:索引的优点是提高了查询效率,缺点是在插入、更新和删除记录时,需要同时修改索引,因此,索引越多,插入、更新和删除记录的速度就越慢。
对于主键,关系数据库会自动对其创建主键索引。使用主键索引的效率是最高的,因为主键会保证绝对唯一。
关于唯一索引 —— UNIQUE
review:在设计关系数据表的时候,看上去唯一的列,例如身份证号、邮箱地址等,因为他们具有业务含义,因此不宜作为主键。
but:这些列根据业务要求,又具有唯一性约束:即不能出现两条记录存储了同一个身份证号。这个时候,就可以给该列添加一个唯一索引!!
例如,我们假设students表的name不能重复:
ALTER TABLE students
ADD UNIQUE INDEX uni_name (name); #通过UNIQUE关键字添加一个唯一索引
补充:也可以只对某一列添加一个唯一约束而不创建唯一索引——
ALTER TABLE students
ADD CONSTRAINT uni_name UNIQUE (name);
这种情况下,name列没有索引,但仍然具有唯一性保证!!!
//廖雪峰解释如何合理使用索引:
无论是否创建索引,对于用户和应用程序来说,使用关系数据库不会有任何区别。这里的意思是说,当我们在数据库中查询时,如果有相应的索引可用,数据库系统就会自动使用索引来提高查询效率,如果没有索引,查询也能正常执行,只是速度会变慢。因此,索引可以在使用数据库的过程中逐步优化。
主要有三种。列举如下:
#单行注释
select * from students;
-- MySQL单行注释方法二
select * from students
/*
此处为注释....
哈哈哈哈嘿嘿嘿嘿
*/
select * from students;
查询是关系数据库中最常见的操作。下面的内容将以一个students表和一个classes表为例,结构 和 数据 如下:
——表1: students表
——表2: classes表
要查询数据库表的数据,我们使用如下的SQL语句:
SELECT * FROM 表名;
# 例如:
SELECT * FROM students;
# 语句解释————————
SELECT是关键字,表示将要执行一个查询。
*表示“所有列”,FROM表示将要从哪个表查询,本例中是students表。
例子的显示结果即为“表1”。SELECT查询的结果是一个二维表。
下面的语句可以显示“表2” ,原理同上:
SELECT * FROM classes;
★ SELECT语句其实并不要求一定要有FROM子句,例如:
SELECT 100+400;
####
输出结果:
100+400
500
不带FROM子句的SELECT语句有一个有用的用途——用来判断当前到数据库的连接是否有效。许多检测工具会执行一条 SELECT 1; 来测试数据库连接。
SELECT语句可以通过WHERE条件来设定查询条件,查询结果是满足查询条件的记录。
例如,要指定条件“分数在80分或以上的学生”,写成WHERE条件就是:
SELECT * FROM students WHERE score >= 80;
####
语句解释——————
WHERE关键字后面的score >= 80就是条件。
score是列名,该列存储了学生的成绩,
因此 score >= 80就筛选出了指定条件的记录
即:标准格式为:
SELECT * FROM 表名 WHERE 条件表达式 ;
★ 注意:条件表达式可以用<条件1> AND <条件2>表达同时满足条件1并且满足条件2 !! 例如:
SELECT * FROM students WHERE score >= 80 AND gender = 'M';
####
同时满足:①成绩大于等于80 ②男性 两个条件。
★ 除了“AND”以外,还可以“OR” ——
SELECT * FROM students WHERE score >= 80 OR gender = 'M';
####
多个条件满足一个即可。
★ 还可以 NOT <条件> ,表示“不符合该条件”的记录——
SELECT * FROM students WHERE NOT class_id = 2;
####
查询:不是2班的学生
————————————————————————————————————————————————————————————
补充————
上述NOT条件
NOT class_id = 2
其实等价于
class_id <> 2
★ 要组合三个或者更多的条件,就需要用小括号()表示如何进行条件运算。例如,编写一个复杂的条件:分数在80以下或者90以上,并且是男生:
SELECT * FROM students WHERE (score < 80 OR score > 90) AND gender = 'M';
加上小括号防止优先级出错!!
补充:
写条件时,如果是相等的条件,写一个等号即可!!
使用SELECT * FROM <表名> WHERE <条件>可以选出表中的若干条记录。我们注意到返回的二维表结构和原表是相同的,即结果集的所有列与原表的所有列都一一对应。
而 如果只需要返回特定的列:
可以用SELECT 列1, 列2, 列3 FROM …,让结果集仅包含指定列。这种操作称为投影查询。
SELECT id, score, name FROM students;
####
代码解释: 从students表中返回id、score和name这三列。
结果集的列的顺序和原表可以不一样!
★ 使用SELECT 列1, 列2, 列3 FROM …时,还可以给每一列起个别名,这样,结果集的列名就可以与原表的列名不同。它的语法是SELECT 列1 别名1, 列2 别名2, 列3 别名3 FROM …。 例如:
SELECT id, score points, name FROM students;
####
该SELECT语句将列名score重命名为points,而id和name列名保持不变。
在使用SELECT查询时,查询结果集通常是按照id排序的,也就是根据主键排序。如果要根据其它条件来排序,可以加上ORDER BY子句:
SELECT id, name, gender, score FROM students ORDER BY score;
####
按照成绩从低到高进行排序。(默认就是升序排列。)
如果要反过来,按照成绩从高到底排序,我们可以加上DESC表示“倒序”:
SELECT id, name, gender, score FROM students ORDER BY score DESC;
####
按score从高到低。
★ 如果score列有相同的数据,要进一步排序,可以继续添加列名。例如:
SELECT id, name, gender, score FROM students ORDER BY score DESC, gender;
####
先按score列倒序,如果有相同分数的,再按gender列排序。
★ 如果有WHERE子句,那么ORDER BY子句要放到WHERE子句后面。例如,查询一班的学生成绩,并按照倒序排序:
SELECT id, name, gender, score
FROM students
WHERE class_id = 1
ORDER BY score DESC;
上例中,结果集仅包含符合WHERE条件的记录,并按照ORDER BY的设定排序。
思想:使用SELECT查询时,如果结果集数据量很大,比如几万行数据,放在一个页面显示的话数据量太大,不如分页显示,每次显示100条。要实现分页功能,实际上就是从结果集中显示第1-100条记录作为第1页,显示第101-200条记录作为第2页,以此类推。因此,分页实际上就是从结果集中“截取”出第M~N条记录。
实现子句:
LIMIT <M> OFFSET <N>
LIMIT 3 OFFSET 0
####
对结果集从0号记录开始,最多取3条!
★ LIMIT 15 OFFSET 30 解释:
offset是偏移的意思 默认是0 偏移30的长度 截取长度是15 所以拿到的是第31-46 左闭右开
注意SQL记录集的索引从0开始。
★ 总结:分页查询需要先确定每页的数量和当前页数,然后确定LIMIT和OFFSET的值。即——
首先要确定每页需要显示的结果数量pageSize(这里是3行),然后根据当前页的索引pageIndex(即当前页的页码,从1开始),确定LIMIT和OFFSET应该设定的值:
注意:OFFSET超过了查询的最大数量并不会报错,而是得到一个空的结果集。
其它:
① 在MySQL中,LIMIT 15 OFFSET 30还可以简写成LIMIT 30, 15。
② 使用LIMIT M OFFSET N分页时,随着N越来越大,查询效率也会越来越低。
对于统计总数、平均数这类计算,SQL提供了专门的聚合函数,使用聚合函数进行查询,就是聚合查询。
使用SQL内置的COUNT()函数查询students表一共有多少条记录:
SELECT COUNT(*) FROM students;
注意——COUNT(*)和COUNT(id)实际上是一样的效果。
Output :
COUNT(*)
10
注:聚合的计算结果虽然是一个数字,但查询的结果仍然是一个二维表,只是这个二维表只有一行一列,并且列名是COUNT(*)。
★ 通常,使用聚合查询时,我们应该给列名设置一个别名,便于处理结果:
SELECT COUNT(*) num FROM students;
# 起名:设置列名为num
Output :
num
10
★ 聚合查询同样可以使用WHERE条件,因此我们可以方便地统计出有多少男生、多少女生、多少80分以上的学生等。例如:
查询男生总人数:
SELECT COUNT(*) boys FROM students WHERE gender = 'M';
Output :
boys
5
★ 聚合函数汇总
其中,MAX、MIN也可计算字符类型并返回。
例:统计男生的平均成绩
SELECT AVG(score) boys_average FROM students WHERE gender = 'M';
Output :
boys_average
81.4
补充: 如果聚合查询的WHERE条件没有匹配到任何行,COUNT()会返回0,而SUM()、AVG()、MAX()和MIN()会返回NULL。
如果我们要统计一班的学生数量,我们知道,可以用SELECT COUNT(*) num FROM students WHERE class_id = 1;。如果要继续统计二班、三班的学生数量,难道必须不断修改WHERE条件来执行SELECT语句吗?(愚)
对于聚合查询,SQL还提供了“分组聚合”的功能。例如下面的聚合查询:
SELECT COUNT(*) num FROM students GROUP BY class_id;
Output :
num
4
3
3
执行这个查询,COUNT()的结果不再是一个,而是3个,这是因为,GROUP BY子句指定了按class_id分组,因此,执行该SELECT语句时,会把class_id相同的列先分组,再分别计算,因此,得到了3行结果。
但是这3行结果分别是哪三个班级的,不好看出来,所以我们可以把class_id列也放入结果集中:
SELECT class_id, COUNT(*) num FROM students GROUP BY class_id;
Output :
class_id | num |
---|---|
1 | 4 |
2 | 3 |
3 | 3 |
警告: 聚合查询的列中,只能放入分组的列!
也可以使用多个列进行分组。
例题:统计各班的男生和女生人数——
SELECT class_id, gender, COUNT(*) num FROM students GROUP BY class_id, gender;
Output :
class_id | gender | num |
---|---|---|
1 | M | 2 |
1 | F | 2 |
2 | F | 1 |
2 | M | 2 |
3 | F | 2 |
3 | M | 1 |
SELECT查询不但可以从一张表查询数据,还可以从多张表同时查询数据。例如:同时从students表和classes表的“乘积”,即查询数据——
SELECT * FROM students, classes;
查询的结果也是一个二维表,它是students表和classes表的“乘积”,即students表的每一行与classes表的每一行都两两拼在一起返回。结果集的列数是students表和classes表的列数之和,行数是students表和classes表的行数之积。
★ 设置列的别名——防止不同表的列重名冲突
SELECT
students.id sid,
students.name,
students.gender,
students.score,
classes.id cid,
classes.name cname
FROM students, classes;
输出的列名依次为——
sid name gender score cid cname
上片代码可简写为:
SELECT
s.id sid,
s.name,
s.gender,
s.score,
c.id cid,
c.name cname
FROM students s, classes c;
★ 多表查询也是可以添加WHERE条件的!
例子——
SELECT
s.id sid,
s.name,
s.gender,
s.score,
c.id cid,
c.name cname
FROM students s, classes c
WHERE s.gender = 'M' AND c.id = 1;
连接查询是另一种类型的多表查询。连接查询对多个表进行JOIN运算,简单地说,就是先确定一个主表作为结果集,然后,把其他表的行有选择性地“连接”在主表结果集上。
例题 :
假设我们希望结果集同时包含所在班级的名称,students表只有class_id列,缺少对应班级的name列。name列在另一个表中,怎么调用呢?
思路——存放班级名称的name列存储在classes表中,只有根据students表的class_id,找到classes表对应的行,再取出name列,就可以获得班级名称。
实现—— //内连接(INNER JOIN)实现
SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score
FROM students s
INNER JOIN classes c
ON s.class_id = c.id;
注意 INNER JOIN查询 的写法是:
上面例题的外连接实现(OUTER JOIN) ——
SELECT s.id, s.name, s.class_id, c.name class_name, s.gender, s.score
FROM students s
RIGHT OUTER JOIN classes c
ON s.class_id = c.id;
★ 内连接 vs 外连接 ★
先说明:OUTER JOIN 包含 RIGHT OUTER JOIN 、LEFT OUTER JOIN 、FULL OUTER JOIN。
INNER JOIN只返回同时存在于两张表的行数据,由于students表的class_id包含1,2,3,classes表的id包含1,2,3,4,所以,INNER JOIN根据条件s.class_id = c.id返回的结果集仅包含1,2,3。
RIGHT OUTER JOIN返回右表都存在的行。如果某一行仅在右表存在,那么结果集就会以NULL填充剩下的字段。
LEFT OUTER JOIN则返回左表都存在的行。
FULL OUTER JOIN,它会把两张表的所有记录全部选择出来,并且,自动把对方不存在的列填充为NULL。
假设查询语句是:
SELECT ... FROM tableA ??? JOIN tableB ON tableA.column1=tableB.column2;
注:下述例子中,把tableA看作左表,把tableB看成右表
熟知4种JOIN查询的覆盖范围,就能知道什么时候该使用哪种了!
总结:
①JOIN查询需要先确定主表,然后把另一个表的数据“附加”到结果集上。
②INNER JOIN是最常用的一种JOIN查询。
③JOIN查询仍然可以使用WHERE条件和ORDER BY排序。
—————————————————————————————————
SQL完整笔记_上篇(完)
下篇请点击跳转:SQL完整笔记(下)