MySql
分类 | 全称 | 说明 |
---|---|---|
DDL | Data Definition Language | 数据定义语言,用来定义数据库对象,如:数据库、表、字段等等 |
DML | Data Manipulation Language | 数据操作语言,用来对数据表中的数据进行增、删、改 |
DQL |
Data Query Language |
数据查询语言,用来查询 数据库中表的记录 |
DCL | Data Control Language | 数据控制语言,用来创建数据库用户、控制数据库的访问权限 |
对于正常数据库操作来说,查询的数据的频率,远远超过其他操作。本文会由渐入深的介绍常用的查询方法。
语法为:
SELECT
字段列表
FROM
表名字段
WHERE
条件列表
GROUP BY
分组字段列表
HAVING
分组后的条件列表
ORDER BY
排序字段列表
LIMIT
分页参数
执行顺序为: FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORGDER BY -> LIMIT
查询
先造数据
CREATE TABLE `tb_user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT'' COMMENT'用户昵称',
`email` VARCHAR(50) COMMENT'用户邮箱',
`phone` VARCHAR(20) DEFAULT'' COMMENT'手机号',
`gender` TINYINT(4) UNSIGNED DEFAULT '0'COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) COMMENT '密码',
`age` TINYINT(4) DEFAULT'0' COMMENT '年龄',
`class` TINYINT(4) DEFAULT'0' COMMENT '班级',
`card` bigint(20) DEFAULT'0' COMMENT '身份证',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8 COMMENT = 'app用户表';
来个十万条数据
-- 插入10万数据.
DELIMITER $$
-- 写函数之前必须要写,标志
CREATE FUNCTION mock_data4 ()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 100000;
DECLARE i INT DEFAULT 0;
WHILE i<num DO
INSERT INTO `tb_user`(`name`,`email`,`phone`, `gender`,`password`, `age`, `class`,`card`)
VALUES(
CONCAT('用户',i+1),
CONCAT(FLOOR(RAND()*999999999),"@qq.com"),
CONCAT('13',CEILING(RAND()*900000000+100000000)),
FLOOR(RAND()*2),
CONCAT('pwd',FLOOR(RAND()*999999999)),
FLOOR(RAND()*50),
FLOOR(RAND()*100),
CONCAT('4310281990',FLOOR(RAND()*99999999))
);
SET i=i+1;
END WHILE;
RETURN i;
END;
SELECT mock_data4() -- 执行此函数 生成十万条数据
基本查询
查询多个字段
select name,email,age from tb_user;
查询全部字段
select * from tb_user;
注意:在实际开发中,不要查询全部字段,按照自己的业务逻辑去查询对应的字段,因为查询全部字段会影响效率,哪怕你的表结构只有几个字段,但是无法保证后续迭代中不会加,这样你就把多余字段查询出来,浪费性能。
别名 as
select name as '姓名' from tb_user;
也可以不带as,建议带as
select name '姓名',age '年龄' from tb_user;
去重查询
select distinct name '姓名' from tb_user;
条件查询
SELECT 字段列表 FROM 表名 WHERE 条件列表;
比较运算符 | 功能 |
---|---|
> | 大于 |
>= | 大于等于 |
< | 小于 |
<= | 小于等于 |
= | 等于 |
<> 或 != | 不等于 |
BETWEEN ... AND ... | 在某个范围内(含最小、最大值) |
IN(...) | 在in之后的列表中的值,多选一 |
LIKE 占位符 | 模糊匹配(_匹配单个字符,%匹配任意个字符) |
IS NULL | 是NULL |
逻辑运算符 | 功能 |
---|---|
AND 或 && | 并且(多个条件同时成立) |
OR 或 || | 或者(多个条件任意一个成立) |
NOT 或 ! | 非,不是 |
-- 年龄等于30
select * from tb_user where age = 30;
-- 没有身份证
select * from tb_user where card is null;
-- 有身份证
select * from tb_user where card is not null;
-- 年龄在20到30之间
select * from tb_user where age >=20 and age <=30;
select * from tb_user where age between 20 and 30;
-- 年龄等于25或30或35,且性别为男
select * from tb_user where age in (20,30,40) and gender = 0;
-- 姓名为三个字
select * from tb_user where name like '___';
-- 身份证最后为1
select * from tb_user where card like '%1';
select * from tb_user where card like '________________1'; # 这个有点蠢了
聚合函数
聚合函数,都是作用于某一列,将一列数据作为一个整体,进行纵向计算。
语法: SELECT 聚合函数(字段列表) FROM 表名;
注意:所有的null值,不参与聚合函数的运算
常见的如下:
函数 | 功能 | 例子 |
---|---|---|
count | 统计数量 | 统计所有人员数量: select count(id) from tb_user; |
max | 最大值 | 统计所有人员的平均年龄:select avg(age) from tb_user; |
min | 最小值 | 统计最大年龄:select max(age) from tb_user; |
avg | 平均值 | 统计最小年龄:select min(age) from tb_user; |
sum | 求和 | 统计所有人员年龄之和:select sum(age) from tb_user; |
分组查询
语法:SELECT 字段列表 FROM 表名 [ WHERE 条件 ] GROUP BY 分组字段名 [ HAVING 分组后的过滤条件 ];
where 和 having 的区别:
- 执行时机不同:where是分组之前进行过滤,不满足where条件不参与分组;having是分组后对结果进行过滤。
- 判断条件不同:where不能对聚合函数进行判断,而having可以。
注意:
- 执行顺序 : where > 聚合函数 > having
- 分组之后,查询的字段一般为聚合函数和分组字段,查询其他字段无任何意义,且有可能会报错
# 根据性别分组,统计男和女用户的数量
select gender,count(*) from tb_user group by gender;
# 根据性别分组,统计男、女用户的平均年龄
select gender,avg(age) from tb_user group by gender;
# 查询小于年龄45的用户,并根据班级分组,并获取班级序号小于10数据,且班级的人生要大于900人。
select class,count(*) as classNum from tb_user where age < 45 group by class having class<10 and classNum>900;
排序查询
日常开发中,这个比较常见的
语法:SELECT 字段列表 FROM 表名 ORDER BY 字段1 排序方式1, 字段2 排序方式2;
如果多字段排序,当第一个字段值相同时,才会根据第二字段排序。
排序方式:ASC: 升序(默认,可以省略),DESC: 降序
# 查询用户年龄大于20,且根据年龄进行升序
select name,age from tb_user where age >20 order by age asc;
# 查询女性用户,并根据创建时间倒序
select name,gender,create_time from tb_user where gender = 0 order by create_time desc ;
# 根据年龄进行升序,年龄相同再按创建时间倒序。
select name,age,create_time from tb_user order by age asc , create_time desc;
分页查询
这个,在开发中也是常用的,虽然都是使用框架,但有必要了解下。
语法:SELECT 字段列表 FROM 表名 LIMIT 起始索引, 查询记录数;
注意:
- 起始索引从0开始,起始索引算法为: (查询页码-1) * 每页记录数 ,如每页显示10条,那么第二页的起始索引就是 (2-1) * 10,则索引为10。
- 查询是从第一页开始,起始索引可以省略。
# 查询第一页用户数据,每页展示30条记录,
select * from tb_user limit 0,10;
#或者
select * from tb_user limit 10;
# 查询第10也用户数据,每页展示20条记录
select * from tb_user limit 180,20;
函数
如同刚刚的聚合函数一样,也是函数:一段可以直接被另一段程序调用的程序或代码。
mysql的函数,常用的包含为:字符串函数、数值函数、日期函数、流程函数。
字符串函数
常用的如下,如果有java或者其他语言的基础,这些函数名应该不陌生:
函数 | 功能 | 例子 |
---|---|---|
CONCAT(s1, s2, ..., sn) | 字符串拼接,将s1, s2, ..., sn拼接成一个字符串 | select concat('hello','world'); //helloworld |
LOWER(str) | 将字符串全部转为小写 | select lower('HELLO'); // hello |
UPPER(str) | 将字符串全部转为大写 | select upper('hello'); // HELLO |
LPAD(str, n, pad) | 左填充,用字符串pad对str的左边进行填充,达到n个字符串长度 | select lpad('01',5,'-'); // ---01 |
RPAD(str, n, pad) | 右填充,用字符串pad对str的右边进行填充,达到n个字符串长度 | select rpad('01',5,'-'); // 01--- |
TRIM(str) | 去掉字符串头部和尾部的空格 | select trim(' h ello ');// h ello |
SUBSTRING(str, start, len) | 返回从字符串str从start位置起的len个长度的字符串 | select substring('hello world',0,5); // hello |
REPLACE(source,column, replace) | 替换字符串 | select replace('hello','e','a');// hallo |
除了查询,在更新的时候也可以利用函数,比如把所有的手机号码前面都改为+86。
update tb_user set phone = lpad(phone,14,'+86')
数值函数
常见函数:
函数 | 功能 | 例子 |
---|---|---|
CEIL(x) | 向上取整 | select ceil(1.1); // 2 |
FLOOR(x) | 向下取整 | select floor(1.1); // 1 |
MOD(x, y) | 返回x/y的模 | select mod(9,4); // 1 |
RAND() | 返回0~1内的随机数 | select rand(); // 随机小于1的浮点数 |
ROUND(x, y) | 求参数x的四舍五入值,保留y位小数 | select round(2.35,1); // 2.4 后面一位四舍五入 |
使用函数生成六位的随机码
select lpad(round(rand()*1000000,0),6,0);
日期函数
常用函数:
函数 | 功能 | 例子 |
---|---|---|
CURDATE() | 返回当前日期 | select curdate(); // 2023-09-19 |
CURTIME() | 返回当前时间 | select curtime();// 14:15:52 |
NOW() | 返回当前日期和时间 | select now(); // 2023-09-19 14:16:00 |
YEAR(date) | 获取指定date的年份 | select year(now()) ;//2023 |
MONTH(date) | 获取指定date的月份 | select month('2023-08-19'); // 8 |
DAY(date) | 获取指定date的日期 | select day('2023-09-19'); //19 |
DATE_ADD(date, INTERVAL expr type) | 返回一个日期/时间值加上一个时间间隔expr后的时间值 | select date_add(now(),interval 70 day );// day/year/month... |
DATEDIFF(date1, date2) | 返回起始时间date1和结束时间date2之间的天数 | select datediff('2021-12-01','2022/12/01'); // -365 |
查询所有用户的注册天数,并且安装天数倒序
select name,datediff(curdate(),create_time) as entry_days from tb_user order by entry_days;
流程函数
常见的一种函数,在sql语句中实现条件筛选,从而提高语句的效率。
常用函数:
函数 | 功能 |
---|---|
IF(value, t, f) | 如果value为true,则返回t,否则返回f |
IFNULL(value1, value2) | 如果value1不为空,返回value1,否则返回value2 |
CASE WHEN [ val1 ] THEN [ res1 ] ... ELSE [ default ] END | 如果val1为true,返回res1,... 否则返回default默认值 |
CASE [ expr ] WHEN [ val1 ] THEN [ res1 ] ... ELSE [ default ] END | 如果expr的值等于val1,返回res1,... 否则返回default默认值 |
举例
- if
select if (false,'ok','error');// error
select if(true,'ok','error');//ok
- if null
select ifnull('ok','defalut'); // ok
select ifnull(null,'default');// default
select ifnull('','defalut');// '',因为空字符串非null
- case
查询用户名称、手机号,如果手机号是138开头,则为移动,如果是139或者137开头 则为联通,其他为电信
select
name,
phone,
case
when phone like '+86138%' then '移动'
when phone like '+86139%' or phone like '+86137' then '联通'
else '电信' end as service
from tb_user;
- case expr
查询用户表,如果gender为0显示男,1显示女,且age如果<15为少年,15<age<20为青少年,age>21为青年
select
name,
case gender when 0 then '男' when 1 then '女' end as 'sex',
case when age<15 then '少年' when 15<age or age<20 then '青少年' else '青年' end as 'group',
age
from tb_user;
多表查询
常用的查询,一般分为三种:一对一、一对多、多对多。
一对多
一般是在多的一方建立外键,指向一的一方的主键。
多对多
多对多,需要建立第三张中间表,中间表至少包含两个外键,分别关联两方主键。
一对一
一对一,通常用户做单表的拆分,如用户和用户详情的关系。
任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE),一般都是在旁支表,比如用户表User,和用户扩展表extend,则在extend中加入user_id。
表结构为:
create table tb_person
(
id bigint auto_increment
primary key,
name varchar(255) not null,
age int null,
email varchar(255) not null,
status int default 1 null,
gender int null,
dept_id int null,
leader_id int null comment '领导id',
constraint email
unique (email),
constraint tb_person_dept__fk
foreign key (dept_id) references tb_dept (id)
)
comment '人员表' charset = utf8;
create table tb_dept
(
id int auto_increment
primary key,
name varchar(50) not null
)
comment '部门' charset = utf8;
笛卡尔积
如我们查询select * from tb_person,tb_dept;
,则会查出来的数据是 tb_person表的所有数据N和tb_dept的所有数据M的乘积。NxM
笛卡尔积:两个集合N集合和M集合的所有组合情况(在多表查询时,需要消除无效的笛卡尔积)
消除无效的笛卡尔积,则只是需要把关联关系执行起来:select * from tb_person, tb_dept where tb_person.dept_id = tb_dept.id;
内连接
内连接查询的是两张表交集的部分。包含了两种类型链接:
隐式内连接
SELECT 字段列表 FROM 表1, 表2 WHERE 条件 ...;
select * from tb_person, tb_dept where tb_person.dept_id = tb_dept.id;
显示内连接
显示内连接性能较高,在工作用也比较常用。
SELECT 字段列表 FROM 表1 JOIN 表2 ON 连接条件 ...;
select p.name,d.name from tb_person p join tb_dept d on p.dept_id=d.id;
外连接
左外连接
相当于查询表1(左表)的所有数据以及包含 表1和表2的交集部分数据。
SELECT 字段列表 FROM 表1 LEFT JOIN 表2 ON 条件 ...;
-- 查询person表的所有数据以及对应的部门信息。 select p.*,d.name from tb_person p left join tb_dept d on p.dept_id = d.id;
在实际开发中,左外连接会使用的更多一点。
右外连接
相当于查询表2(右表)的所有数据以及包含 表1和表2的交集部分数据。
SELECT 字段列表 FROM 表1 RIGHT JOIN 表2 ON 条件 ...;
-- 查询人员和部门的所有信息 select d.*,p.* from tb_person p right join tb_dept d on p.dept_id = d.id;
自连接
自连接查询,可以是内连接查询,也可以是外连接查询。
自连接,看成两张表更加容易理解。
SELECT 字段列表 FROM 表A 别名A JOIN 表A 别名B ON 条件 ...;
```sql
-- 查询员工表以及员工的leader信息,如果员工没有leader,则不需要查询。(如果没有leader也需要查询出来,使用外连接left join即可)
select p1.id,p1.name,p1.leader_id,p2.name leadname from tb_person p1 join tb_person p2 on p1.leader_id=p2.id;
```
联合查询 union
对于union查询,就是把多次查询的结果合并起来,形成一个新的查询结果集。
SELECT 字段列表 FROM 表A ... UNION [ALL] SELECT 字段列表 FROM 表B ...
-- 查询年龄大于40和性别为男的人员,并且去重
select * from tb_person where age >=40
union -- 如果不去重则使用union all, 这样可能会有重复的数据
select * from tb_person where gender = 1;
注意:使用union 必须查询的字段要完全一致,如一个是*,一个只是查询name,这两个合并则会报错。
嵌套查询(子查询)
sql语句中嵌套select语句,成为嵌套查询,也成为子查询。
SELECT * FROM t1 WHERE column1 = ( SELECT column1 FROM t2);
子查询的外部语句可以是 insert、update、delete、select中的任何一个。一般常见的就是select。
根据子查询的结果不同,可以分为:
标量子查询(子查询的结果为单个值)
常用的操作符:=、<>、>、>=、<、<=
-- 查询部门为研发部的人员 select * from tb_person where tb_person.dept_id = (select id from tb_dept where tb_dept.name='研发部');
列子查询(子查询的结果为一列)
返回的结果是一列,可能是多行。这种子查询成为列子查询。
常用的操作符:操作符 描述 IN 在指定的集合范围内,多选一 NOT IN 不在指定的集合范围内 ANY 子查询返回列表中,有任意一个满足即可 SOME 与ANY等同,使用SOME的地方都可以使用ANY ALL 子查询返回列表的所有值都必须满足 -- 查询部门为销售部和研发部的所有员工信息 select * from tb_person where tb_person.dept_id in (select id from tb_dept where tb_dept.name='研发部' or tb_dept.name='市场部');
-- 查询比研发部所有人年龄都大的人员(如果是任意一人,则把all换成any) select * from tb_person where age > all(select age from tb_person where tb_person.dept_id = (select id from tb_dept where tb_dept.name='研发部'));
行子查询(子查询的结果为一行)
子查询返回的结果是一行,可以是多列,这种子查询为行子查询.
常用的操作符为 =、<>、IN、NOT IN-- 查询和zhangsan的age和status一样的人员 -- where后面可以加括号括起来如: select * from tb_person where (age, status) = (1,1); -- 那么子查询就是 select * from tb_person where (age, status) = (select age, status from tb_person where name = 'zhangsan');
表子查询(子查询结果为多行多列)
子查询返回的结果为多行多列,为表子查询
常用的操作符:IN-- 查询与zhangsan/lisi的age和status一样的人员信息 select * from tb_person where (age, status) in (select age, status from tb_person where name = 'zhangsan' or name='lisi');
-- 查询年龄大于30员工,以及部门信息 -- 可以先把年龄大于30的查询后作为一个新的表,再新的表里面再做查询 select * from (select * from tb_person where age > 30) as t left join tb_dept as d on t.dept_id=d.id;