数据库SQL与MySql
0.备注
本教程使用的是MySQL8.0.13社区版,使用内部的三个库:world, sys, sakila
回目录
1.数据库基础
1.主键:目的:用来唯一区分每一行的不同点(学生用id来区分,id是主键)
条件:
- 任意两行不具备相同的主键
- 每行有一个主键
要求:
- 主键键值能够唯一区分表中的每一行
- 每一行都应该有唯一标识自己的一列
- 表中任何列都可以作为主键
2.参考书籍:《mysql必知必会》作者 Ben Forta
3.在线教程:www.mysqltutorial.org
2.WorkBench介绍与Hello World
a) SELECT关键字
1.要点
- SQL不区分大小写,一般关键字用大写
- 结束SQL语句需要使用分号(;)
- SQL语句会忽略空格
- 使用limit 限制返回结果行数(LIMIT几乎永远放在最后面)
- 如果没有WHERE语句,则是随机取LIMIT个数
2.案例
从world库中的city表中随便取出10条数据
SELECT *
FROM world.city LIMIT 10;
3.了解SQL
a) USE关键字
- 指定工作目录,指定的是database(world),而不是table(city)(省的经常要写world.city)
#在当前的库中一直操作,不会跨库(优先执行指定的库,再执行工作空间)
USE sys; #以后的所有表都默认从sys库中先找,除非指定具体的库
SELECT *
FROM sys_config, world.city LIMIT 10;
b) SHOW关键字
- 显示当前数据库一共有多少个库(隐藏的库也能看到)
show databases;
- 显示指定数据库中的所有表
USE world;
SHOW tables;
- 显示一个表中的所有字段
SHOW columns from city;
4.数据排序
a) ORDER BY关键字
要求:
- order by 位于 from子句之后
- 最后用desc降序,asc 升序
代码:
- 仅对一个字段进行排序
USE world;
SELECT *
FROM city ORDER BY name DESC;
- 对两个字段进行排序,需要分组(先根据countrycode进行大体划分成小组排序,再根据前面的小组对population进一步排序)
USE world;
SELECT *
FROM city ORDER BY countrycode DESC ,population ASC;
5.过滤数据——WHERE
- WHERE关键字(在SELECT后,多个过滤子句配合AND / OR使用)
- WHERE子句中的圆括号决定了计算次序
a) AND关键词的使用
- 条件:筛选来自ARE地区人口在10w~30w之间的城市
SELECT *
FROM world.city
WHERE population >= 100000 and population <= 300000 and countrycode = "ARE";
b) OR关键词的使用
- 条件:筛选人口大于300w或者小于1000的城市
SELECT *
FROM world.city
WHERE population >=3000000 OR population <=1000;
c) AND和OR关键词的混合使用
- 条件:人口>10w并且小于50w,代码为AFG或者NLD
SELECT *
FROM world.city
WHERE (population >=100000 AND population <= 500000) AND (countrycode = "AFG" OR countrycode = "NLD");
d) IN
与NOT IN
的使用
- IN的使用
SELECT *
FROM world.city
WHERE (population >=100000 AND population <= 500000) AND countrycode IN ("AFG" , "NLD");
- NOT的使用
SELECT *
FROM world.city
WHERE (population >=100000 AND population <= 500000) AND countrycode NOT IN ("AFG" , "NLD");
6.通配符
a) %
-
“abc%”
以abc开头,任意字符结尾的数据 -
‘%abc’
以abc结尾,任意字符开头的数据 -
‘%abc%’
任意字符开头和结尾,中间包含abc -
案例:从city表中筛选出来以Ab开头的name值
USE world; SELECT * FROM city WHERE name LIKE "Ab%"
b) _
- 有几个下划线,后面就匹配几个字符
- 案例:找出以ab开头,但是后面只有三个字符的name字段
USE world;
SELECT *
FROM city
WHERE name LIKE "Ab___"
c) 注意
- 通配符一般在
WHERE
语句中使用,而且使用LIKE
作为操作符 - 字符串中其实是分大小写的比如
"Ab"
和"ab"
,但是能用"ab"
匹配"Ab"
是mysql配置的语法,而不是sql的语法,自动忽略了字符串的大小写 ~ 可以通过后面的修改 - 首尾有空格的时候,记得要将其算进去,就像"A C"用
“ac%”
是筛选不出来的,这个时候可以用Mysql的rtrim()
函数,这样就能保证处理数据的时候去掉了首尾的空格,但是仍然不能取A Coruña (La Coruña)
这样的数 - 注意NULL值,否则正常也是选取不出来的。~ 下面会讲的
案例:取出以Ac开头的name字段(包含首个字母是空格的情况)
USE world;
SELECT *
FROM city
WHERE name LIKE rtrim("Ac%");
7.数据处理函数
a) 文本处理函数
- 删除两侧空格(而非中间空格)
Rtrim()
~ 删除字符串右边空格ltrim()
~ 删除字符串左边空格
Length()
~ 计算字符串长度,可以在SELECT中使用,也可以在WHERE中使用- 将全部字符以大写 / 小写的形式展现出来
Upper()
~ 字符串大写Lower()
~ 字符串小写
substring(name,a,b)
函数 ~ 对name字段,从第a个位置开始,取b个长度(如果选取100超出字符串长度,则以字符串最大长度为基准)left()
/right()
函数 ~ 选取字段最左面 / 右面的前几个数据replace()
函数- 1.格式:
replace(str1,str2,str3)
- 2.用法:用str3替换str1字段中处出现的str2字符串
- 1.某个参数为NULL,则整体为NULL
- 2.如果没有str3,则str1中包含的str2会被删除
- 1.格式:
案例:
一、关于length( )
函数
1.每一条数据的name
字段的数值有多长?
SELECT id,name,length(name) as name_length
FROM world.city LIMIT 100;
2.name
字段长度≥10的才会被展现出来
SELECT id,name,length(name) as name_length
FROM world.city
WHERE length(name) >= 10;
但是,不可以像下面这么写,因为name_length
只是在展现的时候会显示出来,但是world.city
表中没有name_length
这个字段,因此需要直接写函数完整写法,不能写as后面的新命名名称
SELECT id,name,length(name) as name_length
FROM world.city
WHERE name_length >= 10;
二、关于substring()
函数 ~ 选取子集:只打印name
字段的前三个字母(从第一个开始算,往后数3个)
SELECT id, name, substring(name,1,3)
FROM world.city;
三、关于left()
和right()
函数
SELECT id,name,left(name,3)
FROM world.city;
四、关于upper()
和lower()
函数
SELECT id,name,upper(name) as upper_name
FROM world.city;
五、关于replace()
函数:将name
字段中的"am"替换成“xxoo”
SELECT id,name, replace(name,"am","xxoo")
FROM world.city;
b) 日期和时间处理函数
很多库中都会有日期和时间:订单什么时间发生的,数据是什么时候被记录在库中的
- 一、
curdate()
返回当前日期
SELECT curdate()
- 二、
curtime()
返回当前时间
SELECT curtime()
- 三、返回日期字段的具体部分
date()
、year()
、month()
、day()
、hour()
、minute()
、dayofweek()
案例一:
#先看一下有几个字段
SHOW columns from sys.sys_config;
#从set_time字段的时间戳找出日期部分
SELECT date(set_time)
FROM sys.sys_config;
案例二:
#找到sakila.film的时间戳字段,得到的是last_update
SELECT * FROM sakila.film
#从last_update字段中找到日期部分
SELECT date(last_update)
FROM sakila.film
例:选取出来2006年2月15号的数据
SELECT date(last_update)
FROM sakila.film
WHERE date(last_update) = "2006-02-15";
注意:遇到年月日这样需求的例子,条件中遇到年月日,对时间戳最好date()
一下,这样万无一失
例:选取2006-02-15到2006-02-28日的数据
SELECT date(last_update)
FROM sakila.film
WHERE date(last_update) >= "2006-02-15" and date(last_update) <= "2006-02-28";
当然也可以用提取时间戳具体元素来实现
SELECT date(last_update)
FROM sakila.film
WHERE year(last_update) = '2006'
and month(last_update) = '02'
and day(last_update) between '15' and '28';
或者
SELECT date(last_update)
FROM sakila.film
WHERE year(last_update) = '2006'
and month(last_update) = '02'
and day(last_update)>=15 and day(last_update)<=30;
- 四、比较两个时间的差
datediff()
,返回天数(当前时间-last_update)
SELECT datediff(now(),last_update)
FROM sakila.film;
8.汇总函数
主要就是求最值、均值、求和、统计个数(以上所有的函数都不会对NULL值进行操作)
a) 返回单一结果
- 求出
world.city
表中population
中的最大值
SELECT max(population), min(population), avg(population), sum(population), count(population)
FROM world.city;
- 计算表中一共有多少条数据(如果表中有NULL值,则结果可能和count(population)会不一样)
SELECT count(*)
FROM world.city;
b) 返回分类的唯一值:DISTINCT
(只能跟在SELECT后面,而且是查找后面所有列的唯一值,如:九坤出的如何保证再加进来的值不是重复的,如果第一行是abc,第二行还是abc,那么两行就会被DISTINCT成一个结果)
- 看countrycode中有哪些值?
SELECT DISTINCT countrycode
FROM world.city;
- 查看countrycode下面有多少个唯一值
SELECT count(DISTINCT countrycode)
FROM world.city;
9.分组函数
- 正常统计是按照每个分类进行计算(求和/平均),比如对班级成绩求平均:英语、语文、数学(先分组再平均)。使用
group by
进行分组,使用having
代替WHERE
对分组进行过滤(现在应用的每一个函数,都是对group by
小组的实施的,而不是对全体数据,也不是对单个样本)
案例:
a) 单一分组
- 对每个
countrycode
求population
求平均
SELECT countrycode,avg(population) as avg_population, sum(population) as sum_population
FROM world.city
GROUP BY countrycode;
- 千万不要这么写。有结果,但是是错的(因为id没有被countrycode分组,不应该出现在SELECT中),运行成功不代表分组没有错误(mysql可能有将一个组内随机取出来一个数,取第一个标号,取最后一个标号这样的机制。)
SELECT id,countrycode,avg(population) as avg_population, sum(population) as sum_population
FROM world.city
GROUP BY countrycode;
b) 多个分组
- 多个分组(两个组合起来才是唯一值)
SELECT countrycode,district,avg(population) as avg_population, sum(population) as sum_population
FROM world.city
GROUP BY countrycode,district;
c) 条件过滤having
- 只展显avg(population)>1000000的(WHERE是针对每一条样本,HAVING作用在每一个GROUP上)
SELECT countrycode,district,avg(population) as avg_population, sum(population) AS sum_population
FROM world.city
GROUP BY countrycode,district
HAVING avg(population)>=1000000 and (sum(population))>=1000000;
10.子查询 / 嵌套查询
- 会有非常多的复杂的需求需要用到子查询(即将之前列出来的SQL语句变成一个临时的新表,被上一层的查询调用)
- 子查询可以出现在
FROM
和WHERE
中
案例:
a) 用在FROM
后
- 1.用
WHERE
语句查询平均人口大于100w的语句
SELECT *
FROM
(SELECT countrycode,district,avg(population) as avg_population, sum(population) AS sum_population FROM world.city GROUP BY countrycode,district) AS a
WHERE a.avg_population >= 1000000;
或者省略AS
SELECT *
FROM
(SELECT countrycode,district,avg(population) as avg_population, sum(population) AS sum_population FROM world.city GROUP BY countrycode,district) a
WHERE a.avg_population >= 1000000;
b) 用在WHERE
后
- 2.查询
population
大于avg_population
的所有数据(用countrycode
分组后,但是这样查询不到结果,应该让population逐条比较)
SELECT *
FROM world.city
WHERE population >= (SELECT avg(population) FROM world.city GROUP BY countrycode)
正确做法:去掉GROUP BY
SELECT *
FROM world.city
WHERE population >= (SELECT avg(population) FROM world.city)
由此注意:WHERE
中可以用子查询,但是子查询中比较的数量要一致,前面是一条population
,后面就不应该是一个分组后的population
,这是多个结果
11.表联结
-
作用:将同一个库里的多张表进行联结,用
SELECT
选取数据。一共有三种INNER JOIN
(交集)、LEFT JOIN
(左面为主)、RIGHT JOIN
(右面为主) -
例如:world库中,有
city
、country
、countrylanguage
三张表,我想知道每个city
中的人说什么语言,就用city
、countrylanguage
这两个表相联结 -
一般通过主键进行表的联结,但是也可以通过任何列进行两两联结,一般通过两两相同的列组合(主键:唯一不重复的值)
- 如何查看主键(看到Key上有PRI字样的就是主键)
SHOW columns FROM world.country
案例
-
通过
countrycode
和code
进行组合-
以下方式很老,不推荐!
USE world; SELECT city.*, country.* FROM city,country WHERE city.countrycode = country.code;
-
a) inner join
-
可阅读性强的代码
SELECT a.*, b.* FROM 临时表 a 组合方式 inner join 临时表 b 组合联结处 on
-
组合两张表的方法
USE world; SELECT a.*, b.* FROM (SELECT * FROM city) a inner join (SELECT * FROM country) b on a.countrycode = b.code
-
组合三张表的方法(
inner join
)USE world; SELECT a.*, b.*, c.language FROM (SELECT * FROM city) a inner join (SELECT * FROM country) b on a.countrycode = b.code inner join (SELECT * FROM countrylanguage) c on c.countrycode = b.code #inner join的话写a写b都无所谓,因为是共同出现,但是left join和right join就不一样了,左面/右面出现的都会有,另一面就没有
b) left join
-
组合三张表(
left join
)USE world; SELECT a.*, b.*, c.language FROM (SELECT * FROM city) a left join (SELECT * FROM country) b on a.countrycode = b.code left join (SELECT * FROM countrylanguage) c on c.countrycode = a.countrycode; #left join一般写前面/左面的临时表进行联结
-
检查
left join
是否对齐count(*)
——计算返回的条数(正常情况是不会对齐的:a是学生信息表,b是学生成绩表,但是有两个没来考试),用LIMIT
随机展现出来100条inner join
~ 挑选共同出现的那一块(共1861条结果)
SELECT count(*) FROM (SELECT * FROM city) a inner join (SELECT * FROM country LIMIT 100) b on a.countrycode = b.code;
left join
~ 会将a出现的都展现出来(共4079条结果)
SELECT count(*) FROM (SELECT * FROM city) a left join (SELECT * FROM country LIMIT 100) b on a.countrycode = b.code;
c) 对于NULL值
- 显示具体数据,看到有的是NULL数值(NULL值会被忽略计算)
SELECT a.*,b.* FROM (SELECT * FROM city) a left join (SELECT * FROM country LIMIT 100) b on a.countrycode = b.code;
- 这时再来统计一下:看到
count(*)
=4079,而count(b.code)
=1861,因为有的b.code
是NULL值,因此left join
统计结果会出现差异,而inner join
会取两者相同部分,所以都是1861条数据
SELECT count(*), count(b.code) FROM (SELECT * FROM city) a left join (SELECT * FROM country LIMIT 100) b on a.countrycode = b.code;
-
作业:
OUT JOIN
12.组合查询-UNION
- 作用:将使用不同条件的多个查询结果组合在一起,返回在一个表中(几个表的结果是上下连接在一起的)
- 原则:
- 必须由两条或者两条以上的select语句组成,语句之间用union分隔
- union中的每个查询必须包含相同的列,表达式或聚集函数sum(), avg()
- 列数据类型必须兼容
a) 单一组合
SELECT id,name FROM world.city WHERE population >= 1000000
union
SELECT id,name FROM world.city WHERE population <= 100000
注意:
- SELECT返回的字段一定要是一模一样,不可以如下(应该是同一个表中同一个字段,严格意义讲:只要返回的数据类型是一致的,就不会报错,但是最后返回的东西就会很尴尬,究竟是population还是countrycode,还是district。)
SELECT id,name,population FROM world.city WHERE population >= 1000000
union
SELECT id,name,countrycode FROM world.city WHERE population <= 100000
union
SELECT id,name,district FROM world.city WHERE population <= 500000
b) 去重与不去重union all
-
union会自动对数据进行去重,如果不需要请使用union all
- 以下操作返回3562条(上面238条,下面3562条,上面的值被包含在下面的值中)
SELECT id,name FROM world.city WHERE population >= 1000000 union SELECT id,name FROM world.city WHERE population >= 100000
- 以下会返回两个结果的加和(238+3562=3800)
SELECT id,name FROM world.city WHERE population >= 1000000 union all SELECT id,name FROM world.city WHERE population >= 100000
c) 结果排序
-
对展现结果进行排序——order by只能用于最后一条select语句
SELECT id,name FROM world.city WHERE population >= 1000000 union all SELECT id,name FROM world.city WHERE population >= 100000 ORDER BY id DESC;
13.表的创建与操作
- 将之前操作返回的结果保存下来,需要创建一张表进行保存
- 格式
create table 库名.表名
as
要返回的操作结果
注意
- 1.字段名重复 ~ 需要将重复字段名逐个手写更正(访问结果字段名可以重复,存表时需遵守规范:字段名不能重复)
SELECT a.name as name_city, a.countrycode as code_city, a.population as population_city;
a) 将查询结果存入表(创建临时表)
案例
- 将之前联结的结果保存在一个表中
USE world;
CREATE TABLE if NOT exists world.test1
AS
SELECT a.id, a.name as name_city, a.countrycode as code_city, a.district, a.population as population_city, b.*, c.*
FROM
(SELECT * FROM city) AS a
LEFT JOIN
(SELECT * FROM country LIMIT 100) AS b
ON a.countrycode = b.code
LEFT JOIN
(SELECT * FROM countrylanguage) AS c
ON c.countrycode = a.countrycode;
b) 查询临时创建表
- 可以当做一个正常的表来查询结果
SELECT * FROM test1 LIMIT 100
c) 删除临时创建表
- 删除表(用过 / 创建错误)
drop table if exists world.test1
d) 创建最底层表
-
需要指定
-
表的名字
-
列的名字,列的数据类型
-
是否允许NULL值
-
哪些列是主键?(唯一且不重复的一列)【可以没有主键,主键也可以用多个字段名来设置】
CREATE TABLE IF NOT exists world.testa (id int, name char(15), primary key(id,name) )
-
列与列之间用什么分隔符来分割
-
-
需要注意
- 创建表之前要安排和布局好结构,想好这个表是否是自己想要的,以免后期修改或者删除
- 规范:创建好了表,就不要随意修改和丢弃(因为mysql的操作不可逆,丢弃了就找不回来了)
#需求:创建一个表,以id,name,countrycode,population,gnp,language为字段,以id为主键 CREATE TABLE IF NOT exists world.test2 (id int, name char(15), countrycode char(5), population int, gnp float, language char(15) )
e) 插入查询结果——insert
-
向上面的
test2
表中插入查询结果(返回的字段数要和定义的字段数相同)USE world; INSERT INTO world.test2 SELECT a.id, a.name as name_city, a.countrycode as code_city, a.population, b.gnp, c.language FROM (SELECT * FROM city) AS a LEFT JOIN (SELECT * FROM country LIMIT 100) AS b ON a.countrycode = b.code LEFT JOIN (SELECT * FROM countrylanguage) AS c ON c.countrycode = a.countrycode;
14.更新数据——ALTER
- 可以修改字段名,也可以修改字段类型
- 注意,如果不加
WHERE
语句,就是对所有的行进行insert
、update
、delete
(一定要附加上WHERE
条件,否则就是对全表进行修改)
a) 增加字段名
ALTER TABLE world.test2
add new_name char(80);
SELECT * FROM world.test2;
b) 删除一个字段
ALTER TABLE world.test2
drop gnp;
SELECT * FROM world.test2;
c) 根据条件更改某行值(语法没有问题,但会被警告没有权限)
-
修改非常有风险,一般认为修改库不是去做好事,一般人也没有delete和update的权限
-
在使用where条件前先使用select进行测试,保证正确无误,因为Mysql是没有撤销功能的
UPDATE world.test2
SET population = 0
WHERE id = 129;
再例如
UPDATE world.test2
SET new_name = 1
WHERE new_name is NULL;
d) 根据条件删除行值
DELETE FROM world.test2
WHERE new_name is NULL;
注意:DELETE
和DROP
的区别
- delete是删除记录,但是表还在,表示空表
- drop是删除整个表,整个表都不存在了
15.NULL值
- 使用格式:
is null / is not null
a) 筛选不是NULL值的数
案例:筛选出来不是NULL值的数
SELECT *
FROM world.test2
WHERE new_name IS NOT NULL;
b) 创建表的时候,不允许是NULL值
CREATE TABLE IF NOT exists world.test2
(id int not null,#不允许插入NULL
name char(15) null,#允许插入NULL
countrycode char(5),
population int,
gnp float,
language char(15)
)