MySQL 学习笔记

一、SQL 的执行逻辑


这一节的原文地址是lifepoem

首先我们来看一条完整的SQL语句

(5) SELECT (5-2) DISTINCT (5-3) TOP(<top_specification>) (5-1) <select_list>
(1) FROM (1-J) <left_table> <join_type> JOIN <right_table> ON <on_predicate>
| (1-A) <left_table> <apply_type> APPLY <right_table_expression> AS <alias>
| (1-P) <left_table> PIVOT(<pivot_specification>) AS <alias>
| (1-U) <left_table> UNPIVOT(<unpivot_specification>) AS <alias>
(2) WHERE <where_predicate>
(3) GROUP BY <group_by_specification>
(4) HAVING <having_predicate>
(6) ORDER BY <order_by_list>;

可以看到,SQL语言和其他编程语言的第一个区别就是代码的处理顺序。在多数编程语言中,代码按照其书写的顺序依次处理。而对于SQL,第一个被处理的子句是FROM,尽管SELECT子句最先出现,但它几乎在最后处理。

查询处理的每一步都会生成一个虚表,它会成为下一步骤的输入。这些虚表对调用者是不可见的,只有最后一步生成的表才会返回给调用者。如果查询中没有指定某个子句,其对应的步骤则被略过。下面简单的描述了不同的逻辑步骤,如果你现在对这些描述不甚了解也不用担心,稍后的例子会让你得到更好的理解和更详细的说明。

(1) FROM

FROM处理阶段会确定查询的数据源表并处理表操作符。每个表操作符都会有一系列的子步骤。比如,Join包含的子步骤有:

  • (1-J1) 笛卡儿积(Cross Join),
  • (1-J2) 应用ON条件,
  • (1-J3) 添加外部数据行(Add Outer Rows).

FROM阶段生成虚表VT1.

  • (1-J1) Cross Join 这个阶段针对两个数据表执行cross join,生成VT1-J1.
  • (1-J2) ON Filter 这个阶段会基于ON子句中的条件来对VT1-J1中的记录进行过滤,只有条件返回TRUE的数据行会被插入到VT1-J2.
  • (1-J3) Add Outer Rows 如果指定了外连接(OUTER JOIN),保留表中没有匹配成功的行会加入到VT1-J2的数据行中,生成VT1-J3.

(2) WHERE

这个阶段会基于WHERE子句的条件对VT1表进行过滤,只有条件返回TRUE的数据行会被插入到VT2.

(3) GROUP BY

对VT2中的数据行进行分组,生成VT3. 这样对于每一个组将只有一条记录。

(4) HAVING

基于HAVING子句中的条件对VT3中的分组数据进行过滤,只有条件返回TRUE的分组数据行会被插入到VT4.

(5) SELECT

处理SELECT子句中的元素,生成VT5. 它包含以下几个子步骤:

  • (5-1) Evaluate Expressions 对SELECT列表进行求值,生成VT5-1.
  • (5-2) DISTINCT 去除VT5-1中的重复行,生成VT5-2.
  • (5-3) TOP 基于ORDER BY子句定义的逻辑顺序,过滤出顶部的相应行数(或百分比行数),生成VT5-3.

(6) ORDER BY

对VT5-3中的数据行进行排序,生成游标(cursor) VC6

如果用图表来表示即

二、练习题目

节选于文章:SQL面试,让你的面试官无fu,ck可说,第17题难倒一片人
SQL的各种查询技巧参见:sql各种查询技巧

(1) 数据表

Orders(OID,month,datetime,account,price) --OID 订单编号,month 学订单月份,datetime 订单时间,account 订单账户,price 订单价格

Student(SID,Sname,Sage,Ssex) --SID 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别

Course(CID,Cname,TID) --CID --课程编号,Cname 课程名称,TID 教师编号

Teacher(TID,Tname) --TID 教师编号,Tname 教师姓名

SC(SID,CID,score) --SID 学生编号,CID 课程编号,score 分数

(2) 练习题目

0、信息展示

select a.*, b.score as score_01,c.score as score_02,d.score as score_03,
ifnull(b.score,0)+ifnull(c.score,0)+ifnull(d.score,0) as score_total 
from Student a 
left join SC b 
on a.SID=b.SID and b.CID='01'
left join SC c 
on a.SID = c.SID and c.CID='02' 
left join SC d
on a.SID =d.SID and d.CID='03' 
order by a.SID;

1、查询"01"课程比"02"课程成绩高的学生的信息及课程分数

