MySql数据类型
Enum类型和SET类型
假如我们有一列是性别只能填男或者女,如果使用Int或者varchar非常尴尬,我们提出了一个叫ENUM的类型,也称为枚举类型,它的格式如:ENUM('str1', 'str2', 'str3' ⋯)它表示在给定的字符串列表里选择一个。比如我们的性别一列可以定义成ENUM('男', '女')类型。这个的意思就是性别一列只能在'男'或者'女'这两个字符串之间选择一个,相当于一个单选框~
有的时候某一列的值可以在给定的字符串列表中挑选多个,假设学生的基本信息加了一列兴趣属性,这个属性的值可以从给定的兴趣列表中挑选多个,那我们可以使用SET类型,它的格式如下:
SET('str1', 'str2', 'str3' ⋯)它表示可以在给定的字符串列表里选择多个。我们的兴趣一列就可以定义成SET('打球', '画画', '扯犊子', '玩游戏')类型。这个的意思就是兴趣一列可以在给定的这几个字符串中选择一个或多个,相当于一个多选框~
综上所述,ENUM和SET类型都是一种特殊的字符串类型,在从字符串列表中单选或多选元素的时候会用得到它们。
主键和UNIQUE约束的区别
主键和unique约束都能保证某个列或者列组合的唯一性,但是:
- 1.一张表中只能定义一个主键,却可以定义多个unique约束
- 2.规定:主键列不允许存放null,而声明了unique属性的列可以存放null,而且null可以重复出现在多条记录中。
如果A表中的某个列或者某些列依赖与B表中的某个列或者某些列,那么就称A表为子表,B表为父表。子表和父表可以使用外键来关联起来,上边例子中student_score表的number列依赖于student_info的number列,所以student_info就是一个父表,student_score就是子表。
查询通配符匹配:
- %:代表任意一个字符串。
- _:代表任意一个字符。_只能代表一个字符(%是代表任意一个字符串)
转义通配符
如果待匹配的字符串中本身就包含普通字符'%'或者'_'该咋办,怎么区分它是一个通配符还是一个普通字符呢?
答:如果匹配字符串中需要普通字符'%'或者'_'的话,需要在它们前边加一个反斜杠\来和通配符区分开来,也就是说:
'\%'代表普通字符'%'
'\_'代表普通字符'_' 比方说这样:
COUNT函数
COUNT函数使用来统计行数的,它有下边两种使用方式:
- 1.COUNT(*):对表中行的数目进行计数,不管列的值是不是NULL。
- 2.COUNT(列名):对特定的列进行计数,会忽略掉该列为NULL的行。
两者的区别是会不会忽略统计列的值为NULL的行!两者的区别是会不会忽略统计列的值为NULL的行!两者的区别是会不会忽略统计列的值为NULL的行!
学生信息表
DROP TABLE IF EXISTS `student_info`;
CREATE TABLE `student_info` (
`number` int(11) NOT NULL,
`name` varchar(5) DEFAULT NULL,
`sex` enum('男','女') DEFAULT NULL,
`id_number` char(18) DEFAULT NULL,
`department` varchar(30) DEFAULT NULL,
`major` varchar(30) DEFAULT NULL,
`enrollment_time` date DEFAULT NULL,
PRIMARY KEY (`number`),
UNIQUE KEY `uk_id_number` (`id_number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `student_info` VALUES ('20180101', '杜子腾', '男', '158177199901044792', '计算机学院', '计算机科学与工程', '2018-09-01');
INSERT INTO `student_info` VALUES ('20180102', '杜琦燕', '女', '151008199801178529', '计算机学院', '计算机科学与工程', '2018-09-01');
INSERT INTO `student_info` VALUES ('20180103', '范统', '男', '17156319980116959X', '计算机学院', '软件工程', '2018-09-01');
INSERT INTO `student_info` VALUES ('20180104', '史珍香', '女', '141992199701078600', '计算机学院', '软件工程', '2018-09-01');
INSERT INTO `student_info` VALUES ('20180105', '范剑', '男', '181048199308156368', '航天学院', '飞行器设计', '2018-09-01');
INSERT INTO `student_info` VALUES ('20180106', '朱逸群', '男', '197995199501078445', '航天学院', '电子信息', '2018-09-01');
学生成绩表
DROP TABLE IF EXISTS `student_score`;
CREATE TABLE `student_score` (
`number` int(11) NOT NULL,
`subject` varchar(30) NOT NULL,
`score` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`number`,`subject`),
CONSTRAINT `student_score_ibfk_1` FOREIGN KEY (`number`) REFERENCES `student_info` (`number`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `student_score` VALUES ('20180101', '母猪的产后护理', '78');
INSERT INTO `student_score` VALUES ('20180101', '论萨达姆的战争准备', '88');
INSERT INTO `student_score` VALUES ('20180102', '母猪的产后护理', '100');
INSERT INTO `student_score` VALUES ('20180102', '论萨达姆的战争准备', '98');
INSERT INTO `student_score` VALUES ('20180103', '母猪的产后护理', '59');
INSERT INTO `student_score` VALUES ('20180103', '论萨达姆的战争准备', '61');
INSERT INTO `student_score` VALUES ('20180104', '母猪的产后护理', '55');
INSERT INTO `student_score` VALUES ('20180104', '论萨达姆的战争准备', '46');
having查询理解
查询最高分超过98的科目的平均分
SELECT subject, AVG(score) FROM student_score GROUP BY subject HAVING MAX(score) > 98;
与where子句的区别:where子句在分组前进行过滤,作用于每一条记录,where子句过滤掉的记录将不包含在分组中。而having子句在数据分组后进行过滤,作用于分组
子查询
- 1.标量子查询
SELECT * FROM student_score WHERE number = (SELECT number FROM student_info WHERE name = '杜琦燕');
- 2.列子查询
SELECT * FROM student_score WHERE number IN (SELECT number FROM student_info WHERE major = '计算机科学与工程');
- 3.表子查询
SELECT * FROM student_score WHERE (number, subject) IN (SELECT number, '母猪的产后护理' FROM student_info WHERE major = '计算机科学与工程');
- 4.exists和not子查询
有时候外层查询并不关心子查询中的结果是什么,而只关心子查询的结果集是不是为空集,这时可以用到下边这两个操作符
SELECT * FROM student_score WHERE EXISTS (SELECT * FROM student_info WHERE number = 20180108);
其中子查询的意思是在student_info表中查找学号为20180108的学生信息,很显然并没有学号为20180108的学生,所以子查询的结果集是一个空集,于是EXISTS表达式的结果为FALSE,所以外层查询也就不查了,直接返回了一个Empty set,表示没有结果。你可以自己试一下NOT EXISTS的使用。
- 5.相关子查询
有时候我们需要在子查询语句中引用到外层查询的值,这样的子查询就不能当作一个独立的语句执行,这种子查询被称为相关子查询
SELECT number, name, id_number, major FROM student_info WHERE EXISTS (SELECT * FROM student_score WHERE student_score.number = student_info.number);
这条查询语句含义为:查询在student_score表有成绩的学生信息。
查询流程分析:
1.先执行外层查询获得到student_info表的第一条记录,发现它的number值是20180101。把20180101当作参数传入到子查询,此时子查询的意思是判断student_score表的number字段是否有20180101这个值存在,子查询的结果是该值存在,所以整个EXISTS表达式的值为TRUE,那么student_info表的第一条记录可以被加入到结果集。
2.再执行外层查询获得到student_info表的第二条记录,发现它的number值是20180102,与上边的步骤相同,student_info表的第二条记录也可以被加入到结果集。
3.与上边类似,student_info表的第三条记录也可以被加入到结果集
4.与上边类似,student_info表的第四条记录也可以被加入到结果集。
5.再执行外层查询获得到student_info表的第五条记录,发现它的number值是20180105,把20180105当作参数传入到它的子查询,此时子查询的意思是判断student_score表的number字段是否有20180105这个值存在,子查询的结果是该值不存在,所以整个EXISTS表达式的值为FALSE,那么student_info表的第五条记录就不被加入结果集中。
6.与上一步骤类似,student_info表的第六条记录也不被加入结果集中。
7.student_info表没有更多的记录了,结束查询。
exists和in的查询方式
#对B查询涉及id,使用索引,故B表效率高,可用大表 -->外小内大
select * from A where exists (select * from B where A.id=B.id);
#对A查询涉及id,使用索引,故A表效率高,可用大表 -->外大内小
select * from A where A.id in (select id from B);
exists是对外表进行loop循环,每次loop循环在对内表(子查询)进行查询,那么因为对内表的查询使用的索引(内表效率高,故可用大表),而外表有多大都需要遍历,不可避免(尽量用小表),故内表大的使用exists,可加快效率;
in是把外表和内表做hash连接,先查询内表,再把内表结果与外表匹配,对外表使用索引(外表效率高,可用大表),而内表多大都需要查询,不可避免,故外表大的使用in,可加快效率。
笛卡尔积
select * from t1,t2;
上述sql查询出来的结果集为笛卡尔积:表里数据任意组合比如t16条记录,t2中8条记录,他们的笛卡尔积数量为6*8=48。
Join,Inner Join,Cross Join
select * from t1,t2;
select * from t1 INNER JOIN t2;
select * from t1 CROSS JOIN t2;
select * from t1 JOIN t2;
上述几种写法均是等价的
Union和Union All
Union和Union All的每个子查询加入Order By语句作用和没有加入一样:组合查询并不保证最后汇总起来的大结果集中的顺序是按照各个小查询的结果集中的顺序排序的
Insert Ignore Into
使用场景:对于那些是主键或者具有UNIQUE约束的列或者列组合来说,如果表中已存在的记录中没有与待插入记录在这些列或者列组合上重复的值,那么就把待插入记录插到表中,否则忽略此次插入操作
insert on duplicate key update
对于那些是主键或者具有UNIQUE约束的列或者列组合来说,如果表中已存在的记录中没有与待插入记录在这些列或者列组合上重复的值,那么就把待插入记录插到表中,否则按照规定去更新那条重复的记录中某些列的值
insert into first_table VALUES(2,'红牛'),(3,'谷歌') on DUPLICATE key UPDATE second_column = values(second_column)
delete和update
delete和update语句可以使用 order by、limit限制删除或者更新的条目数量
视图
创建视图语句:
CREATE VIEW 视图名 AS 查询语句
指定视图虚拟列名称:
CREATE VIEW student_info_view(no, n, m) AS SELECT number, name, major FROM student_info;
在对视图进行查询时,MySQL服务器将会帮助我们把对视图的查询语句转换为对底层表的查询语句然后再执行。
在使用层面,我们完全可以把视图当作一个表去使用,但是它的实现原理却是在执行语句时转换为对底层表的操作。视图的优势:简化语句的书写,避免了每次都要写一遍又臭又长的语句,而且对视图的操作更加直观,使用者也不用去考虑它的底层实现细节。
对视图执行INSERT、DELETE、UPDATE语句的本质上是对该视图对应的底层表中的数据进行增、删、改操作
不过并不是可以在所有的视图上执行更新语句的,在生成视图的时候使用了下边这些语句的都不能进行更新:
- 聚集函数(比如SUM(), MIN(), MAX(), COUNT()等等)
- DISTINCT
- GROUP BY
- HAVING
- UNION 或者 UNION ALL
- 某些子查询
- 某些连接查询
- 等等等等
自定义变量:规定自定义变量前边必须加@符号
SET @a = 1;
SELECT @a;
SET @a = '哈哈哈';
SET @b = @a;
select @b;
SET @a = (SELECT m1 FROM t1 LIMIT 1);
SELECT n1 FROM t1 LIMIT 1 INTO @b;
SELECT @a, @b;
SELECT m1, n1 FROM t1 LIMIT 1 INTO @a, @b;
SELECT @a, @b;
Mysql语句结束分隔符
在Mysql客户端命令交互界面,当我们按下回车键,mysql客户端会检测输入内容中是否包含;、\G、\g这三个符号,如果有那么命令会被发送到服务器。如果我们想要一次性给服务器发送多个,需要将多条命令写到一行。比如
mysql> SELECT * FROM t1 LIMIT 1;SELECT * FROM t2 LIMIT 1;SELECT * FROM t3 LIMIT 1;
mysql给我们提供了delimiter命令自定义mysql语句输入结束符号(比如delimiter EOF )也就是说通过delimiter命令,我们可以将mysql语句写在多行,直到打出字符EOF 回车后,我们的sql语句才会被提交给mysql服务器执行。
mysql> delimiter EOF
mysql> SELECT * FROM t1 LIMIT 1;
-> SELECT * FROM t2 LIMIT 1;
-> SELECT * FROM t3 LIMIT 1;
-> EOF
最后在执行delimiter ;切换回常用的分号
存储函数:
1.一般形式:
CTREATE FUNCTION 存储函数名称(参数列表)
RETURNS 返回值类型
BEGIN
函数体内容
END
参数列表跟java不同的是,java类型在前参数名在后,mysql相反参数列表格式比如c
2.查看已定义的存储函数
SHOW FUNCTION STATUS [LIKE 需要匹配的函数名]
3.查看存储函数定义
SHOW CREATE FUNCTION 函数名
4.删除存储函数
DROP FUNCTION 函数名
5.存储函数体内的变量定义:
DECLARE 变量名1, 变量名2, ... 数据类型 [DEFAULT 默认值];
比如:DECLARE result INT DEFAULT 0;
6.函数体内的判断语句
IF 表达式 THEN
处理语句列表
[ELSEIF 表达式 THEN
处理语句列表]
... # 这里可以有多个ELSEIF语句
[ELSE
处理语句列表]
END IF;
7.1函数体内的循环语句
WHILE 表达式 DO
处理语句列表
END WHILE;
7.2函数体内的循环语句
REPEAT
处理语句列表
UNTIL 表达式 END REPEAT;
7.3函数体内的循环语句
LOOP
处理语句列表
END LOOP;
8.一个简单的存储函数
CREATE FUNCTION sum_all(n INT UNSIGNED)
RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
DECLARE i INT DEFAULT 1;
REPEAT
SET result = result + i;
SET i = i + 1;
UNTIL i > n END REPEAT;
RETURN result;
END
CREATE FUNCTION sum_all(n INT UNSIGNED)
RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
DECLARE i INT DEFAULT 1;
flag:LOOP
IF i > n THEN
LEAVE flag;
END IF;
SET result = result + i;
SET i = i + 1;
END LOOP flag;
RETURN result;
END
8.1调用存储函数sum_all;
SELECT sum_all(3);
存储过程
存储函数一般会执行语句返回一个值,但是存储过程在于执行一系列sql语句
1一般形式
CREATE PROCEDURE 存储过程名称([参数列表])
BEGIN
需要执行的语句
END
2.调用存储过程
CALL 存储过程名称([参数列表])
3.查看已定义的存储过程
SHOW PROCEDURE STATUS [like 需要匹配的存储过程名称]
4.查看存储过程的定义
SHOW CREATE PROCUDURE 存储过程名称
5.删除存储过程
DROP PROCEDURE 存储过程名称
6.存储过程也支持像存储函数变量使用、判断、循环语句。
7.存储过程的参数列表支持添加前缀,如下:
[IN | OUT | INOUT] 参数名 数据类型
参数如果默认不加前缀默认为IN参数
存储过程与存储函数的不同点
- 1.存储函数在定义时候需要显示用RETURNS语句指定返回的数据类型,而在函数体内必须用RETURN语句显示指定返回值,存储过程不需要
- 2.存储函数只支持IN参数,存储过程支持IN参数、OUT参数、和INOUT参数
- 3.存储函数只能返回一个值,而存储过程可以设置多个OUT参数或者INOUT参数来返回多个结果
- 4.存储函数执行过程中产生的结果集不会被显示到客户端,而存储过程执行过程中产生的结果集会被显示到客户端
- 5.存储函数直接在表达式中调用,而存储过程只能通过CALL显式调用
游标的使用
1.游标的意义
游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行相应的操作;游标既可以用在存储函数中,也可以用在存储过程中,还能用在触发器与事件
2.游标使用步骤
创建游标->打开游标->通过游标访问记录->关闭游标
3.创建游标
DECLARE 游标名称 CURSOR FOR 查询语句;
如果存储程序中也有声明局部变量的语句,创建游标的语句一定要放在局部变量声明后头。
3.打开关闭游标
OPEN 游标名称;
CLOSE 游标名称;
4.使用游标
FETCH 游标名 INTO 变量1,变量2...变量n;
5.遍历时的执行策略
DECLARE CONTINUE HANDLER FOR NOT FOUND 处理语句;
6.完整示例
CREATE PROCEDURE cursor_demo()
BEGIN
DECLARE m_value INT;
DECLARE n_value CHAR(1);
DECLARE not_done INT DEFAULT 1;
DECLARE t1_record_cursor CURSOR FOR SELECT m1, n1 FROM t1;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET not_done = 0;
OPEN t1_record_cursor;
flag: LOOP
FETCH t1_record_cursor INTO m_value, n_value;
IF not_done = 0 THEN
LEAVE flag;
END IF;
SELECT m_value, n_value, not_done;
END LOOP flag;
CLOSE t1_record_cursor;
END
触发器
我们可以在对表中记录做增、删、改前后让MySql服务器自动执行一些额外的语句,便可以通过触发器去实现
1.创建触发器语句
CREATE TRIGGER 触发器名
{BEFORE|AFTER}
{INSERT|DELETE|UPDATE}
ON 表名
FOR EACH ROW
BEGIN
触发器内容
END
ps:由大括号`{}`包裹并且内部用竖线`|`分隔的语句表示必须在给定的选项中选取一个值,比如`{BEFORE|AFTER}`表示必须在`BEFORE`、`AFTER`这两个之间选取一个。
2.解释
{INSERT|DELETE|UPDATE}表示具体的语句,MySQL中目前只支持对INSERT、DELETE、UPDATE这三种类型的语句设置触发器。
FOR EACH ROW BEGIN ... END表示对具体语句影响的每一条记录都执行我们自定义的触发器内容:
对于INSERT语句来说,FOR EACH ROW影响的记录就是我们准备插入的那些新记录。
对于DELETE语句和UPDATE语句来说,FOR EACH ROW影响的记录就是符合WHERE条件的那些记录(如果语句中没有WHERE条件,那就是代表全部的记录)。
因为MySQL服务器会对某条语句影响的所有记录依次调用我们自定义的触发器内容,所以针对每一条受影响的记录,我们需要一种访问该记录中的内容的方式,MySQL提供了NEW和OLD两个单词来分别代表新记录和旧记录,它们在不同语句中的含义不同:
- 对于INSERT语句设置的触发器来说,NEW代表准备插入的记录,OLD无效。
- 对于DELETE语句设置的触发器来说,OLD代表删除前的记录,NEW无效。
- 对于UPDATE语句设置的触发器来说,NEW代表修改后的记录,OLD代表修改前的记录。
3.查看触发器
SHOW TRIGGERS;
4.查看触发器的具体定义
SHOW CREATE TRIGGER 触发器名;
5.删除触发器
DROP TRIGGER 触发器名称;
6.定义一个触发器
CREATE TRIGGER bi_t1
BEFORE INSERT ON t1
FOR EACH ROW
BEGIN
IF NEW.m1 < 1 THEN
SET NEW.m1 = 1;
ELSEIF NEW.m1 > 10 THEN
SET NEW.m1 = 10;
END IF;
END $
7.触发器注意事项
7.1触发器内容中不能有输出结果集的语句
mysql> delimiter $
mysql> CREATE TRIGGER ai_t1
-> AFTER INSERT ON t1
-> FOR EACH ROW
-> BEGIN
-> SELECT NEW.m1, NEW.n1;
-> END $
ERROR 1415 (0A000): Not allowed to return a result set from a trigger
7.2Update触发器无法更改旧值
mysql> delimiter $
mysql> CREATE TRIGGER bu_t1
-> BEFORE UPDATE ON t1
-> FOR EACH ROW
-> BEGIN
-> SET OLD.m1 = 1;
-> END $
ERROR 1362 (HY000): Updating of OLD row is not allowed in trigger
7.3AFTER触发器不能使用SET new.列明=值操作因为在执行AFTER触发器的内容时记录已经被插入完成或者更新完成了。
mysql> delimiter $
mysql> CREATE TRIGGER ai_t1
-> AFTER INSERT ON t1
-> FOR EACH ROW
-> BEGIN
-> SET NEW.m1 = 1;
-> END $
ERROR 1362 (HY000): Updating of NEW row is not allowed in after trigger
事件
有时候我们想让Mysql服务器在某个时间点或者每隔一段时间自动执行一些语句
1.创建事件语法
CREATE EVENT 事件名
ON SCHEDULE
{
AT 某个确定时间点|
EVERY 期望的时间间隔 [STARTS datetime] [END datetime]
}
DO
BEGIN
具体的语句
END
2.查看事件
SHOW EVENTS;
3.查看具体的事件定义
SHOW CREATE EVENT 事件名;
4.删除事件
DROP EVENT 事件名;
5.事件使用注意事项
默认情况下,MySQL服务器不会帮助我们执行事件,我们通过以下语句开启
SET GLOBAL event_scheduler = ON;