参考
写sql语句的顺序:
select 输出
from 获取表中的数据
where 对 from 得到的数据进行过滤。and. or .in .like . != 等等
group by 分组
having 过滤
order by 排序
limit 限定个数
执行sql语句的顺序
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
5 | E | 1 |
6 | F | 1 |
7 | G | 2 |
select id,name,classid from stu where id%2 = 0 group by classid
先执行where过滤。之后把数值相同的分为一组,此时根据classid分为了两组。把每组的第一行当作各自组的标识,select只输出标识。
输出:
原表的第二行和第四行
id | name | classid |
---|---|---|
2 | B | 1 |
4 | D | 2 |
数据表格同例子1。
selec count(1), classid from stu group by classid
相当于在原表中添加了一列temp(恒等于1),一列count,每个元组都会在自己的count属性上设置为相应的计数值。
输出:
count(1) | classid |
---|---|
4 | 1 |
3 | 2 |
数据表格同例子1。
sql1
select count(name), classid from stu group by classid 不去重
和上例的count(1)的区别:如果name中有null,那么count不计入null。其他情况二者一样。
sql2
select count(distinct name), classid from stu group by classid 去重
查询每个班中人数大于2的班级号和人数
select count(*) as n, clid
from stu
group by clid
having n > 2
(order by n desc)
前四行等价于
select clid
from stu
group by clid
having count(*) > 2
注意此时先执行select(添加了一列count),再执行having
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | A | 1 |
4 | C | 2 |
5 | D | 2 |
from stu group by clid,name
clid和name都相同的分为一组
上图的就被分为了4组
与group by搭配的常用聚集函数
1. count
2. sum
3. max
4. min
5. avg
6. group-concat: 字符串的集合,不常用
sql1
select sum(1), classid from stu group by classid
可以看作等于count(1)
sql1.1
select sum(2), classid from stu group by classid
可以看作等于 count(2) * 2
sql2
selec count(classid), classid from stu group by classid
当前分组中不为null的classid 的总和
sql3
selec count(distinct classid), classid from stu group by classid
当前分组中不为null且不重复的clid的总和
case
when then 输出
when then 输出
else 输出
end
例子1:
将1班的学生改为2班,2班的改为1班
update stu
set classid = (case when classid=1 then 2 when classid=2 then 1 end)
如果只是想将输出结果进行改变,而不是修改原数据,则用select case when
格式:
select stuid,stuname,
(case when classid=1 then 2 when classid=2 then 1 end) as newClassid
from stu
例子2:聚集函数加case
查询:name 基础成绩 爬虫成绩 sql成绩(3列的表格重新组合为4列的形式)
day2 p03
select name,
max(case when 学科=‘基’ then score else null end) as 基,
max(case when 学科=‘爬’ then score else null end) as 爬,
max(case when 学科=‘sql’ then score else null end) as sql,
from socre
group by name
注意先执行group再执行select。case会把每一行都扫描一遍。其中max函数可以变成其他min、avg等。
假设stu表有m个元素,3种属性,
class表有n个元素,3种属性,
stu join class where XXX
:两张表的所有数据都要组合起来,会生成m*n条数据量,产生6种属性。
其中把 stu join class
看作一张表。
stu join class on stu.classid = class.id where XXX
on是在where之前的操作,生成的数据量会小(与没有on,直接where相比)。
其中把stu join class on stu.classid = class.id
看作一张表。
单纯join,on和where都可以用。
和join的区别:
join on
的前提下,会查看左边表的数据是不是都包含在新生成的表中,如果都包含了,则和join没区别;如果不是,用null与不包含的行组成新行加入新表(也就是必须让左表的数据都出现在新表之中)。例子
假设stu有3条数据,class有1条数据。
stu表
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
class表
id | name | manager |
---|---|---|
1 | O | 黄 |
class left join stu
on class.id = stu.classid
会生成2条数据。
class.id | name | manager | stu.id | name | classid |
---|---|---|---|---|---|
1 | O | 黄 | 1 | A | 1 |
1 | O | 黄 | 2 | B | 1 |
先join on生成了两条,然后再检查class中的所有元素是不是都出现了,此时已经都出现了。
A right join B
等价于
B left join A
上面是stu表,下表是class表
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
id | name | manager |
---|---|---|
1 | X | 蓝 |
查询哪些同学没有班级。
用join只能查出来有班级的同学,此时需要用left join来补充null。
select stu.name
from stu left join class
on classid = class.id
where class.id is null
此时如果用一个子查询,sql语句如下,查询结果是什么样子:
select name,
(select id from class where manager='蓝')
from stu;
当id返回单个值的时候,会创建一个新列class.id
stu.id | class.id |
---|---|
1 | 1 |
2 | 1 |
3 | 1 |
4 | 1 |
假设class表变成如下,此时id返回多值的时候,会报错:
id | name | manager |
---|---|---|
1 | X | 蓝 |
2 | Y | 黄 |
3 | Z | 蓝 |
查询 01 课程比 02 课程成绩高的学生的信息及课程分数。
同一个表中的数据(‘01’课程的成绩和‘02’课程的成绩)进行对比,一般需要自己join自己。
# 连续两次join
select c_01.score as '01分数',c_02.score as '02分数', s.sname
from SC c_01 join SC c_02 on c_01.SId = c_02.SId and c_01.CId = '01' and c_02.CId = '02' join Student s on s.SId = c_01.SId
where c_01.score > c_02.score
查询同时选择了 01 课程和 02 课程的学生
// 第一步找到了符合条件的学生id
select s1.SId
from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02'
// 如果使用子查询,完整如下
select * from Student
where SId in (
select s1.SId
from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02'
)
// 如果使用join,完整如下
select st.*
from SC s1 join SC s2 on s1.SId=s2.SId and s1.cid='01' and s2.cid='02' join Student st on s1.SId=st.SId
查询 没有学全所有课程的学生的信息
# 1 NOT IN方式。先找出学全了的学生。三重嵌套
select * from Student
where SID not in (
select SId
from SC
group by SId
having count(1) = (select count(1) from Course)
)
# left join的方式
select st.*
from student st left join sc on sc.SId = st.SId
group by st.sid
having count(1) < (select count(1) from Course)
select、 where、from中都可以有子查询。
from的子查询例子,必须有别名
select stu.name
from stu join (select * from class where manager='蓝') as C
on stu.classid = C.id
子查询内有引用父查询中的表的信息
stu表:
id | name | classid |
---|---|---|
1 | A | 1 |
2 | B | 1 |
3 | C | 2 |
4 | D | 2 |
5 | E | 3 |
查找每个班级中学号最大的学生的信息
子查询的方式:
select * from stu
where id in (
select max(id) # 子查询中,表新增了一列,value是对应分组的id最大值
from stu
group by classid
)
半连接的方式:
select *
from stu s1
where id = (select max(id) from stu s2 where classid=s1.classid)
半连接方式的过程:
select max(id) from stu s2 where classid=1
,内部子查询返回的结果是2
,整个查询相当于select *
from stu s1
where 1 = 2 # false
select max(id) from stu s2 where classid=1
,内部子查询返回的结果是2
,整个查询相当于select *
from stu s1
where 2 = 2 # true
并发执行的问题:
存取和存储不正确的数据,破坏事务的隔离性和数据库的一致性。
不可重复读
还有两种情况称之为幻影读
:
封锁就是事务T在对某个数据对象操作之前,对其加锁。加锁后事务T就对该数据对象有了一定的控制,在T释放它的锁之前,其他的事务不能更新此数据对象。
数据资源释放时间不确定,导致某些事务长时间等待,得不到封锁的机会。先来的却可能等待很久。
采用先来先服务策略。
多个事务各自占有部分资源等待另一部分资源,资源需求出现回路,导致事务停顿得不到执行。
预防死锁: 就是要破坏死锁产生的条件,包含一次封锁法和顺序封锁法。
诊断与解除死锁: 定期检测是否存在死锁,诊断死锁包含超时法和等待图法。解除死锁的方式是选择一个处理死锁代价最小的事务并将其撤销。
以不同的顺序串行执行多个事务有可能会产生不一致的结果,但不会让数据库处于不一致状态,所以可以认为是正确的。
几个事务的并行执行是正确的<=>其并行执行的结果与某一种串行执行时的结果相同。
这种并行调度策略称为可串行化的调度策略。
事务遵循两段锁协议是保证可串行化调度的充分条件。
在运用X锁和S锁这两种基本封锁时,还需要约定一些基本规则,例如何时申请X或S锁,何时释放等,称这些规则为封锁协议。
一级封锁协议
:事务T在修改数据R之前必须先对其加X锁,直到事务结束才释放。防止丢失修改。
二级封锁协议
:一级封锁协议的基础上添加:事务T在读取数据R之前必须先加S锁,读完后释放S锁。除了防止丢失修改之外,还防止了读脏数据。
三级封锁协议
:一级封锁协议的基础上添加:事务T在读取数据R之前必须先加S锁,直到事务结束才释放S锁。除了防止丢失修改之外,还防止了读脏数据和不可重复读。
“加锁”阶段只能加锁,“解锁”阶段只能解锁。加锁和解锁分为两个阶段进行。
数据结构之B树与B+树
不同存储引擎具有不同的索引类型和实现。
BTree是多路平衡查找树。有的数据只在非叶子节点上。所有的叶子节点都位于同一层。
B树与B+树对比
B树与B+树都是对磁盘友好的数据结构,能大幅降低磁盘访问次数。B树的优点在于数据存储在每个结点中,可以更快访问到,而不必须走到叶子结点,B树更多的用在文件系统中。B+树的每个非叶子结点都只充当索引,所以查询必须到叶子结点结束,但它十分适合“扫库”和区间查找,而且因为大多结点只用于索引,所以并不会存储真正的数据,在内存上会更紧凑,相同的内存就可以存放更多的索引数据了。比如字典的拼音和汉字是分离的,只需要几十页就能得到完整的拼音表,但是如果拼音和汉字掺杂在一起,要得到完整的索引(拼音)表就需要整个字典。B+树的这些特性使得它更适合用来做数据库的索引。
哈希索引能以 O(1) 时间进行查找,但是失去了有序性:
InnoDB 存储引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。
MyISAM 存储引擎支持全文索引,用于查找文本中的关键词,而不是直接比较是否相等。
查找条件使用 MATCH AGAINST,而不是普通的 WHERE。
全文索引使用倒排索引实现,它记录着关键词到其所在文档的映射。
InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持全文索引
MyISAM 存储引擎支持空间数据索引(R-Tree),可以用于地理数据存储。空间数据索引会从所有维度来索引数据,可以有效地使用任意维度来进行组合查询。
使用 Explain
进行分析。
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
try {
Class.forName("com.mysql.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 取得数据库的连接
* @return 一个数据库的连接
*/
public static Connection getConnection(){
Connection conn = null;
try {
//初始化驱动类com.mysql.jdbc.Driver
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/database_name?characterEncoding=UTF-8","root", "password");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
Statement s = conn.createStatement();
// 注意: 字符串要用单引号'
String sql = "insert into t_courses values(null,"+"'数学')";
s.execute(sql);
String sql = "insert into t_course(course_name) values(?)";
PreparedStatement pstmt = (PreparedStatement) conn.prepareStatement(sql);
pstmt.setString(1, courseName); //给占位符赋值
pstmt.executeUpdate(); //执行
?
在这里就起到占位符的作用。这种方式除了避免了statement拼接字符串的繁琐之外,还能够提高性能。每次SQL语句都是一样的,数据库就不会再次编译,这样能够显著提高性能。
注意一点,这里的参数索引是从1
开始的
ResultSet rs = s.executeQuery(sql);
while (rs.next()){ // 注意是next(),不是hasNext
int courseId = rs.getInt("course_id");
String courseName = rs.getString("course_name");
// 还可以填该属性在数据表中的列号,从1开始编码
}
/**
* 封装三个关闭方法
* @param pstmt
*/
public static void close(PreparedStatement pstmt){
if(pstmt != null){ //避免出现空指针异常
try{
pstmt.close();
}catch(SQLException e){
e.printStackTrace();
}
}
}
public static void close(Connection conn){
if(conn != null){
try {
conn.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
public static void close(ResultSet rs){
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
// TODO: handle exception
e.printStackTrace();
}
}
}