# 查询同时存在"01"课程和"02"课程的情况
select a.* , b.score as score_01,c.score as score_02
from Student a , SC b , SC c 
where a.SID = b.SID 
and a.SID = c.SID 
and b.CID = '01' 
and c.CID = '02' 
and b.score > c.score;

# 查询存在"01"课程但可能不存在"02"课程的情况
select a.* , b.score,c.score
from Student a 
left join SC b 
on a.SID = b.SID and b.CID = '01' 
left join SC c 
on a.SID = c.SID and c.CID = '02' 
where b.score > ifnull(c.score,0);

2、查询平均成绩小于60分的同学的学生编号和学生姓名和平均成绩

# 查询在sc表存在成绩的学生信息的SQL语句
select a.* ,avg(b.score) as score_avg 
from Student a,SC b 
where a.SID=b.SID 
group by a.SID 
having avg(b.score) < 60 
order by a.SID;

#查询在sc表中不存在成绩的学生信息的SQL语句
select a.SID , a.Sname , ifnull(avg(b.score),0) as count_score 
from Student a 
left join sc b on a.SID = b.SID 
group by a.SID 
having ifnull(avg(b.score),0) < 60 
order by a.SID;

3、查询所有同学的学生编号、学生姓名、选课总数、所有课程的总成绩

select a.SID, a.Sname, count(b.CID) as CSum, ifnull(sum(b.score),0) as Score_Sum 
from Student a 
left join SC b 
on a.SID = b.SID 
group by a.SID
order by a.SID;

4、查询没学过"张三"老师授课的同学的信息

select m.* from Student m 
where m.SID not in 
(select distinct SC.SID 
from SC , Course , Teacher 
where SC.CID = Course.CID and Course.TID = Teacher.TID and Teacher.Tname = '张三') 
order by m.SID;

5、查询学过编号为"01"但是没有学过编号为"02"的课程的同学的信息

# 方法一:在大的信息表中查询
select a.*, b.score as score_01,c.score as score_02 
from Student a 
left join SC b 
on a.SID=b.SID and b.CID='01'
left join SC c 
on a.SID = c.SID and c.CID='02' 
where b.score>0 and isnull(c.score) 
order by a.SID;

#方法二:直接来
select a.*
from Student a,SC b 
where a.SID =b.SID and b.CID='01' 
and a.SID not in 
(select a.SID from Student a,SC b where a.SID=b.SID and b.CID='02');

6、查询和"01"号的同学学习的课程完全相同的其他同学的信息

select Student.* from Student 
where SID in 
(select distinct SC.SID 
from SC 
where SID <> '01' and SC.CID in 
(select distinct CID from SC where SID = '01') 
group by SC.SID 
having count(1) = 
(select count(1) from SC where SID='01')
);

7、按平均成绩从高到低显示所有学生的所有课程的成绩以及平均成绩

select a.SID as 学生编号 , a.Sname as 学生姓名 , 
max(case c.Cname when '语文' then b.score else null end) as 语文, 
max(case c.Cname when '数学' then b.score else null end) as 数学, 
max(case c.Cname when '英语' then b.score else null end) as 英语, 
cast(avg(b.score) as decimal(18,2)) as 平均分,  
sum(b.score) as 总分 
from Student a 
left join SC b 
on a.SID = b.SID 
left join Course c 
on b.CID = c.CID 
group by a.SID , a.Sname 
order by 平均分 desc

8、随机选取1条学生信息

# 简单但效率可能比较低的方法
select * from Student order by rand() limit 1; 
# 一种高效的方式,手动找一个随机的ID号
select * from Student 
where SID >= (
(select min(SID) from Student)+
((select max(SID) from Student)-(select min(SID) from Student))*rand()
) limit 1;

9、选择年龄大于等于26岁的学生信息

select *,date_format(from_days(to_days(now())-
to_days(Sage)),'%Y')+0 as age from Student 
where date_format(from_days(to_days(now())-
to_days(Sage)),'%Y')+0>=26 
order by age;

10、选取总分排名前三的学生及其总成绩
因为MySQL没有 rank() 函数,所以我们用limit来实现

# 分数相同的随机选择其一
select a.SID,a.Sname,sum(b.score) as total  
from Student as a,SC as b 
where a.SID=b.SID
group by a.SID 
order by sum(b.score) desc 
limit 5;

此时排名是连续的,分数相同的时候只能选择其一。如果需要都被选上,我们可以这样解读排名。排名第一的同学代表比他分数高的同学人数为0,排名第二的同学代表比他分数高的同学人数为1,以此类推。于是便有了下面的方法

