Note:这里根据 CSDN Mysql技能树 整理的易错题,可参考MySQL 有这一篇就够,MySQL详细学习教程(建议收藏),MySQL 菜鸟教程
create
+ grant
),参考mysql创建新用户并授权mysql -h Joe的开发机地址-u joe-p mysql
在局域网内的任何计算机上使用。mysql -h IP地址 -u 用户 -p 密码 -P 端口
登录。create database 数据库名
或者 drop ...
)id INT PRIMARY KEY AUTO_INCREMENT
)drop table 表名
),参考MySQL 有这一篇就够innodb
,参考MySQL常见的三种存储引擎,mysql索引详解InnoDB
支持事务和外键,有更好的并发能力。更新(删除)操作频率也高,或者要保证数据的完整性,比如OA自动化办公系统。MyISAM
不支持事务和外键,结构简单,可以压缩为只读状态。以读写插入为主的应用程序,比如博客系统、新闻门户网站。Memory
引擎将数据保存在内存中,重启会丢失数据,读速度快很快,适合作为会话表和缓存表。Memory
引擎:
memory
存储引擎myisam
存储引擎 (磁盘临时表也可以使用innodb
存储引擎,通过internal_tmp_disk_storage_engine
参数来控制使用哪种存储引擎,mysql5.7.6
之后默认为innodb
存储引擎,之前版本默认为myisam
存储引擎。Note:
DDL
代表数据定义语言,是一种有助于创建数据库模式的SQL
命令。DDL
中常用的命令有:create,drop,alter,truncate
和rename
等等。DML
代表数据操作语言,是一种有助于检索和管理关系数据库中数据的SQL命令。DML中常用的命令有:insert,update,delete
和select
等等。varchar str = "123dafa" // 转换为数字是 123
DECIMAL(P,D)
:P是表示有效数字数的精度,P范围为1〜65
,D是表示小数点后的位数,D的范围是0~30
,MySQL要求D小于或等于(<=)P 比如DECIMAL(4,2),值为23.46。int
类型的x
和y
,select id, (x^2 + y^2)/2 as result from points;
输出的result
类型为decimal
类型。default
约束,这题datetime
和timestamp
都行),可参考MySQL默认值约束,mysql datetime与timestamp区别varchar
类型的长度是可变的,而char
类型的长度是固定的,当定义为 char(10)
时,即使插入的内容是 abc
3 个字符,它依然会占用 10 个字符,其中包含了 7 个空字符的存储空间。char
长度最大为 255 个字符,varchar
长度最大为 65535 个字符;varchar
类型的查找效率比较低,而char
类型的查找效率比较高,因此变长度使用 varchar
,固定长度使用char
。MySQL 5.0.3
版的一项更改包括将VARCHAR
字段的最大长度从255
个字符增加到65,535
个字符。这使得VARCHAR
类型比以往任何时候都更类似于TEXT
。虽然两种数据类型共享的最大长度为65,535
个字符,但VARCHAR
中的VAR表示您可以将最大大小设置为1到65,535之间的任何值。 TEXT
字段的最大固定大小为65,535
个字符。MEDIUMTEXT
最多可存储16 MB的字符串,LONGTEXT
最多可存储4 GB的字符串。mediumBlob
),可参考MySQL各个类型详解(看评论),Blob数据类型及应用,MySQL:BLOB与TEXT及其最大存储限制blob
用于存放二进制数,最大字节数为65535
,即文件大小最大为65K
;而varchar
最大字符数为65535
。char,varchar,text
是以字符为基本单位;而blob
是以字节为基本单位,没有字符集的说法。INSERT INTO SELECT
语句从一个表复制数据,然后把数据插入到一个已存在的表中,要求目标表和源表都存在,mysql支持INSERT INTO SELECT
语句。SELECT INTO FROM
语句在插入时会自动创建目标表,并将源表中指定字段数据插入到目标表中,这里要求目标表不存在。但mysql
中不可用 SELECT INTO FROM
语句,建议先创建目标表,接着使用INSERT INTO SELECT
语句把源表数据转存到目标表中。call
调用)。而函数可以作为查询语句的一个部分来调用。join
、in
、exists
)或差集(not in
、not exists
),参考SQL求交集与差集,日期大小比较(DATEDIFF('2017-11-30','2017-11-27')
)user_id | begin_date | end_date | pay
100 | 2023-02-11 13:34:38 | 2023-02-12 13:34:43 | 1
101 | 2023-02-13 12:30:44 | 2023-02-17 12:30:47 | 1
102 | 2023-02-13 12:31:26 | 2023-02-17 12:31:29 | 1
103 | 2023-02-15 12:35:22 | 2023-02-16 12:35:28 | 1
104 | 2023-02-15 12:35:46 | 2023-02-16 12:35:55 | 1
105 | 2023-02-15 12:36:08 | 2023-02-16 12:36:13 | 1
101 | 2023-02-16 12:30:44 | 2023-02-17 12:30:47 | 1
102 | 2023-02-16 12:31:26 | 2023-02-17 12:31:29 | 1
101 | 2023-02-18 12:53:49 | 2023-02-19 12:53:54 | 1
105 | 2023-03-12 13:43:39 | 2023-03-14 13:43:46 | 1
假设我想从上表中筛选出2023.2.14
之后新注册的会员个数(标准答案为3
,即103
,104
,105
),SQL
语句的编写思路如下:
2023.2.14
之前就已经注册的会员,查询字段为DISTINCT(user_id)
,查询结果记为b
;select DISTINCT(b.user_id) from (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') < 0
) as b
---
user_id
101
102
100
2023.2.14
之后注册的会员(这些会员中可能包括2023.2.14
之前已注册的会员),查询字段为user_id
,查询结果记为a
;select * from vip_test where DATEDIFF(begin_date,'2023-02-14') > 0
---
user_id
103
104
105
101
102
101
105
a
作为基准,求解不在b.user_id
中的a.user_id
(即求a,b
之间的差集,用not in
或not exists
),并且进行去重处理。#正确答案为3
select count(DISTINCT(a.user_id)) from (
select user_id from vip_test where DATEDIFF(begin_date,'2023-02-14') > 0 ) as a
where a.user_id not in (
select DISTINCT(b.user_id) from (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') < 0
) as b
)
---
count(DISTINCT(a.user_id))
3
DATEDIFF
进行日期的比较,忽略掉了105
#错误答案为2
select DISTINCT(a.user_id) from (
select * from vip_test where day(begin_date) > 14 ) as a
where a.user_id not in (
select DISTINCT(b.user_id) from (
select * from vip_test where day(begin_date) < 14
) as b
)
---
user_id
103
104
inner join
,对左右两表(左表为2023.2.14
前,右表为2023.2.14
后)中相同的user_id
进行内连接,得到两表中user_id
的交集,记作a
;select DISTINCT(a.user_id) from (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') < 0
) as a
INNER JOIN (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') > 0
) as b
ON a.user_id = b.user_id
---
user_id
101
102
接着以全表为基准,使用not in
求解不在a.user_id
中的用户编号,此时查询结果多了一个100
这个用户 #错误答案为4
select DISTINCT(user_id) from vip_test as c where user_id not in (
select DISTINCT(a.user_id) from (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') < 0
) as a
INNER JOIN (
select * from vip_test where DATEDIFF(begin_date,'2023-02-14') > 0
) as b
ON a.user_id = b.user_id
)
---
user_id
103
104
105
100
表的基本结构(InnoDB存储引擎用B+Tree实现其索引结构,B+树相较于B-树的特点是:非叶子节点只存储键值信息;所有叶子节点之间都有一个链指针;数据记录都存放在叶子节点中),可参考mysql索引详解
建表语句(根据业务要求建表)
主键(主键或唯一键可以被引用为外键约束foregin key
,可 级联更新 和 级联删除),可参考MySQL外键约束详解
唯一约束1(如何保证user输入的book唯一,在 isbn 列上加唯一约束或者设置为主键),唯一约束2(不修改表字段,可添加唯一约束),可参考MySQL添加唯一约束(UNIQUE)
Note:
唯一约束用来保证一列(或一组列)中的数据是唯一的,类似与主键,但是有以下区别:
NULL
值。自增序列(自增字段必须是主键;即使插入操作失败,自增计数仍然会被递增,下次操作使用下一个整数;),可参考关于 MySQL 自增 ID 的事儿,自增主键与UUID的优缺点
Note:
ID
不发生主键冲突(前提是老系统也是数字型的)。特别是在新系统上线时,新旧系统并行存在,并且是异库异构的数据库的情况下,需要双向同步时,自增主键将是你的噩梦。UUID
唯一识别码 (Universally Unique Identifier):是指在一台机器上生成的数字,它保证对在同一时空中的所有机器都是唯一的。uuid
之间比较大小相对数字慢不少, 影响查询速度;uuid
占空间大, 如果你建的索引越多,影响越严重。Max ID
,当执行语句时则会提示主键冲突:1062 - Duplicate entry ‘4294967295’ for key ‘PRIMARY’
,此时可以直接选择 Bigint
类型,它的取值范围是无符号情况下: 0 ~ 2^64–1
(18446744073709551615),在一般场景下它是足够使用:假设每秒增加的记录数为1亿/秒
,大约5849
年才会用完。视图( 视图是从一个或多个表中导出来的表,是一种虚拟存在的表,其物理上是不存在的,但可用于查询),可参考【MySQL视图】视图的概念、创建、查看、删除和修改
计税(存储函数,create function
得到个人所得税函数),可参考MySQL的存储函数
Note:
create function individual_income_tax(salary decimal(12, 4))
returns decimal(12, 4)
deterministic
begin
-- ...
end;
创建存储过程,参考存储过程——输入参数(in)、输出参数(out)、输入输出参数(inout)
Note:
create procedure sp_idt(in salary decimal(12, 4), out tax decimal(12, 4), out take_home decimal(12, 4))
begin
set tax = individual_income_tax(salary);
set take_home = salary - tax;
end;
触发器是一种特殊的存储过程。
删除函数(drop function individual_income_tax;
)
触发器,交易审计(定义insert
和delete
类型的trigger
),可参考MySQL触发器
Note:
SQL
片段,但是触发器无需调用,当对数据库表中的数据执行DML操作(insert,update,delete
)时自动触发这个SQL片段的执行, 无需手动调用。语法如下:create trigger 触发器名 before|after 触发事件
on 表名 for each row
begin
执行语句列表
end;
create trigger trigger_test after insert
on user for each row
begin
insert into user_logs values(1,now,'有新用户增加');
end
MYSQL
中触发器中不能对本表进行insert,update,delete
操作,以免递归循环触发1s
, insert table
500条数据,那么就需要触发500次触发器,光是触发器执行的时间就要花费500s
, 而insert
500条数据一共是1s
,那么insert
的效率就非常的低了。show triggers;
,删除触发器drop trigger if exists trigger_test;
。存储引擎修改(将MyISAM
修改成innoDB
:alter table goods engine innodb;
)
as
可省略),可参考mysql中别名(列别名和表别名),MySQL 别名WHERE
子句中使用列别名。原因是当MySQL
评估求值WHERE
子句时,SELECT
子句中指定的列的值可能尚未确定。Where
不配Having
,WHERE age BETWEEN 18 AND 24
相当于WHERE age >= 18 AND age <= 24
),可参考MySQL中的BETWEEN...AND
的用法(BETWEEN
是闭区间)MySQL
用group by
进行分组,用case when ... then ... when ... then ... end
增加多个判断条件),可参考Sql 实现数据透视表功能,MySQL之数据分组与数据透视表dept
升序按salary
降序排序:select id, name, dept, salary from employee order by dept, salary desc;
UNION
和 UNION ALL
内部的 SELECT
语句必须拥有相同数量的列。SELECT
语句中列的顺序必须相同,因为union
时是按照字段顺序匹配,而不是按照字段名称匹配。union
对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序。union all
对两个结果集进行并集操作,包括重复行,不进行排序。REGEXP
与 模糊查询like
,可参考MySQL中的正则表达式(REGEXP),MySql like模糊查询语句用法,MySQL学习笔记: like和regexp的区别WHERE
子句中使用LIKE
子句,并且使用LIKE
子句代替等号 =
。'%a' 以a结尾的数据
'a%' 以a开头的数据
'%a%' 含有a的数据
'_a_' 三位且中间字母是a的
'_a' 两位且结尾字母是a的
'a_' 两位且开头字母是a的
Contacts.Phone
以查找以 00
结尾的所有电话号码:SELECT Surname, Surname, City, Phone
FROM Contacts
WHERE Phone REGEXP '\\d{8}00';
json
,为的是实现非关系型数据库),可参考MySQL常用Json函数,mysql的json函数$
表示整个json对象,在索引数据时用下标(对于json array,从0
开始)或键值(对于json object,含有特殊字符的key
要用"括起来,比如$."my name"
)。[3, {"a": [5, 6], "b": 10}, [99, 100]]
,那么:$[0]:3
$[1]: {"a": [5, 6], "b": 10}
$[2] :[99, 100]
$[3] : NULL
$[1].a:[5, 6]
$[1].a[1]:6
$[1].b:10
$[2][0]:99
CAST()
),可参考MySQL基础—数据类型转换CAST
SELECT CAST('123' AS INT); //语法错误,没有INT表达式
正确语法:SELECT CAST('123' AS SIGNED); //结果:123;结果转换为整数;
select CAST('3.1415926' AS DECIMAL(4,2)); //结果:3.14;结果转换为浮点型;
SELECT CAST(NOW() AS DATE); //结果:2020-10-22;结果转化为DATE类型;NOW() 输出为DATETIME类型,被转换成DATE类型;
CONVERT()
,不使用ENCODE
,DECODE
加密解密),可参考[Mysql] CONVERT函数,MySQL ENCODE和DECODE加密列//语法:CONVERT(expr USING transcoding_name)
-- utf8mb4
SELECT CHARSET('ABC');
-- gbk
SELECT CHARSET(CONVERT('ABC' USING gbk));
//语法:CONVERT(expr,type)
-- 2022-05-25
SELECT CONVERT('2022-05-25', DATE);
-- 2022-05-25 17:58:48
SELECT NOW();
-- 2022-05-25
SELECT CONVERT(NOW(), DATE);
窗口函数:
FIRST_VALUE()
,LAST_VALUE()
分别获取分组后表的首行和末行数据),可参考MySQL FIRST_VALUE() 函数 | LAST_VALUE()函数 | NTH_VALUE()函数,row_number() over(partition by) 和 first_value over(partition by) 实用例子order by
,分组partition by
使用。RANK()
可以将数值映射跳跃间断的排名,会跳过重复的序号;而dense_rank()
不会跳过重复的序号),可参考Mysql常用函数之Rank 排名函数LAG()
和LEAD()
分别表示向下和向上偏移数据),可参考MYSQL lag() 和lead() 函数用法PERCENT_RANK()
和 小于或等于其RANK值的百分比CUME_DIST()
),可参考[Mysql] PERCENT_RANK()函数 | CUME_DIST()函数时间日期函数:
DAY
和DAYOFMONTH
),可参考MySQL日期时间操作函数(挺全的),MySQL常用的日期时间函数curtime()
和current_time()
)WeekDay(date_str)
值为0表示星期一),可参考MySQL weekday()函数TO_DAYS(date_str)
,可参考Mysql中TO_DAYS函数FROM_DAYS(days_count)
,可参考MYSQL的日期处理函数to_days()和from_days()EXTRACT(unit FROM date)
,可参考MySQL extract()函数unit
参数的有效间隔主要包括:DAY, DAY_HOUR, DAY_MICROSECOND, DAY_MINUTE, DAY_SECOND, HOUR, HOUR_MICROSECOND, HOUR_MINUTE, HOUR_SECOND, MICROSECOND, MINUTE, MINUTE_MICROSECOND, MINUTE_SECOND
extract(second from date)
相当于 second(date)
extract(minute from date)
相当于 minute(date)
extract(hour from date)
相当于 hour(date)
extract(day from date)
相当于 day(date)
extract(week from date)
相当于 week(date)
extract(month from date)
相当于 month(date)
extract(quarter from date)
相当于 quarter(date)
extract(year from date)
相当于 year(date)
UNIX_TIMESTAMP()
,可参考mysql的UNIX_TIMESTAMP 和 FROM_UNIXTIME用法MAKEDATE(year,n)
和MAKETIME(hour,minute,second)
date_format(date,format)
和time_format(time,format)
分别将日期和时间转字符串,可参考MySQL日期格式化,日期格式转换(str_to_date,date_format,time_format)group by
详解(可进行多个字段的分组,但非多次分组)name
和grade
看成一个整体,只要是name
和grade
相同的可以分成一组;如果只是name
相同,grade
不同就不是一组;而不是先进行name
分组,再进行grade
分组。COUNT()
,答案有误),可参考MySql统计函数COUNT详解(COUNT()
在开发中常用来统计表中数据,全部数据,不为NULL数据,或者去重数据)COUNT(1)
:统计不为NULL
的记录。COUNT(*)
:统计所有的记录(包括NULL
)。COUNT(字段)
:统计该"字段"不为NULL
的记录。not null
的话,一行行地从记录里面读出这个字段,判断不能为null
,按行累加。null
的话,判断到有可能是null
,还要把值取出来在判断一下,不是null
才累加。COUNT(DISTINCT 字段)
:统计该"字段"去重且不为NULL
的记录。SUM()
,配合group by
使用)min()
,配合group by
使用)max()
,配合group by
使用)avg()
,配合group by
使用)HAVING
可筛选聚合函数处理后的结果,可参考MySQL 中 having的用法where
字句在聚合前先筛选记录,执行顺序在group by
和having
字句前,因此where
不能筛选聚合函数值,即where salary = max(salary)
是错误的。having
子句在聚合后对组记录进行筛选,这些数据是通过一些聚合函数产生的://显示每个类型的用户数量.仅显示用户数量大于14的类型
mysql> select type,count(*) as count from ts_user group by type having count >14;
having
单独使用,与where
类似://查询用户id=1000的用户信息,查询结果一样
SELECT name,sex FROM `ts_user` having id = 1000 ;
SELECT name,sex FROM `ts_user` where id = 1000 ;
子查询(单列子查询包含多种操作符),参考【MySQL】子查询详解
带ANY
的子查询(外层查询满足内层查询结果中的一个即可),参考MySQL使用IN、EXISTS、ANY、ALL关键字的子查询
带ALL
的子查询(外层查询满足内层查询结果中的一个即可),参考MySQL使用IN、EXISTS、ANY、ALL关键字的子查询
带Exists
的子查询(内层查询不返回结果表,返回真假值),参考MySQL使用IN、EXISTS、ANY、ALL关键字的子查询
Note:
ANY
关键字表示满足其中任意一个条件。使用ANY
关键字时,只要满足内层查询语句返回的结果中的任意一个,就可以通过该条件来执行外层查询语句。//构造一个员工列表,排除每个部门最高工资的员工
select id, name, dept, salary
from employee as o
where o.salary < any(select salary from employee as i where i.dept=o.dept)
ALL
关键字表示满足所有条件。使用ALL
关键字时,只有满足内层查询语句返回的所有结果,才可以执行外层查询语句。// 找出所有其所在部门没有助理(post 为 assistant)的员工信息
select id, name, dept
from employee as o
where 'assistant' != all(select post from employee as i where o.dept = i.dept);
EXISTS
关键字时,内层查询语句不返回查询的记录。而是返回一个真假值。如果内层查询语句查询到满足条件的记录,就返回true
,否则false
。//找出所有其所在部门没有助理(post 为 assistant)的员工信息,使用exists实现
select id, name, dept
from employee as o
where not exists(select * from employee as i where o.dept = i.dept and post='assistant');
带IN
子查询(in
用于列表查询,不用select
),参考MySQL使用IN、EXISTS、ANY、ALL关键字的子查询
Note:只有子查询返回的结果列包含一个值时,比较运算符才适用。假如一个子查询返回的结果集是值的列表,这时比较运算符就必须用IN
运算符代替。
// 查询出研发部(dept为'rd')和人力资源部(dept为'hr')的员工列表
select id, dept, name, post
from employee
where dept in ('dev', 'hr');
列子查询(子表和主表的列匹配,当指定列唯一时,可以进行类似group by
的分组聚合)
// 在不使用group by下,统计每个部门的人数
select distinct(dept) as dept,
(select count(*)
from employee as i
where i.dept = o.dept) as emp
from employee as o;
参考MySQL中的内连接,左连接,右连接,全连接,交叉连接等相关总结
A join B on ...
,一般用于描述树级结构),例子如下://现有 node 表如下:
create table node(
id int primary key auto_increment,
pid int,
content varchar(256)
)
//想要给出 content 以 fork- 开头的所有节点,和它们的子节点,输出 parent_id, parent_content, child_id, child_content 。
select l.id as parent_id,
l.content as parent_content,
r.id as child_id,
r.content as child_content
from node as l
join node as r on l.id = r.pid
where l.content like 'fork-%';
A left join B on ...
的左表A
为基准),例子如下://列出所有的部门,如果这个部门有部门助理(post 为 assistant),则将 stuff 的名字也列出来
select d.id, d.name, e.name as assistant
from department as d
left join employee as e on e.dept = d.id
where e.post = 'assistant'
A right join B on ...
的右表B
为基准),例子如下://写一个查询,找出部门信息写错的员工,这些员工所在的部门在 department 表中没有对应记录
select e.id, e.name, e.dept
from department as d
right join employee as e on d.id = e.dept
where d.id is null;
cross join
(不带on
进行值比较)也称作笛卡尔积:左表中的所有行,左表中的每一行与右表中的所有行组合;实际中用的少)//Joe 需要生成 goods 表中所有T恤(category为T-Shirt)的所有尺寸,尺寸信息在 size 表
select g.id, g.name, s.name as size
from goods as g
cross join size as s
where g.category = 'T-Shirt';
//给出每个部门工资最高的员工的 id, name, dept, salary 四项信息
select l.id, l.name, l.dept, l.salary
from employee as l
join (select max(salary) as salary, dept
from employee
group by dept) as r
on l.dept = r.dept and l.salary = r.salary
这里注意,下面这种写法是错误的,group by
在对多字段分组时,是把多个字段看成一个整体,只有当多个字段相同时才分为一组,并不是多次分组:select id, name, dept, max(salary)
from employee
group by dept, id, name;
上面语句等价于对主键id
进行分组select id, name, dept, max(salary)
from employee
group by id;
InnoDB
默认对主键建立聚集索引;聚集索引可以应用于非自增的字段;索引可以基于一个或多个字段。order by age desc
,因为B+
索引树本身就是排好序的,所以再查询如果触发索引,就不用再重新查询了。alter table ... add index
或create index
;一个类别category_id
包含多种商品,因此对于goods
表,不能为goods
表创建关于category_id
的唯一性索引),可参考MySQL索引的创建与使用,别踩坑!使用MySQL唯一索引请注意ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL] INDEX | KEY [索引名] (字段名1 [(长度)] [ASC | DESC]) [USING 索引方法];
或者CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX 索引名 ON 表名(字段名) [USING 索引方法];
B+
树):UNIQUE
:可选。表示索引为唯一性索引。FULLTEXT
:可选。表示索引为全文索引。SPATIAL
:可选。表示索引为空间索引。INDEX
和KEY
:用于指定字段为索引,两者选择其中之一就可以了,作用是 一样的。ASC
:可选。表示升序排列。DESC
:可选。表示降序排列。alter table ... drop index
),可参考MySQL索引的创建与使用