CRUD:
注释:在SQL中可以使用“–空格+描述”来表示注释说明
CRUD 即增加(Create)、查询(Retrieve)、更新(Update)、删除(Delete)四个单词的首字母缩写
语法:
INSERT [INTO] table_name
[(column [, column] ...)]
VALUES (value_list) [, (value_list)] ...
value_list: value, [, value] ...
示例:
-- 创建一张学生表
CREATE TABLE student (
id INT,
sn INT comment '学号',
name VARCHAR(20) comment '姓名',
qq_mail VARCHAR(20) comment 'QQ邮箱'
);
-- 插入两条数据,value_list 数量必须和定义表的数量及顺序一致
INSERT INTO student VALUES (100, 10000, '唐三藏', NULL);
INSERT INTO student VALUES (101, 10001, '孙悟空', '11111');
-- 插入两条记录,value_list 数量必须和指定列数量及顺序一致
INSERT INTO student (id, sn, name) VALUES
(102, 20001, '曹孟德'),
(103, 20002, '孙仲谋');
当显示OK,后面的sec单位为秒,插入操作相对其它是比较慢的.
因此:关系型数据库由于对于数据要进行大量的校验,所以牺牲了性能,换来的是数据的完整性和可靠性.
正因为关系型数据库性能比较低,在一个高并发的系统中,数据库很容易就成为性能瓶颈,数据库也是在极端情况下最容易挂的服务.
注:所有的select操作都不会对原来的表造成任何改变,基于原来的表,生成结果表。大部分的select操作的查找结果是无法和原来的表记录一 一对应的。
语法:
SELECT
[DISTINCT] {* | {column [, column] ...}
[FROM table_name]
[WHERE ...]
[ORDER BY column [ASC | DESC], ...]
LIMIT ...
使用select * from [表名]
,"*"代表的是通配符,它的意思是查找所有列.
原来的test1数据:
指定列插入:
再次查询test1中的数据:
使用select [列名] from 表名;
原来表中的数据:
只查找id和name的列:
查询字段为表达式,针对查到的列进行一定的表达式计算.
语法:select [列名+列名] from test1;
a)例如:查找所有同学的名字和总成绩:
注意:MySQL中null加任何数字结果都为null
b)查找所有同学的语文成绩,并在其基础上加10分:
c) 查询字段使用别名:为查询结果中的列指定别名,表示返回的结果集中,以别名作为该列的名称,语法:SELECT column [AS] alias_name [...] FROM table_name;
-- 结果集中,表头的列名=别名
SELECT id, name, chinese + math + english 总分 FROM exam_result;
首先,孙悟空和白骨精的chinese是相同的,此时进行去重操作:
b)也可以指定多列去重,即如果两列互相之间有相同的,则能够去重。
示例:
注意:
使用distinct的时候,必须把对应的列都放到distinct之后。
去重查找得到的结果表的行数和原来的可能不一样
语法:
-- ASC 为升序(从小到大)
-- DESC 为降序(从大到小)
-- 默认为 ASC
SELECT ... FROM table_name [WHERE ...]
ORDER BY column [ASC|DESC], [...];
a) 对某一列进行升序排序
示例如下:
b)对某一列进行降序排序
c)对某几列进行计算的基础上进行排序
d)对某列进行计算后排序并使用别名
赋予别名后可以直接order by 别名 来排序。
d)按照多个列进行排序。
先把所有同学的信息按照语文降序排序,再按照数学降序排序,再按照英语降序排序。例如当前两个同学的语文、数学成绩相同,最后排序的位置依赖于英语成绩的排序高低决定。
列越靠前,优先级越高。
注意:Null是所有值中最小的,如果按升序排序则它在最上方,如果按降序排序则它在最下方。
比较运算符:
运算符 | 说明 |
---|---|
>, >=, <, <= | 大于,大于等于,小于,小于等于 |
= | 等于,NULL 不安全,例如 NULL = NULL 的结果是 NULL |
<=> | 等于,NULL 安全,例如 NULL <=> NULL 的结果是 TRUE(1) |
!=,<> | 不等于 |
BETWEEN a0 ANDa1 | 范围匹配,[a0, a1],如果 a0 <= value <= a1,返回 TRUE(1) |
IN(option,…) | 如果是option 中的任意一个,返回 TRUE(1) |
IS NULL | 是 NULL |
IS NOT NULL | 不是 NULL |
LIKE | 模糊匹配。% 表示任意多个(包括 0 个)任意字符;_ 表示任意一个字符 |
逻辑运算符:
运算符 | 说明 |
---|---|
AND | 多个条件必须都为 TRUE(1),结果才是 TRUE(1) |
OR | 任意一个条件为 TRUE(1), 结果为 TRUE(1) |
NOT | 条件为 TRUE(1),结果为 FALSE(0) |
a) 查找数据中chinese为null的记录
原先的表:
要用<=>来识别NULL。
另一种方式:
b)查找英语成绩不及格的同学的信息(<60)
c)查找语文成绩比英语成绩好的同学信息
d)查找总分在200分以下的同学
注:在where后不能使用别名来操作。
e)查找语文成绩和英语成绩都小于80的同学
注:如果用到两个或两个以上的逻辑符号,可以加上括号来表明优先级,否则若and和or同时出现则and的优先级更高。
f)查询语文成绩在70到90之间的同学信息
between and 两边的数字一定是前面的比后面的数字大,并且两边都是闭区间。
或者:
g)找出数学成绩是78.5或87.5的同学的信息
当然也可以借助逻辑符号or来完成。
h) 模糊匹配
%是用来匹配任意个的任意字符
_ 是用来匹配一个任意字符
示例:
查找所有姓孙的同学的成绩:
注意:当使用下划线时,要使用两个下划线
模糊查找也可以用于数字。
如:
查找所有同学中语文成绩以8开头的信息
注意:模糊匹配的查询效率比较低,一般不太建议使用like。
i) 进行复杂条件查询的时候,存在一个“最左原则”。
查找所有同学中 姓孙 并且 语文成绩>60 的同学。
此处建议第二种写法,因为第二种写法在原表过滤掉不姓孙的同学时只剩下两个同学,此时再去过滤语文小于60的同学只是在剩余两个同学当中找。
而第一种写法在原表过滤掉语文成绩小于60的同学则还剩下4个同学(如下图),此时再去过滤掉不姓孙的同学要在这4个同学当中去找。
两种写法的第一次遍历都要遍历全表,因此第二种的写法效率更高。
总结:多个条件在一起联合生效时,一般要求哪个条件能过滤掉的数据多(剩下的数据少)就该把该条件放在最左侧。
上面的select操作,除了条件查找外,其余的都不应该出现在生产服务器上直接执行。最保险的就是分页查找,相当于把查找结果只选取一小部分来作为结果。关键字limit。
示例:
a)查找所有同学中总分成绩最高的前三名。
b) 查找同学信息中总分最高的4到6名
关键字:limit和offset
offset后面的数字相当于下标,从该下标往后数limit后面的数字个(包括该下标本身算一个)。需要特别注意。
c)如果limit后面的数字过大,超过的记录的数目,返回结果不会有任何错误。
如:
原表中的数据:
limit后的数字过大对结果没影响:
d) offset后面的数字过大,得到的结果可能是一个空的结果
语法:
UPDATE table_name SET column = expr [, column = expr ...]
[WHERE ...] [ORDER BY ...] [LIMIT ...]
具体:
update [表名] set [列名]=[修改的值],[列名]=[修改的值] where 子句;
a) 把孙悟空的数学成绩改为90分
原来的表:
改完的表:
注意:如果不加where,则一整列都会被修改,一般搭配where使用。
update每次修改几行记录是不确定的,具体取决于where中的条件怎么写,得看where条件过滤后还剩下多少条记录。
b) 把所有同学的语文成绩都-10分。
原来的表:
进行修改:
改后的表:
d) 将总成绩倒数3名的同学的数学成绩加10分。
原来的表:
修改:
注:Rows matched后面的数字指的是where子句匹配后还剩多少行,Changed后面的数字指的是实际修改的行数。一般来说两个值会相等,但是如果对应内容为null可能会出现差异。
就好比上面的来说,有一个同学的成绩为null,每一行相加但null是不会操作的。
修改后:
注:修改数据时,数据不应该超过指定列的数据类型的范围。
语法:
DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ..
具体:
delete from [表名] where [筛选条件]
示例:原来的表:
此时要删除同学中姓孙的全部信息:
删除后:
注:删除操作是很危险的,一旦数据被删了,通过常规手段是无法恢复的。
目的是为了防止表中的数据最终不为null。
先创建一个表,我们此处设置id不能为空。
再去查看表的属性,可以看到id的Null列是NO。
当插入的数据为空时,有:
先创建一个表,指定id列中的所有行不能重复
再去看表的属性,可以发现id中的Key为UNI,是unique的缩写。
若插入的id有重复的,则有:
如果要一个列要同时满足不为空并且该列的所有行不重复,则可以这样创建表:
观察表的属性:
这样创建表的效果下面介绍的主键的效果。
创建一个表
插入一个id为空的值报的错误信息:
插入id相同的值报的错误信息:
创建一个表,让名字都给到默认值unknown。
查看表的属性:
id 的默认值为null,name的默认值为unknown。
此时插入一个元素,有:
auto_increment能够实现自增。
创建一个表:
在id设置主键约束后加上auto_increment,此时查看表的属性:
此时插入id为3的孙权和id为null的刘备,但是打印表后刘备的id紧加着孙权的id之后:
自增的特点:
1、如果表中没有任何记录,自增从1开始。
2、如果表中已经有记录了,自增从上一条记录往下自增。
但是有个特殊例外:如果把中间的某个数据删了之后,再插入的时候会根据前面删掉的自增主键的值不会重复被利用。
先将id为4的刘备删掉:
再插入id为null的刘备:此时刘备的id变为5.
但有同学会问:主键约束不是不能插入null吗,为什么insert的时候可以为null?
实际上在主键的auto_increment中插入null不是最终结果,而是让数据库自动生成新的自增值。
外键的使用场景:
创建了一个class表:
创建了一个student表:
class表中插入的班级数据:
student表中插入学生的数据:
此时,student表中的classId字段的值应该与class表中的id值匹配才是逻辑正确的,虽然以上方法的插入也对,但是不科学。为了让此处的数据校验更严格,可以使用外键。
重新创建student表:
外键的设置习惯放在最后。
在设置外键时需要指定三方面信息:
1.指定当前表中的哪一列关联。
2.指定和哪张表关联。
3.指定和目标表中的哪一列关联。
因此后续往student表中插入数据的时候,MySQL就会自动检查当前的classId字段的值是否在class表的id列中出现过。如果没有,则插入失败。
例子:
先向class中插入3个元素如下:
此时向student表中插入:
如果插入的学生的classId超过了class中的id,则会报错:
MUL则代表的是外键的意思:
注:
1.使用外键,会对插入操作的效率产生一定的影响。
2.外键约束会影响表的删除
如:class表被其它表关联着,此时是无法直接删除class表的。
如果真的把class删了,此时再对student的classId列进行任何操作都是无意义的。因为student表的插入依赖于class表。
MySQL使用时不报错,但忽略该约束:
drop table if exists test_user;
create table test_user (
id int,
name varchar(20),
sex varchar(1),
check (sex ='男' or sex='女')
);
比如说一个人对应一个身份证号,就是一个一对一的关系。
组织形式是:
class表(id,name)
students表(id,name,classId)
MySQL中:student表中可能会出现很多记录,但这很多条记录中,classId可能是相同的,这些classId相同的记录就表示是存在于同一个班级。
但是用代码的组织是:class表(id,name,student),student列当成是一个数组,数组中每个元素表示一个学生的信息。但是MySQL中没有数组类型,这是平时写代码中的常见的表示方式。
例如:
当前有很多学生:甲乙丙丁。当前又有很多门课程:语文,数学,英语,物理,化学。任意一个学生,都可能会选择多门课程。任意一门课程,都可能会被多个学生所选择。
图示:
因此多对多的关系,可以引入中间表来解决问题。例如:描述每个同学的每个科目的信息。
首先创建一个student表和course表。
创建一个成绩表,里面包含学生的id,课程的id和成绩。意思是丁在语文这个科目上考了90分。当然成绩表也可以将学生id和课程id设置为外键更保守与严谨。
由于是多对多的关系,会看到courseId存在很多重复的(很多同学都可能修了这门课程),很多studentId也存在重复(一个同学可能修了多门课程)
因此可以这样创建:
语法:
INSERT INTO table_name [(column [, column ...])] SELECT ...
示例:
先创建两个表,一个表先填入数据,另一个表为空:
之后将user中的name和decription属性用select关键字导入user2表中:
此时user2表中的name和decription和user中的一模一样:
注:select后面的子句为子查询,子查询得到列的数目、顺序、类型都得和被插入的表的列的数目、顺序、类型一致。列的名字一致不一致都无所谓。
如:
a) 将user的全部属性导入user2中是不可行的:
因为user中有三列,user2中只有两列。
b) 将user中的某两个属性不按照顺序来导入user2中,虽然导入成功,但是毫无意义。
聚合查询:把查询结果中的若干数据合并在一起。
常见的统计总数、计算平局值等操作,可以使用聚合函数来实现,常见的聚合函数有:
函数 | 说明 |
---|---|
COUNT([DISTINCT] expr) | 返回查询到的数据的 数量 |
SUM([DISTINCT] expr) | 返回查询到的数据的 总和,不是数字没有意义 |
AVG([DISTINCT] expr) | 返回查询到的数据的 平均值,不是数字没有意义 |
MAX([DISTINCT] expr) | 返回查询到的数据的 最大值,不是数字没有意义 |
MIN([DISTINCT] expr) | 返回查询到的数据的 最小值,不是数字没有意义 |
示例:
下面这张图记录的是select * from user的行数。这样查询的开销更大。
下面这张图记录的是 select * from id 的行数。
两个只是碰巧相等。
注:
1、 count是一个函数,在敲代码的时候count后面要紧挨着括号,如果与括号中间多了空格,那否则就是一个列。
2、count本来是一个函数,如果count与括号之间多了空格,那么count会被当成一个列名。
SELECT 中使用 GROUP BY 子句可以对指定列进行分组查询。需要满足:使用 GROUP BY 进行分组查询时,SELECT 指定的字段必须是“分组依据字段”,其他字段若想出现在SELECT 中则必须包含在聚合函数中。
示例:
下面已经创建了一个emp表:
若要求出不同role的平均salary,则:角色相同的会被分到同一个组
有了group by后,就把role相同的记录放到同一组中,avg就是针对每个组分别来求平均值。
也可以多个函数一起使用:
group by中也可以结合一些条件对数据进行进一步的筛选。不是使用where,而是having。
GROUP BY 子句进行分组以后,需要对分组结果再进行条件过滤时,不能使用 WHERE 语句,而需要用HAVING
where和having的意义是不一样的:
where是针对原始表中的每条记录都进行筛选。
having是针对group by之后的结果进行筛选。
例如此时有两个表:
笛卡尔积:无非就是把不同表中的每列中的每个元素结合起来。下面这行命令的最后可以加上where子句,如果没有where则得到的结果就是两个表的笛卡尔积。
笛卡尔积处理的结果:
第一次的id由1到8是拿表2中的第一条记录和表1中的每一条记录进行匹配。第二次的id由1到8是拿表2中的第二条记录和表1中的每一条记录进行匹配。
此时我们发现有一些数据是没有意义的,如studentId对应的是学生的id,如果不对应则数据是没有意义的。比如第一行是有意义的:是黑旋风李逵的哪个成绩是70.5分。
因此,为了处理两个表中笛卡尔积结果后有些没有意义的数据,在原来的基础上添加where子句过滤掉没有意义的数据是很有必要的。
当进行笛卡尔积之后再按照id筛选,这样的筛选结果一定是同时在两张表中都出现过的记录。这样的操作称为内连接。
语法:
select 字段 from 表1 别名1 [inner] join 表2 别名2 on 连接条件 and 其他条件;
select 字段 from 表1 别名1,表2 别名2 where 连接条件 and 其他条件;
a) 查找名为许仙的所有成绩
如果再按照处理过的笛卡尔积的结果查找同学名字为许仙的成绩,则可以:
这种方法是比较推荐的,很好理解。
还有例外一种方法:
因此面对表的联合查询的设计思路:
1.先把两张表联合在一起,得到笛卡尔积。
2.按照student id 对笛卡尔积的记录进行筛选,保留有意义的数据。
3.再针对名字进行筛选。
b) 查找所有同学的总成绩,以及该同学的基本信息
先直接进行联合查找,此时没有任何条件,得到的就是笛卡尔积。
再根据student中的id与score中的id对应,去除无意义的数据:
运行sum函数求出每个学生对应的总成绩,要用group by子句。
如果按照学生的名字来分组,id可能会不是按照顺序来排,因为group by 的过程类似于hash的过程。如果没有指定order by语句,得到的结果的顺序都是不确定的。
c) 查找所有同学的每一科成绩,和同学的相关信息。最终的效果需要显示:同学姓名,科目名称,对应的成绩
第一步:先针对三表进行联合,得到一个非常大的笛卡尔积,这个笛卡尔积中大部分数据都是无意义的。
第二步:按照student.id和score.course_id 针对笛卡尔积的数据进行筛选。
效果:
但是我们发现,course.id与score.course_id对不上,因此有部分数据还是没有意义的。因此要添加course.id=score.course_id 的条件。
第三步:此时已经是最简的笛卡尔积结果,按照要求要保留 同学姓名,科目名称,对应的成绩 ,因此其它不变,只需要改显示的列。
仔细观察发现:student表中本来有8个同学,最终查询的所有同学的成绩的时候发现,只有7个同学有成绩,“老外学中文”没有成绩。我们发现“老外学中文的学号是8”,而score表中,并没有学号为8的同学的成绩。
外连接分为左外连接和右外连接。如果联合查询,左侧的表完全显示我们就说是左外连接;右侧的表完全显示我们就说是右外连接。
了解内连接、外连接等更多连接的博客
语法:
-- 左外连接,表1完全显示
select 字段名 from 表名1 left join 表名2 on 连接条件;
-- 右外连接,表2完全显示
select 字段 from 表名1 right join 表名2 on 连接条件;
自连接是指在同一张表连接自身进行查询。
案例:
显示所有“计算机原理”成绩比“Java”成绩高的成绩信息。
步骤1:先找到java和计算机原理课程 id
步骤二:按照课程id在分数表中筛选数据
a) 针对score表自身进行笛卡尔积
有很多个数据并且是不符合我们题目要求的。
b) 加上学生id的限制。像第二行的数据就是符合要求的。
c) 加上课程id的限制
d) 按照分数大小进行比较。
a) 查询和“不想毕业”同班的同学有哪些
原表:
找到与“不想毕业”的同学名字:
a) 借助 in 的方式来进行子查询:先执行子查询(后面的括号中的查询称为子查询),把子查询的结果保存到内存当中,再进行主查询,再结合刚才子查询的结果来筛选最终结果。并且子查询只执行一次
b) 借助exists来完成子查询:先执行主查询,再触发子查询。子查询针对主表中的查询记录,都会再一次触发一次子查询,会执行很多次。相当于用时间换空间。
结论:
如果子表查询的结果集合比较小,就推荐使用in。
如果子表查询的结果集合比较大,而主表的集合小,就使用exists。
在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。使用UNION和UNION ALL时,前后查询的结果集中,字段需要一致。
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
案例:查询id小于3,或者名字为“英文”的课程:
select * from course where id<3
union
select * from course where name='Java';
-- 或者使用or来实现
select * from course where id<3 or name='Java';
select * from course where id<3
union all
select * from course where name='Java';