#创建临时表tmp
create table tmp (
select a.SID,a.Sname,sum(b.score) as total  
from Student as a,SC as b 
where a.SID=b.SID
group by a.SID);
#选择不连续排名前5的同学
select a.* 
from tmp as a 
left join tmp as b 
on a.total < b.total 
group by a.SID 
having count(b.total)<5;

11、5月份有订单6月份没订单的用户[京东的面试题]
Order(userid,orderid,month,price)

# 方法一
select userid ,month,count(orderid) as count 
from order 
group by userid,month 
having (month=5 and count(orderid)>0) or 
(month=6 and count(orderid)=0);

三、MySQL 技巧分析

1、联结技巧

SQL join技巧

注1:MySQL没有 full join,图中左下角可以用 left join union all right join 实现
注2:left outer join 等价于 left join
注3: select a.* ,b.* from tablea as a inner join tableb as b on a.key=b.key 等价于 select a.*,b.* from tablea as a,tableb as b where a.key=b.key
注4: select a.* ,b.* from tablea as a cross join tableb as b 等价于 select a.*,b.* from tablea as a,tableb as b

2、UNION
union:上下连接,返回不重复数据
union all:上下连接,返回重复数据

差集Except:

SELECT ID FROM (
SELECT DISTINCT A.AID AS ID FROM TABLE_A A
UNION ALL
SELECT DISTINCT B.BID AS ID FROM TABLE_B B
)TEMP GROUP BY ID HAVING COUNT(ID) = 1

交集Intersect:

SELECT ID FROM (
SELECT DISTINCT A.AID AS ID FROM TABLE_A A
UNION ALL
SELECT DISTINCT B.BID AS ID FROM TABLE_B B
)TEMP GROUP BY ID HAVING COUNT(ID) = 2

四、MySQL的安装和使用

1、安装
下载 msi文件 MySQL ,一路默认就好

2、添加到环境变量

3、CMD启动方式

# cmd下运行
mysql -uroot -p
mysql>show 数据库名;
mysql> use 具体数据库名;
mysql> 执行语句

4、python3.x启动方式(参考文章

# 如果电脑安装了MySQL
import pymysql
conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='******',db='*****');
cursor = conn.cursor()
cursor.execute('select * from table')
cursor.fetchone()

# 如果不像安装任何数据库(SQL lite)
from pandasql import sqldf
pysqldf = lambda q: sqldf(q, globals())
df=pd.DataFrame()
d=pysqldf('select * from df')

5、MySQL的导入导出

5-0: 准备工作
我使用的是MySQL5.7,平台是win10。在导入导出数据前得先开权限。


  1. 找到MySQL的数据目录:C:\ProgramData\MySQL\MySQL Server 5.7
  2. 找到my.ini 文件,注释掉其中的secure-file-priv = "*************"
  3. 保存重启MySQL即可
  4. 注意导入导出时,数据都只能在工作目录中进行,其他地方不被允许.

5-1: 导入数据(.sql、.txt、.csv)

# =========导入 sql文件===========
# 方法一
mysql -u 用户名 -p 数据库名 < 文件名.sql
#方法二
mysql>use 数据库名;
mysql>source 文件名.sql;
# =========导入 文本 文件===========
mysql> LOAD DATA LOCAL INFILE 'dump.txt' INTO TABLE mytbl 
-> CHARACTER SET utf8 
-> FIELDS TERMINATED BY ':'
-> LINES TERMINATED BY '\r\n';

5-1 : 导出数据(.sql、.txt、.csv)

# ====直接导出数据库=======
mysqldump -u用户名 -p 数据库名 > 文件名.sql
mysqldump -u用户名 -p 数据库名  表名> 文件名.sql
mysqldump -u用户名 -p 数据库名  表名> 文件名.txt

# ====SQL语句导出数据到文件=======
mysql -uroot -p123456 --default-character-set=utf8
mysql> use 数据库名
mysql> SELECT * FROM EMPLOYEE INTO OUTFILE '/tutorials.txt' 
-> FIELDS TERMINATED BY ',' 
-> OPTIONALLY ENCLOSED BY '"' 
-> LINES TERMINATED BY '\r\n';

参考文章:

SQL教程
SQL语法大全
MySQL学习笔记(1)—— 入门与初步
MySQL学习笔记(2)—— 数据类型和列类型
MySQL学习笔记(3)—— MySQL的安装与配置
MySQL学习笔记(4)—— MySQL 的服务器管理
MySQL学习笔记(5)—— Python 操作 MySQL
MySQL学习笔记(6)—— MySQL 的数据库操作
MySQL学习笔记(7)—— MySQL 查询/更新数据记录

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容