SQL数据库学习笔记

还在更新中,若是有错误欢迎指出,参考了 Oracle手册数据库系统概念第6版部分博客教授上课讲述 的内容html

目录


前言:

数据库是学业课,同时居然爬虫也学了,那么爬取大量数据时用来管理和处理海量数据的数据库怎么能少了呢,因此写一篇关于SQL语言的学习笔记,可能部分语法在不一样的商业数据库软件有些出入,不过大部分应该是统一的,本文语法大部分以 OraclemySQL 为准(固然主要应该仍是 mySQL),在不一样的地方会分别给出属于不一样数据库的不一样写法
说到数据库,最多见的就是关系数据库了,SQL关系数据库提供了 4 类语言供用户使用和修改数据库,分别是:前端

  • DDL(Data Definition Language)数据定义语言
  • DML(Data Manipulation Language)数据操纵语言
  • DCL(Data Control Language)数据控制语言
  • DQL(Data Query Language) 数据查询语言

其中不少分类把 DQLDML 合在一块儿称为 DML ,那样就是 3
也有人在这三类基础上又加上了 TCL —— 事务控制语言,那仍是 4程序员

那么为何要把SQL语言分类?
由于这些语言使用的人群不一样,一个数据库须要有程序员为他写一个对外界用户交互的程序(或者说前端),这个时候程序员接触到的更多的是 DMLDQL,就是说只须要查询和适当的修改数据库里的数据便可;固然一个数据库还须要一个超级管理员来维护,这个时候超级管理员就能够接触到 DCLDDL,用来修改数据库和分配权限;最后若是一个数据库没有前端,直接让外部用户来操做的话,那咱们指望的就是让用户使用一部分 DQL ,仅提供部分查询功能供用户使用web

另外就是关系数据库其实是由不少个表构成的,因此咱们大部分操做围绕 关系关系的集合——表 进行,做为前驱知识可能会用到 实际上并不会用到 关系代数sql


返回目录
数据库

DDL:数据定义语言

1. 建表操做 create :

表 (table) 做为关系数据库的基础,几乎全部操做都围绕表进行,对此,应先创建一个关系表来存储数据oracle

create table <table_name>(
	<属性名_1> <type_1>,
	<属性名_2> <type_2>,
	...
	<属性名_n> <type_n>,
	<完整性约束_1>,
	...
	<完整性约束_n>
);

其中属性名的类型有如下几种:
SQL Server 为准:app

  1. char(n): 最多 8000 个字符,char 前加 n 能够存汉字,最多 4000
  2. varchar(n): 最多 8000 个字符,则被转换为 text 类型,加 n 同上
  3. int:范围 -21474836482147483647,若是加括号,括号内是容许出现的最大位数
  4. smallint: 范围 -3276832767,加括号同上
  5. numeric(p,d): 固定精度和比例的数字,容许从 1 0 38 1 -10^ { 38 }-1 1 0 38 1 10^ { 38 }-1 之间的数字。
    p 表示能够存储的最大位数(小数点左+右的位数)并且 p 必须是 138 之间的值;默认是 18
    s 表示小数点右侧最大位数,并且 s 必须是 0p 之间的值;默认是 0
  6. float(n) :精度为 n 位的浮点数,范围 1.7 9 308 -1.79^ { 308 } 1.7 9 308 1.79^ { 308 } 的浮动精度数字数据

其它数据库的能够查看 SQL数据类型svg

另外若是想约束一个属性值不能为空,能够在后面加上 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
deletedrop 弱一点

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

注意这个表里的 birthdaydate 类数据,是 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 语句执行结果:

能够看到 id666666 的被设为了 null
由于 update set 的原理是这样的

  1. 先找到对应要更新的 age 列,从第一行开始执行更新
  2. 进入 select 子查询嵌套,先执行按照 select 的顺序依次执行 fromwhereselect
  3. 返回 select 查询结果,检查是否返回多行数据,若是返回多行数据则报错
  4. 使用外层 where 查看其是否知足条件,知足就更新,不然不更新
  5. 更新结果

由于我在 set 内部执行了 where a.id <> ‘666666’ 在更新 Table7id=‘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;

具体理解顺序能够这么理解

  1. 先执行 from 操做,找到须要查询的表集合
  2. 再执行 where 操做,把表集合中不符合的去掉
  3. 最后执行 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 allexcept ,作 A B A-B 操做,也就是说若 A 中列在 B 中出现则去掉,最后结果是 A 的子集
    PS: Oracle 使用 minus 代替 except,而 mySQL 不支持 except ,代替方法能够看下文

  • 交运算: intersect allintersect,等价于 A A B A-A\cap 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 joinright joinfull 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:

如何找到 Table1Table2age 最大的那我的
咱们能够用下列语句:

//先建立一个 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提供给了咱们一类分组函数,下面是几个比较经常使用的

  1. max()min() 函数
  2. sum() 求和函数
  3. avg() 求平均值函数
  4. 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 来讲 具体运行顺序:

  1. 执行 from 获得作一些操做(笛卡儿积等)获得要处理的关系数据集合
  2. 执行 on 对关系数据集进行初步筛选
    2_plus. 是外链接(left join 等)则将对应表加到结果中,若是是多个表 from ,返回 1 执行,直到所有处理完
  3. 执行 where 对关系数据集进行筛选,一行一行遍历,若是 where 返回 ture 则保留,反之剔除
  4. 执行 group by 对关系数据集进行分组
  5. 执行 汇集函数 获得每一个组的结果
  6. 执行 having 对每一个组的结果进行有条件的筛选,同 where
  7. 执行 非汇集函数的 其余函数计算表达式计算
  8. 执行 select 返回结果
  9. 执行 distinctselect 的结果去重
  10. 执行 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) 就很轻松啦 (应该是这样的