还在更新中,如果有错误欢迎指出,参考了 Oracle手册,数据库系统概念第6版,部分博客 和 教授上课讲述 的内容
目录
前言:
数据库是学业课,同时竟然爬虫也学了,那么爬取大量数据时用来管理和处理海量数据的数据库怎么能少了呢,所以写一篇关于SQL语言的学习笔记,可能部分语法在不同的商业数据库软件有些出入,不过大部分应该是统一的,本文语法大部分以 Oracle 或 mySQL 为准(当然主要应该还是 mySQL),在不同的地方会分别给出属于不同数据库的不同写法
说到数据库,最常见的就是关系数据库了,SQL关系数据库提供了 4 类语言供用户使用和修改数据库,分别是:
- DDL(Data Definition Language)数据定义语言
- DML(Data Manipulation Language)数据操纵语言
- DCL(Data Control Language)数据控制语言
- DQL(Data Query Language) 数据查询语言
其中很多分类把 DQL 和 DML 合在一起称为 DML ,那样就是 3 类
也有人在这三类基础上又加上了 TCL —— 事务控制语言,那还是 4 类
那么为什么要把SQL语言分类?
因为这些语言使用的人群不同,一个数据库需要有程序员为他写一个对外界用户交互的程序(或者说前端),这个时候程序员接触到的更多的是 DML 和 DQL,就是说只需要查询和适当的修改数据库里的数据即可;当然一个数据库还需要一个超级管理员来维护,这个时候超级管理员就可以接触到 DCL 和 DDL,用来修改数据库和分配权限;最后如果一个数据库没有前端,直接让外部用户来操作的话,那我们期望的就是让用户使用一部分 DQL ,仅提供部分查询功能供用户使用
另外就是关系数据库实际上是由很多个表构成的,所以我们大部分操作围绕 关系 和 关系的集合——表 进行,作为前驱知识可能会用到 实际上并不会用到 关系代数
DDL:数据定义语言
1. 建表操作 create :
表 (table) 作为关系数据库的基础,几乎所有操作都围绕表进行,对此,应先建立一个关系表来存储数据
create table <table_name>(
<属性名_1> <type_1>,
<属性名_2> <type_2>,
...
<属性名_n> <type_n>,
<完整性约束_1>,
...
<完整性约束_n>
);
其中属性名的类型有以下几种:
以 SQL Server 为准:
- char(n): 最多 8000 个字符,char 前加 n 可以存汉字,最多 4000
- varchar(n): 最多 8000 个字符,则被转换为 text 类型,加 n 同上
- int:范围 -2147483648 到 2147483647,如果加括号,括号内是允许出现的最大位数
- smallint: 范围 -32768 到 32767,加括号同上
- numeric(p,d): 固定精度和比例的数字,允许从
−
1
0
38
−
1
-10^ { 38 }-1
−1038−1 到
1
0
38
−
1
10^ { 38 }-1
1038−1 之间的数字。
p 表示可以存储的最大位数(小数点左+右的位数)而且 p 必须是 1 到 38 之间的值;默认是 18
s 表示小数点右侧最大位数,而且 s 必须是 0 到 p 之间的值;默认是 0 - float(n) :精度为 n 位的浮点数,范围 − 1.7 9 308 -1.79^ { 308 } −1.79308 到 1.7 9 308 1.79^ { 308 } 1.79308 的浮动精度数字数据
其它数据库的可以查看 SQL数据类型
另外如果想约束一个属性值不能为空,可以在后面加上 not null 限定
更多的完整性约束语句待更
create table Table1(
name varchar(8) not null,
age int,
id nchar(18) not null,
primary key (id) //选取 id 作为主键
);
2. DataBase 操作:
用来创建和使用数据库
create database <database_name>; //创建一个名为 database_name 的数据库
use <database_name>; //使用这个数据库
3. 删除操作 drop :
还有一个删除操作 delete 属于 DML
delete 比 drop 弱一点
delete from <table_name>; //会删除表中所有关系,保留一个空表
drop table <table_name>; //直接删掉表
4. 修改属性操作 alter :
alter table <table_name> add <属性名> <type>; //将会在表后添加一列属性
alter table <table_name> drop <属性名>; //删除那一列属性
DML:数据操纵语言
1. 插入操作 insert :
在表中插入一条数据,可以有空值
insert into <table_name> values (<信息_1>, <信息_2> ... <信息_n>);
//例子
insert into Table1 values ("李三", 24, "114514");
//注意顺序要跟建表属性顺序一致
insert into Table1 values ("李三", null, "114514");
insert into Table1 (name, id, age) values ("张四", "415411", 42);
//或者指定顺序,比较麻烦
另外值得注意的是在 mySQL 里字符串用双引号是没问题的,但在 Oracle 中需要用单引号(双引号另有别的意思)
建议看完这个直接去看 select (DQL),然后回来看 update
//在 insert 中也可以使用 select 语句
insert into Table1 select * from Table2;
2. 更新操作 update :
Table7:
name | age | id | sex | salary |
---|---|---|---|---|
李三 | 24 | 114514 | 男 | 3500.00 |
张四 | 42 | 415411 | 女 | 3000.00 |
王五 | 99 | 666666 | 男 | 6666.00 |
弟弟 | 2 | 123123 | 男 | 1500.00 |
妹妹 | 99 | 123124 | 女 | 1500.00 |
//将所有年龄 大于50岁的人 工资增长10%
update Table7_1 set salary = 1.1*salary
where age > 50;
现在我们又有一个表,记录了这些人在另一处填写的出生日期,众所周知,别人在写年龄的时候总喜欢往年轻了写,而小孩写年龄的时候总会瞎写 (不是) ,但有了出生日期以后我们就可以更正他们的年龄
Table7_1:
id | birthday |
---|---|
114514 | 1995-01-11 |
415411 | 1969-02-03 |
666666 | 1953-11-11 |
123124 | 2011-10-01 |
注意这个表里的 birthday 是 date 类数据,是 SQL 提供给使用者的一种数据类型,可以方便的计算和存储日期和时间,声明的时候类型写 date 就行
//在 mySQL 中想要插入一个 date 数据很简单
mySQL:
insert into <table_name> values ('2019-10-01');
//在 Oracle 中这样是不行的,要用 to_date(a, b) 函数, a 是你要输入的日期,b 是格式
//通常 year 是4个y,mouth 是两个m,day 是两个d
Oracle:
insert into <table_name> values (to_date('20191001','yyyymmdd'));
insert into <table_name> values (to_date('2019-10-01','yyyy-mm-dd'));
//而从 date 中提取年份等
mySQL:
year(date) //就可以直接提取年份了,返回数字类型, mouth 和 day 同理
Oracle:
to_char(date,'yyyy') //用to_char(),返回字符串
cast(to_char(date,'yyyy') as int) //这样可以返回 int 参与数值计算
//以 2019 年为准更新正确的年龄,且不要更新 id 为 666666 的那个人(不要问为什么XD)
A 语句:
update Table7 a set age = (
select 2019 - year(birthday)
from Table7_1 b
where a.id = b.id and a.id <> '666666'
);
B 语句:
update Table7 a set age = (
select 2019 - year(birthday)
from Table7_1 b
where a.id = b.id
)
where a.id <> '666666';
C 语句://利用 case then else end 语句,不如 D 语句
update Table7 a set age = (
case
when a.id in (select id from Table7_1)
then (select 2019 - year(birthday) from Table7_1 b where a.id = b.id)
else (select age from (select b.age from Table7 b where a.id = b.id) c)
//mySQL 这里需要重命名并重建表,因为不能让原表 = 原表,只能让原表 = 原表的复制,略蠢 XD
//Oracle 直接写 else (select b.age from Table7 b where a.id = b.id) 就行
end
)
where id <> '666666';
D 语句:
update Table7 a set age = (
select 2019 - year(birthday)
from Table7_1 b
where a.id = b.id
)
where id <> '666666' and id in (select id from Table7_1);
A 语句执行结果:
可以看到 id 为 666666 的被设为了 null
因为 update set 的原理是这样的
- 先找到对应要更新的 age 列,从第一行开始执行更新
- 进入 select 子查询嵌套,先执行按照 select 的顺序依次执行 from,where,select
- 返回 select 查询结果,检查是否返回多行数据,如果返回多行数据则报错
- 使用外层 where 查看其是否满足条件,满足就更新,否则不更新
- 更新结果
因为我在 set 内部执行了 where a.id <> ‘666666’ 在更新 Table7 中 id=‘666666’ 那一行时,内部 where 判定都不符合条件,返回了 null ,外部没有 where ,默认为更新,就把 null 更新了上去,所以这个判定应该放在外部 where 中
name | age | id | sex | salary |
---|---|---|---|---|
李三 | 24 | 114514 | 男 | 3500.00 |
张四 | 50 | 415411 | 女 | 3000.00 |
王五 | null | 666666 | 男 | 6666.00 |
弟弟 | null | 123123 | 男 | 1500.00 |
妹妹 | 8 | 123124 | 女 | 1500.00 |
B 语句执行结果:
的确解决了上一个问题,但是仔细看 Table7_1 会发现 弟弟 没有对应的生日,会被返回一个 null,解决方法有两种:
一种是 C ,在内部 select 处让他没法返回 null (很麻烦,仅仅是为了引出 case then else end 语句)
一种是 D ,在外部 where 限定让 null 的值不更新 (简单点)
name | age | id | sex | salary |
---|---|---|---|---|
李三 | 24 | 114514 | 男 | 3500.00 |
张四 | 50 | 415411 | 女 | 3000.00 |
王五 | 99 | 666666 | 男 | 6666.00 |
弟弟 | null | 123123 | 男 | 1500.00 |
妹妹 | 8 | 123124 | 女 | 1500.00 |
case 语句:
跟 if else 类似
//使用方法
case <name>
when <值_1> then <执行_1>
when <值_2> then <执行_2>
... //可以有多个when... then...
else <执行_n>
end
//上面的意思就是 name 满足等于那个值,就执行对应 then 的语句,如果都不满足就执行 else
//也可以这么写
case
when <条件_1> then <执行_1>
when <条件_2> then <执行_2>
... //可以有多个when... then...
else <执行_n>
end
//上面的意思就是满足哪个条件,就执行对应 then 的语句,如果都不满足就执行 else
3. 删除操作 delete :
没啥好说的,运行原理跟 update set 差不多,一行行检索,where 限定满足就删除,不满足就保留,如果没有where就是全删(默认where 恒为真)
delete from <table_name>
where <条件>;
DQL:数据查询语言
选择操作 select :
select 语句一般由三个部分组成
- select 属性选择,用来选择你要查询的属性
- from 表选择,用来选择你要在那些表中查询
- (可选) where 限定选择,用来限定你的选择内容
//以上文Table1为例:
//我现在要找出所有年龄大于 20 岁的人 name 和 id
select name, id
from Table1
where age > 20;
//如何查询一个表的所有元素?
select * from <table_name>;
另外值得注意的是,select 返回的结果默认 all 属性,也就是说不去重复
如果想要去重需要 distinct 限定
PS: 实际操作时能不用 * 就不用 * ,带 * 的 select 无法很好的利用引索
//所有年龄大于 20 岁的人 name 和 id,且他们 id 不相同
select name, distinct id
from Table1
where age > 20;
具体理解顺序可以这么理解
- 先执行 from 操作,找到需要查询的表集合
- 再执行 where 操作,把表集合中不符合的去掉
- 最后执行 select 操作,返回查询结果
Table1:
name | age | id |
---|---|---|
李三 | 24 | 114514 |
张四 | 42 | 415411 |
Table2:
name | age | id |
---|---|---|
王五 | 99 | 666666 |
弟弟 | 2 | 123123 |
//我要查询两个表中年龄大于 25 岁的人 name 和 id
//错误写法:
select name, distinct id
from Table1, Table2
where age > 25;
//正确写法:
select name, id
from( //用 union all 语句将两者合并
select * from Table1
union all
select * from Table2
) a
// mySQL 一定将这个要重命名,不然会报错,因为 mySQL 强制要求所有派生出来的表(嵌套子查询)都要重命名
// 而 Oracle 则不用
where age > 25;
Table1, Table2:
Table1.name | Table1.age | Table1.id | Table2.name | Table2.age | Table2.id |
---|---|---|---|---|---|
李三 | 24 | 114514 | 王五 | 99 | 666666 |
李三 | 24 | 114514 | 张四 | 42 | 415411 |
张四 | 42 | 415411 | 王五 | 99 | 666666 |
张四 | 42 | 415411 | 弟弟 | 2 | 123123 |
实际上给出的是两个表的 笛卡尔积
集合运算:
-
并运算: union all 表示返回合并结果不去重,union 表示返回结果去重
-
差运算: except all 和 except ,做 A − B A-B A−B 操作,也就是说若 A 中列在 B 中出现则去掉,最后结果是 A 的子集
PS: Oracle 使用 minus 代替 except,而 mySQL 不支持 except ,代替方法可以看下文 -
交运算: intersect all 和 intersect,等价于 A − A ∩ B A-A\cap B A−A∩B
PS:这个 mySQL 也不支持
值得注意的是集合运算只能在两表列数相同,且列属性完全一致时使用,不然会报错
//求 A 和 B 的交,在 mySQL 中代替 intersect
select *
from A
where (A.<属性名1>, A.<属性名2>... A.<属性名n>) in (select * from B);
//求 A 和 B 的差,在 mySQL 中代替 except 或 minus
select *
from A
where (A.<属性名1>, A.<属性名2>... A.<属性名n>) not in (select * from B);
join 操作:
分为以下几种,我认为 偷懒 这个图片写的很好
当然,SQL语言也提供了 natural join(自然链接) 的操作,这个操作会自动检索同名的列属性进行笛卡儿积合并,因为是自动检索,最后不会出现同名属性,所以如果使用自然链接,不需要在属性前加限定词
Table3:
id | sex |
---|---|
123123 | 女 |
114514 | 男 |
这个时候用 natural join(自然链接)
select *
from Table1 natural join Table3;
//返回结果不同于下列语句
select *
from Table1 a join Table3 b on a.id = b.id;
//或者也可以用 where 代替 on,但不推荐(慢一点,而且乱)
select *
from Table1 a join Table3 b
where a.id = b.id;
第一个语句会返回如下结果,将 id 相同的合并在一起
name | age | id | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
而用第二个或第三个语句会返回
name | age | Table1.id | Table3.id | sex |
---|---|---|---|---|
李三 | 24 | 114514 | 114514 | 男 |
natrual 字段会自动将相同词条合并,所以说 natural join 实际上比单纯的 join + on 要快,因为 join 是先生成笛卡儿积再用 on 筛选,而 natural join 边生成边筛选
如果想用 join 并且合并相同属性,可以在 select 处进行处理,跟 natrual join 效果一样(但麻烦了)
select a.name, a.age, a.id, b.sex //剔除多余的id列,只保留a的id列
from Table1 a join Table3 b on a.id = b.id;
有时候我们需要保留原表信息,那么就会用到 left join, right join 和 full join
select *
from Table1 natural left join Table3;
//也可以写成
select a.name, a.age, a.id, b.sex
from Table1 a left join Table3 b on a.id = b.id;
//其它同理
Left join:
name | age | id | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
张四 | 42 | 415411 | null |
Right join:
name | age | id | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
null | null | 123123 | 女 |
Full join:
name | age | id | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
null | null | 123123 | 女 |
张四 | 42 | 415411 | null |
值得注意的是 mySQL 是不支持 full join 的 (Oracle 可以)
如果你使用 mySQL 可以用下列语句代替 full join
(select * from Table1 natural left join Table3)
union
(select * from Table1 natural right join Table3);
order by:
如果你想对某一个或某几个元素进行排序,可以使用 order by 后缀
//对 Table1 和 Table2 的人按 age 排序,如果年龄相同按 id 排序,且满足 age 大于 10
select *
from(
select * from Table1
union all
select * from Table2
) a
where age > 10
order by age, id;
返回结果:
name | age | id |
---|---|---|
李三 | 24 | 114514 |
张四 | 42 | 415411 |
王五 | 99 | 666666 |
order by 默认升序,在属性名后加 desc 可以降序
order by age desc, id;
//代表先用 age 降序排序,如果 age 相同再用 id 升序排序
聚集函数 和 group by:
如何找到 Table1 和 Table2 中 age 最大的那个人
我们可以用下列语句:
//先创建一个 Table4 储存 Table1 和 Table2 的并
create table Table4 as
select * from(
select * from Table1
union all
select * from Table2
) a;
//找到其中年龄最大的,mySQL 的写法,因为 mySQL 没有差运算,所以可以这么写
select a.name, a.age
from Table4 a
where (a.name, a.age) not in(
select b.name, b.age
from Table4 a, Table4 b
where a.age > b.age
);
//原理是先做一个此表与自己的笛卡儿积,如果其中有左面年龄大于右面的情况,那就说明右面的年龄必不是最小值,从原表剔除,最后剩下的一定是最大值
//如果有差运算(以 Oracle 为例)也可以这么写,效率没有上面高
select name, age
from(
(select a.name, a.age from Table4 a, Table4 b)
minus
(select b.name, b.age from Table4 a, Table4 b where a.age > b.age)
);
做起来很麻烦,所以SQL提供给了我们一类分组函数,下面是几个比较常用的
- max() 和 min() 函数
- sum() 求和函数
- avg() 求平均值函数
- count() 计数函数
//使用分组函数max,可以看到简单了很多
select name, age, sex
from Table4, (select max(age) Max_age from Table4) b
where age = b.Max_age;
//更好的写法(省空间和时间):
select name, age, sex
from Table4
where age in (select max(age) Max_age from Table4);
//注意,下面是错误的写法,这样 name 无法对应正确的最大年龄,会默认第一个人的姓名
select name, max(age) Max_age, sex
from Table3;
通常分组函数需要配合 group by 后缀使用,这个后缀决定了你的分组函数是以什么为基准进行计算的,比如说什么都不加,那就意味着以全表数据为准,举个例子
Table5:
name | age | id | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
张四 | 42 | 415411 | 女 |
王五 | 99 | 666666 | 男 |
弟弟 | 2 | 123123 | 男 |
//找 Table5 中男性和女性的最大年龄者(包括姓名)
select name, age, sex
from Table5
where age in (select max(age) Max_age from Table5 group by sex);
//找 Table5 中对应性别的最大年龄
select max(age) Max_age, sex
from Table5
group by sex;
以第二个为例,由于不需要找到对应的姓名,所以理解起来比较轻松
group by sex 意思是告诉这个 select 在执行分组函数时以 sex 为组,意思就是以不同的 sex 男和女分组,对每个组分别进行找 max(age) 操作
第二个最后返回结果:
Max_age | sex |
---|---|
42 | 女 |
99 | 男 |
如果你的 group by 选择了多个属性名,那么会根据这些属性名一起分组,比如说
group by sex,name 就是找 sex 相同,再找 name 也相同的那一组人
另外值得注意的是 order by 应该写在 group by 的后面
Table6:
company | id | salary |
---|---|---|
Baidu | 114514 | 3500 |
Baidu | 666666 | 6666 |
Baidu | 123123 | 1500 |
HuaWei | 415411 | 3000 |
HuaWei | 100000 | 2019 |
XiaoMi | 100001 | 9102 |
DaMi | 100002 | 3 |
我现在想知道 平均薪水 (avg_salary) 大于 2500 的所有公司
这时候由于是对分组函数的结果进行选择,应使用 having 语句
//错误写法
select company, round(avg(salary), 2) avg_salary
from Table6
where round(avg(salary),2) > 2500
group by company;
//正确写法
select company, round(avg(salary), 2) avg_salary
from Table6
group by company
having round(avg(salary),2) > 2500;
//round(a, b) 函数是对 a 进行小数位截取,保留 b 位小数
company | avg_salary |
---|---|
Baidu | 3888.67 |
HuaWei | 2509.50 |
XiaoMi | 9102.00 |
有关 null 和 unknown :
集合的比较限定:
是否还记得我们找 Table4 中的年龄最大者
Table4_1:
name | id | age | sex |
---|---|---|---|
李三 | 24 | 114514 | 男 |
张四 | 42 | 415411 | 女 |
王五 | 99 | 666666 | 男 |
弟弟 | 2 | 123123 | 男 |
妹妹 | 99 | 123124 | 女 |
例1:我现在想换种方式找最大的年龄:
select name, age
from Table4_1
where age >= all ( select age from Table4_1 );
> all 是指找一个比这个集合中所有元素都大的
> some 是指找一个比这个集合至少某一个元素大的
其他运算符 <, <>, = 等同理
例2:找到所有年龄有重复的人
select name, age
from Table4_1 a
where a.age = some ( select b.age from Table3 b where b.name <> a.name );
//其实这个 =some 换成 in 也行
//类似的,可以用来找表中重复的数据,但是这个效率不高,下面效率高
select *
from Table4_1
where age in(
select age
from Table3
group by age having count(*)>1
);
对 select 的总结:
对于 select 来说 具体运行顺序:
- 执行 from 得到做一些操作(笛卡儿积等)得到要处理的关系数据集合
- 执行 on 对关系数据集进行初步筛选
2_plus. 是外连接(left join 等)则将对应表加到结果中,如果是多个表 from ,返回 1 执行,直到全部处理完 - 执行 where 对关系数据集进行筛选,一行一行遍历,如果 where 返回 ture 则保留,反之剔除
- 执行 group by 对关系数据集进行分组
- 执行 聚集函数 得到每个组的结果
- 执行 having 对每个组的结果进行有条件的筛选,同 where
- 执行 非聚集函数的 其他函数计算 和 表达式计算
- 执行 select 返回结果
- 执行 distinct 将 select 的结果去重
- 执行 order by 将最后结果排序
PS:值得注意的是在聚集函数中使用 distinct 是可以去重的,虽然 distinct 在 聚集函数 之后进行,但可以理解成聚集函数里是一个 子select 语句,是对这个选出来的子表进行 distinct 操作之后再聚集,也符合顺序
select count(distinct <属性A>) from <table_name>;
//找出表中不同的 属性A 列数量
这个PS的内容我不太确定,如果有错误欢迎在评论指正Orz
写法顺序:
select <属性集>
from <表集合> on <条件集合>
//有三个和三个以上的表用 on 这么写:
//from ((A join B on <条件集合>) join C on <条件集合>) join D on <条件集合>
where <条件集合>
group by <属性集>
having <条件集合>
order by <属性集>
有了这些知识,再去看 update(DDL) 就很轻松啦
(应该是这样的