目录
准备工作
一、数据的插入 (insert)
注意
1. 整行插入
2. 指定列的插入(常用)
3. 一次插入多行数据
4. 清空数据库的数据(truncate)
5. 拓展练习:对于数据库中的数据进行统计(count)
二、数据的查询(retrieve/query/select)
1. 普通数值
2. 全列查询(select * from 表名)
3. 指定列查询(select 列名 from 表名)
4. 查询字段为表达式(select 列名表达式 from 表名)
5. 别名(as)
6. 去重(distinct)
7. 排序(order by)
7.1 多字段排序
8. 行筛选(where)
8.1 字段/字面量 between ... and ...
8.2 集合判断 in(...)
8.3 模糊匹配 like
8.4 分页查询 Limit offest
三、数据的更新(update)
四、数据的删除(delete)
五、OJ 练习
六、函数
6.1 截取字符串
6.2 将字符转为大写
6.3 连接字符串
6.4 DATEDIFF 计算两个日期相隔多少天
6.5 获取时间字符串的年月日 时分秒
准备工作
在开始学习之前确认相关的隧道以及数据库创建成功,具体通过以下步骤:
首先在 WindTerm 中确认隧道已经打开:
接下来在 WorkBench 中创建数据库连接隧道:
点击右下角 “Test Connection” 出现如下图所示界面,表示连接成功。
接下来,点击下图进入我们创建的数据库中:
可以看到我们默认进入的数据库会加粗显示:
点开后可以看到在上篇文章中我们在 WindTerm 中创建的数据库的内容已经同步至此:
接下来我们将上次的练习导入:
此时我们可以看到表格中已经存在了我们创建好的框架,但是还没有具体的数据:
一切准备就绪后,我们开始正式进入主题——MySQL 的增删查改。
一、数据的插入 (insert)
首先,我们需要向数据库中插入数据。在插入数据时需要注意以下几点:
一、数据的插入必须以行为单位,即插入的数值要填充一行中的所有列:
要么对应的列有默认值(自增 或者 default 或者 允许为 null);
要么对应的列指定了对应的值。
二、插入的时候,要求插入的值符合对应列的数据类型,比如类的数据类型是 int,则值是 'hello' 时就会插入失败。
三、插入的时候,如果对应列不允许重复(主键 or Unique)则插入重复的值会失败。
接下来,我们来验证以上三点。
第一点:插入的数值要填充一行中的所有列。
首先,我们先来简单的插入一组数据:
insert into oj_records (source) values ('牛客');
可以看到出现报错信息:
Error Code: 1364. Field 'link' doesn't have a default value
为什么会出现以上的报错信息呢?我们先来看一下我们所创建的数据库:
我们在创建时,明确说明了 source、link、question 的值均不能为空(NOT NULL),但是在插入数据时只给了 source 的值,导致出现以上报错信息。因此,我们必须赋值足够的值才能够正确的插入数据,如下图所示:
扩展:
decimal(2,3) 精确小数 表示数值最多有三位,其中小数点后有两位。如果插入的数值不符合要求同样插入失败。
第二点:插入的值符合对应列的数据类型。
在创建数据库时,我们设置了 passed 的数据类型为 INT 类型:
为了验证以上的第二点,我们在插入数据时插入 passed 的值为字符串类型:
insert into oj_records (source, link, question, passed)
value ('力扣', 'bbb', '冒泡排序', 'ccc');
可以看到插入失败,出现以下报错:
报错信息为:
Error Code: 1366. Incorrect integer value: 'ccc' for column 'passed' at row 1
说明 passed 列应为整数值,但是我们插入了字符串类型的数值。因此,修改为以下数据即可插入成功:
第三点:插入的时候,如果对应列不允许重复(主键 or Unique)则插入重复的值会失败。
我们直接插入同样的数据:
可以看到报错信息为:
Error Code: 1062. Duplicate entry 'eee' for key 'oj_records.link_UNIQUE'
即在创建数据库时我们选择的主键非常重要,它直接决定了对于插入的数据的要求,决定了数据能否成功插入,对于插入的数据要求十分严格。
注意
1. 整行插入
insert into 表名 values (值列表);
insert into 表名 values (值列表), (值列表), ...;
-- 一列不能少,并且值的顺序和建表时的顺序一致
-- null:要么 null or 默认值
insert into oj_records values (null, 'source', 'link', 'question', '简单', 0, null);
2. 指定列的插入(常用)
insert into 表名 (字段列表) values (值列表);
插入指定列,可以和创建数据库时的顺序不一致,但是赋值时对应的列要保持一致。
insert into oj_records (link, source, question)
values('link1', 'question1', 'source');
3. 一次插入多行数据
insert into 表名 (字段列表) values (值列表), (值列表), ...;
一次插入多行数据时,注意不要重复插入相同数据即可。
insert into oj_records (link, question, source)
values
('link2', 'question2', 'source'),
('link3', 'question3', 'source'),
('link4', 'question4', 'source');
4. 清空数据库的数据(truncate)
通过以下命令,我们可以直接清空数据库中的数据(截断表,可以视为 数据库阶段的 clear() 操作)。
truncate oj_records;
5. 拓展练习:对于数据库中的数据进行统计(count)
首先,我们向数据库中添加一组数据:
insert into oj_records
(source, link, question, difficulty, passed, last_passed_at)
values
('牛客', 'l1', 'q1', '简单', 1, '2023-04-23 09:18:27'),
('leetcode', 'l2', 'q2', '简单', 1, '2023-04-22 09:18:27'),
('牛客', 'l3', 'q3', '中等', 1, '2023-04-20 23:55:27'),
('牛客', 'l4', 'q4', '困难', 1, '2023-03-30 19:27:22'),
('牛客', 'l5', 'q5', '中等', 0, null),
('牛客', 'l6', 'q6', '困难', 0, null),
('牛客', 'l7', 'q7', '简单', 0, null),
('leetcode', 'l8', 'q8', '中等', 1, '2023-03-21 13:13:13');
可以通过 count 命令,统计数据。比如,统计题目总数:
select count(*) from oj_records;
还可以通过 where 命令对题目进行筛选,比如统计通过的题目总数:
select count(*) from oj_records where passed = 1;
还可以通过 and 语句增加判断条件,通过 between... and ... 限制判断的区间,比如统计三月份通过的题目总数:
select count(*) from oj_records
where passed = 1 and last_passed_at
between '2023-03-01 00:00:00' and '2023-03-31 23:59:59';
二、数据的查询(retrieve/query/select)
select
指定列(或者表达式)
from 表名
where 进行行筛选
order by 排序依据
limit ... offset ... 取指定行
最简形式下,可以只使用 select :
select ...;
1. 普通数值
比如,我们先来使用如下:
select 1;
select 100;
接下来我们创建两张表,并插入数据。首先是学生表:
DROP TABLE IF EXISTS students;
CREATE TABLE students (
id INT,
sn INT comment '学号',
name VARCHAR(20) comment '姓名',
qq_mail VARCHAR(20) comment 'QQ邮箱'
);
INSERT INTO students VALUES (100, 10000, '唐三藏', NULL);
INSERT INTO students VALUES (101, 10001, '孙悟空', '11111');
INSERT INTO students (id, sn, name) VALUES
(102, 20001, '曹孟德'),
(103, 20002, '孙仲谋');
接下来创建一张学生的成绩表:
DROP TABLE IF EXISTS exam_result;
CREATE TABLE exam_result (
id INT,
name VARCHAR(20),
chinese DECIMAL(3,1),
math DECIMAL(3,1),
english DECIMAL(3,1)
);
INSERT INTO exam_result (id,name, chinese, math, english) VALUES
(1,'唐三藏', 67, 98, 56),
(2,'孙悟空', 87.5, 78, 77),
(3,'猪悟能', 88, 98, 90),
(4,'曹孟德', 82, 84, 67),
(5,'刘玄德', 55.5, 85, 45),
(6,'孙权', 70, 73, 78.5),
(7,'宋公明', 75, 65, 30);
2. 全列查询(select * from 表名)
select * from exam_result;
* 代表通配符,表示展示所有的字段,但是在正式生产中不建议使用,因为 * 的指向不明确,可读性较差。
3. 指定列查询(select 列名 from 表名)
查询指定列顺序,不需要按定义表的顺序来。
select id, name, english from exam_result;
4. 查询字段为表达式(select 列名表达式 from 表名)
select 10 + 3;
select 1 from exam_result;
select id * 100 from exam_result;
select id <= 5 from exam_result;
可以看到以上代码执行完毕后,并没有出现我们认为的显示 1~5 行,而是将 id 中 >= 5 的用 1 表示,否则用 0 表示。
同样,我们可以使用逻辑运算符进行判断:
与 | AND |
或 | OR |
非 | NOT |
同或 | NOR |
异或 | XOR |
select not (id <= 5) from exam_result;
select id <= 10, id > 3, id = 5, id <> 1 from exam_result;
-- <> 代表不等于
可以看到,当条件判断为四个表达式且用逗号隔开时,每一个表达式对应一列。
5. 别名(as)
为查询结果中的列指定别名,表示返回的结果集中,以别名作为该列的名称。
select
id <= 10 as a,
id > 3 as b,
id = 5 as c,
id <> 1 as d
from exam_result;
需要注意的是,as 可以省略。比如:
6. 去重(distinct)
去重就是对指定列进行合并重复项处理。我们先来查询一下数学成绩,可以看到有两位同学的数学成绩相同。
select id, math from exam_result;
接下来我们使用去重的语句,将重复项只显示一个。
select distinct math from exam_result;
需要注意的是,当我们使用 distinct 进行去重时,尽量只针对一列数据进行去重,否则会造成歧义。比如,执行以下语句,我们的目的是显示不重复的数学成绩并显示它们对应的行号,但是结果并不能实现。
可以看到重复的成绩依然存在,因为表是二维结构,在一张表格中不会出现某一行或者某一列多了或者少了的情况,因此,它会显示原来的表中的内容。
注意:
select 语句是针对列进行投影,不影响查询出来的行数(distinct 除外),但是影响列数。例如 select 1 from exam_result 就是将每一行投影为 1。
select 查询出来的结果,在没有加 order by 的情况下,不应该被认为是有序的,虽然大部分实现时都表现为插入时的顺序。
7. 排序(order by)
select ... from ... order by 字段;
以该字段为依据进行比较排序,默认是升序,也可以将 asc / desc 加在字段后,表示以升序或者降序进行排列。
-- ascend 升序
select ... from ... order by 字段 asc;
-- descend 降序
select ... from ... order by 字段 desc;
我们实际演示一下:
select * from exam_result order by id;
select * from exam_result order by id desc;
我们再来看一个例子,此处的字段也可以是表达式:
可以看到,此处并不是将 id 直接 * 10 再显示最后进行排序,而是直接将 id 显示,但是排序时是按照 id * 10 的结果进行升序排序(默认)的。
还需要注意的是,对于 order by 后面的字段并没有特别的限制,它可以来自于表中,也可以并不是表中的字段:
select id, chinese + math + english total from exam_result order by total;
以上就是按照顺序将已有的字段求和并改名后根据改名后的字段来进行排序。
7.1 多字段排序
select ... from ... order by 字段1 asc, 字段2 asc;
select ... from ... order by 字段1 desc, 字段2 asc;
select ... from ... order by 字段1 asc, 字段2 desc;
优先按照 字段1 比较排序,如果 字段1 的值不相等,直接按照 字段1 进行排序;
如果 字段1 的值相等,才会按照 字段2 的值进行排序。
order by math desc, chinese asc;
张三 数学 28 语文 32
李四 数学 28 语文 32
我们可以认为:张三 等于 李四
因此,张三排在李四前 or 李四排在张三前 【都可能】
select * from exam_result order by math, id desc;
SELECT name, qq_mail FROM students ORDER BY qq_mail DESC;
注意: NULL 比任何值都小。
8. 行筛选(where)
将每行记录代入 where 后边的逻辑表达式中,得到 true(1) / false(0) 的结果,只保留结果为 true(1) 的行。没有 where 子句时,则表中有多少行,展示多少行。
运算符
|
说明
|
>, >=, <, <=
|
大于,大于等于,小于,小于等于
|
=
|
等于,
NULL
不安全,例如
NULL = NULL
的结果是
NULL
|
<=>
|
等于,
NULL
安全,例如
NULL <=> NULL
的结果是
TRUE(1)
|
!=, <>
|
不等于
|
BETWEEN a0 AND
a1
|
范围匹配,
[a0, a1]
,如果
a0 <= value <= a1
,返回
TRUE(1)
|
IN (option, ...)
|
如果是
option
中的任意一个,返回
TRUE(1)
|
IS NULL
|
是
NULL(标准做法)
|
IS NOT NULL
|
不是
NULL
|
LIKE
|
模糊匹配。
%
表示任意多个(包括
0
个)任意字符;
_
表示任意一个字 符
|
-- 没有 where 子句,展示 7 行数据
select * from exam_result;
-- 只保留 id 为 1 的行
select * from exam_result where id = 1;
-- 展示 [2, 3, 4, 5, 6, 7] 6 行
select * from exam_result where id > 1;
-- 只有字面量的表达式,哪行代入,1都等于1,所以展示 7 行
select * from exam_result where 1 = 1;
当 where 后为常量时,一定为真,因此显示表中所有的数据。
-- 0 条
select * from exam_result where 1 != 1;
由于 1 != 1 条件为假,因此不会显示任何数据。
-- 0 条,因为 qq_mail = null 的结果恒为 null(false)
select * from student where qq_mail = null;
关于数据库中的值 null
null 代表该值不知道,不清楚。
有 null 参与的表达式的结果:仍然不知道,所以仍然是 null
null + 3 -> null
null = 3 -> null
null = null -> null
null <=> null -> 1
null 被视为是 false
select * from students where qq_mail is null;
select * from students where qq_mail <=> null;
-- 二者等价
-- 观察AND 和 OR 的优先级:
SELECT * FROM exam_result WHERE chinese > 80 or math>70 and english > 70;
SELECT * FROM exam_result WHERE (chinese > 80 or math>70) and english > 70;
8.1 字段/字面量 between ... and ...
需要注意的是,此处的区间为前闭后闭。
select id, chinese from exam_result where chinese between 60 and 100;
8.2 集合判断 in(...)
判断前面的元素是否位于后面的集合中。
select * from exam_result where chinese in (70, 75, 80, 85, 90, 95);
-- 查询语文成绩是 70 或者 75 或者 80 或者 85 或者 90 或者 95的同学
SELECT * FROM exam_result
WHERE chinese = 70 or chinese = 75
OR chinese = 80 OR chinese = 85
OR chinese = 90 OR chinese = 95;
-- 二者等价
8.3 模糊匹配 like
模糊匹配主要用于字符串比较。
name like '%张%';
name 可以任何字符开头,并且长度不限 [0, +∞)
后边跟着 '张' 最后任何字符结尾,并且长度不限 [0, +∞)
-> name 中有 '张' 这个字符,结果就是 true
"张" => true
"1张" => true
"1张1" => true
name like '%张';
name 可以任何字符开头,并且长度不限 [0, +∞),后边跟着 '张';
-> name 中以 '张' 这个字符结尾,结果就是 true
"张" => true
"1张" => true
"1张1" => false
name like '张%';
name 以 '张' 开头,任何字符结尾,并且长度不限 [0, +∞)
-> name 中以 '张' 这个字符开始,结果就是 true
"张" => true
"1张" => false
"1张1" => false
name like '张' 等价于 name = '张'。
name like '_张_';
name 可以任何字符开头,并且长度必须是 1
后边跟着 '张'
最后任何字符结尾,并且长度必须是 1
一定是 3 个字,第二个字是'张'
8.4 分页查询 Limit offest
要使用 limit offset 切片,前提:结果数据已经有序(order by),否则结果不做保证。
select ... from ... where ... order by ...
limit 几条 offset 从哪开始(从0开始)
-- 前 3 条
limit 3 offset 0;
-- [5, 15]
limit 11 offset 4;
-- 等价于,但不推荐
limit 0, 3;
limit 4, 11;
-- 当 offset 是 0 时,可以省略 offset
limit 3 offset 0;
limit 3;
三、数据的更新(update)
update 表名
set
字段1 = ...;
字段2 = ...;
where ...;
修改表中的符合 where 的所有记录,更新字段1 和 字段 2。如果没有 where 条件,则全部更改。
update exam_result set math = 100 where name = '孙悟空';
update
exam_result
set
math = math + 1
order by
chinese + math + english
limit
3;
-- 将总成绩倒数前三的同学的数学成绩 +1
四、数据的删除(delete)
delete from 表 where ...;
删除所有符合条件的行。如果没有 where 条件,删除所有行。
需要注意的是 在 WorkBench 中可能无法直接进行更新和删除,根据报错信息我们可以看到需要禁用安全模式才可以删除或者更新数据,因此按照下图关闭安全模式即可。
五、OJ 练习
OJ 题目练习
六、函数
6.1 截取字符串
substr(字段, 1, n);
MySQL 中 下标从 1 开始。
select substr("hello world", 2, 3);
以上语句就表示将 “hello world” 字符串从第 2 个元素开始,截取 3 个字符:
6.2 将字符转为大写
-- upper(...)
select upper("hello");
6.3 连接字符串
-- concat(...)
select concat("hello"," ","world");
6.4 DATEDIFF 计算两个日期相隔多少天
select datediff("2023-04-22","2023-06-01");
6.5 获取时间字符串的年月日 时分秒
select year("2023-04-22 12:13:45");
select month("2023-04-22 12:13:45");
select day("2023-04-22 12:13:45");
select hour("2023-04-22 12:13:45");
select minute("2023-04-22 12:13:45");
select second("2023-04-22 12:13:45");