SQL语法
结构化查询语言(Structured Query Language)简称SQL,是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询、更新和管理关系数据库系统。
基础语法
数据库表
一个数据库通常包含一个或多个表。每个表有一个名字标识(例如:“Websites”),表包含带有数据的记录(行)。
在本教程中,我们在 MySQL 的 RUNOOB 数据库中创建了 Websites 表,用于存储网站记录。
我们可以通过以下命令查看 “Websites” 表的数据:
mysql> use RUNOOB;
Database changed
mysql> set names utf8;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM Websites;
+----+--------------+---------------------------+-------+---------+
| id | name | url | alexa | country |
+----+--------------+---------------------------+-------+---------+
| 1 | Google | https://www.google.cm/ | 1 | USA |
| 2 | 淘宝 | https://www.taobao.com/ | 13 | CN |
| 3 | 菜鸟教程 | http://www.runoob.com/ | 4689 | CN |
| 4 | 微博 | http://weibo.com/ | 20 | CN |
| 5 | Facebook | https://www.facebook.com/ | 3 | USA |
+----+--------------+---------------------------+-------+---------+
5 rows in set (0.01 sec)
SQL 语句
所有SQL 语句SQL语句都以:SELECT,INSERT,UPDATE,DELETE,ALTER,DROP,CREATE,USE
,SHOW
等任何关键字开头,所有语句都以分号(;
)结尾。
- SQL大小写( show Variables like '%table_names’查看lower_case_table_names的值,0代表区分,1代表不区分。Linux下mysql默认区分大小写,Windows下mysql默认不区分大小写)。
- SQL语句依赖于文本行,可以在一个或多个文本行上放置一个SQL语句。
select
column1
,column2
from
databases.table_name
;
- SQL依赖于关系代数和元组关系演算。
- 大多数数据库系统要求在每条 SQL 语句的末端使用分号。
- 分号是在数据库系统中分隔每条 SQL 语句的标准方法,这样就可以在对服务器的相同请求中执行一条以上的 SQL 语句。
基础操作
1.增
插入数据:
INSERT INTO table_name( column1, column2....columnN)
VALUES ( value1, value2....valueN);
创建表:
CREATE DATABASE database_name;
2.删
删表数据:
DELETE FROM table_name
WHERE {CONDITION};
TRUNCATE TABLE database_name;(不可回滚,清空,不能加条件)
删表:
DROP DATABASE database_name;
3.改
改数据:
UPDATE table_name
SET column1 = value1, column2 = value2....columnN=valueN
[ WHERE CONDITION ];
改表名:
ALTER TABLE table_name RENAME TO new_table_name;
改/增加字段:
ALTER TABLE table_name {ADD|DROP|MODIFY} column_name {data_ype};
4.查
SELECT column1, column2....columnN
FROM table_name;
复杂SQL语法
关联
内关联(内连接)
inner join
table1 table2
col1 col2 col1 col2
a 1 a 1
b 1 b 2
a 2
c 2
select
a.col1
,b.col2
from
table1 a
inner join
table2 b
on a.col1 = b.col1
只输出字段匹配上的行
col1 col2
a 1
b 2
a 1
外关联(外连接)
1.左外关联
left join
table1 table2
col1 col2 col1 col2
a 1 a 1
b 1 b 2
a 2 d 1
c 2
select
a.col1
,a.col2
,b.col2 as col3
from
table1 a
left join
table2 b
on a.col1 = b.col1
以left左边的表做为主表,主表的数据都保存,没关联上的数据以null补充
col1 col2 col3
a 1 1
b 1 2
a 2 1
c 2 null
2.右外关联
right join
table1 table2
col1 col2 col1 col2
a 1 a 1
b 1 b 2
a 2 d 1
c 2
select
a.col1
,a.col2
,b.col2 as col3
from
table1 a
right join
table2 b
on a.col1 = b.col1
以right右边的表做为主表,主表的数据都保存,没关联上的数据以null补充
col1 col2 col3
a 1 1
b 1 2
a 2 1
null null 1
联合查询 ***
union union all
table1 table2
col1 col2 col3 col4
a 1 a 1
b 1 b 2
a 2 d 1
c 2
select
a.col1
,a.col2
from
table1 a
union all
select
b.col1
,b.col2
from
table2 b
强硬的行拼接:字段数必须相同,字段名以第一个表的字段为准
col1 col2
a 1
b 1
a 2
c 2
a 1
b 2
d 1
union 的区别在于,union会消除表中的重复行
col1 col2
a 1
b 1
a 2
c 2
b 2
d 1
交叉连接 (笛卡尔积) ***
cross join
比较消耗资源,数据量大的话要谨慎使用
table1 table2
col1 col2 col3 col4
a 1 a 1
c 2 d 1
select
a.col1
,a.col2
,b.col3
,b.col4
from
table1 a
union all
table2 b
将所有行做交叉关联
col1 col2 col3 col4
a 1 a 1
c 2 a 1
a 1 d 1
c 2 d 1
“全关联”
mysql不支持全关联, 可以使用 LEFT JOIN 和 UNION 和 RIGHT JOIN 联合使用。
子查询 ***
子查询分类
子查询可以按照两种方式进行分类。若按照期望的数量,可以将子查询分为标量子查询和多值子查询;若按查询对外部查询的依赖,可分为独立子查询(self-contained subquery)和相关子查询(correlated subquery)。标量子查询和多值子查询可以是独立子查询,也可以是相关子查询。
其他说法:
子查询按返回结果集的不同分为4种:表子查询,行子查询,列子查询和标量子查询。
表子查询:返回的结果集是一个行的集合,N行N列(N>=1)。表子查询经常用于父查询的FROM子句中。
行子查询:返回的结果集是一个列的集合,一行N列(N>=1)。行子查询可以用于福查询的FROM子句和WHERE子句中。
列子查询:返回的结果集是一个行的集合,N行一列(N>=1)。
标量子查询:返回的结果集是一个标量集合,一行一列,也就是一个标量值。可以指定一个标量表达式的任何地方,都可以用一个标量子查询。从定义上讲,每个标量子查询也是一个行子查询和一个列子查询,反之则不是;每个行子查询和列子查询也是一个表子查询,反之也不是。
按照对返回结果的调用方法
子查询按对返回结果集的调用方法,可分为where型子查询,from型子查询及exists型子查询。
where型子查询:(把内层查询结果当作外层查询的比较条件)
定义:where型的子查询就是把内层查询的结果当作外层查询的条件。
from型子查询:(把内层的查询结果供外层再次查询)
定义:from子查询就是把子查询的结果(内存里的一张表)当作一张临时表,然后再对它进行处理。
exists型子查询:(把外层查询结果拿到内层,看内层的查询是否成立)
定义:exists子查询就是对外层表进行循环,再对内表进行内层查询。和in ()差不多,但是它们还是有区别的。主要是看两个张表大小差的程度。若子查询表大则用exists(内层索引),子查询表小则用in(外层索引);
使用子查询规则
1.一个子查询必须放在圆括号中。
2.将子查询放在比较条件的右边以增加可读性。子查询不包含 ORDER BY 子句。对一个 SELECT 语句只能用一个 ORDER BY 子句,并且如果指定了它就必须放在主 SELECT 语句的最后。
子查询是指在一个SELECT语句中嵌套另一个SELECT语句。
MYSQL数据库从4.1版本开始支持子查询,并且支持所有SQL标准的子查询,也扩展了一些其独有的子查询标准。下面是一个子查询:
SELECT
*
FROM
t1
WHERE
colimn1 =
(
SELECT
column1
FROM
t2
);
在这个实例中,SELECT * FROM t1 是外部查询(outer query),SELECT column1 FROM t2是子查询。一般来说,称子查询嵌套(nested)于外部查询中。实际也可以将两个或两个以上的子查询进行嵌套。需要注意的是,子查询必须包含括号。
在这个实例中,SELECT * FROM t1 是外部查询(outer query),SELECT column1 FROM t2是子查询。一般来说,称子查询嵌套(nested)于外部查询中。实际也可以将两个或两个以上的子查询进行嵌套。需要注意的是,子查询必须包含括号。
一个子查询会返回一个标量(单一值)、一个行、一个列或一个表(一行或多行及一列或多列),这些子查询被称为标量、列、行和表子查询。可返回一个特定种类结果的子查询经常只能用于特定的语境中。子查询可以包括普通SELECT可以包含的任何关键词或子句,如DISTINCT、GROUP BY、ORDER BY、LIMIT、JOIN、UNION等。
独立子查询
独立子查询是不依赖外部查询而运作的子查询。与相关子查询相比,独立子查询更接近自然语言(英语),便于SQL语句的调试。
标量子查询可以出现在查询中希望产生标量值的任何地方,而多值子查询可以出现在查询中希望产生多值集合的任何地方。只要标量子查询返回的是单个值或NULL值,就说明该子查询是有效的。
select
*
from
table1
where id=
(
select
id
from
table2
where id=3 -- 多值子查询就是没有这个筛选条件,返回的值为多个
)
大多数情况下,MYSQL数据库都将独立子查询转换为相关子查询。(可通过
explain
关键字来查看语句的执行计划)InnoSQL数据库支持在慢查询日志中记录InnoDB的逻辑IO和物理IO的次数(物理IO就是实际读取磁盘的次数),故开启IO记录,可在慢查询日志中查看
运算符EXISTS与NOT EXISTS
运算符EXISTS
和NOT EXISTS
只会测试某个子查询是否返回了行。如果有返回,则EXISTS的结果为真,而NOT EXISTS的结果为假。
在使用EXISTS和NOT EXISTS 时,子查询通常将“ * ”用作输出列的列表。因为这两个运算符是根据子查询是否返回了行来判断真假的,并不关心行所包含的具体内容,所以没必要显式地列出列名。事实上,可以在子查询的列选取列表里编写任何东西,但如果想要确保在子查询成功时返回一个真值,则可以把它写成 SELECT 1,而不要写成 SELECT *。
相关子查询
相关子查询通常用在EXISTS 和 NOT EXISTS 子查询里,这类子查询主要用于在某个表里查找在另一个表里有匹配行或没有匹配行的表。
相关子查询的工作原理是:把值从外层查询传递到子查询,并检查它们是否满足子查询里指定的那些条件。 因此,如果列名会引起歧义(在多个表里有同名列),那么必须使用表名来限定这些列名。(与join在语法层面的差别,看个人使用习惯)
select
*
from
table2 a
where exists(
select
id
from
table1 b
where
a.id >= b.id
)
ANY、IN和SOME,ALL,<>
ANY关键词必须与一个比较符一起使用。ANY关键词的意思是“对于子查询返回的列中的任一数值,如果比较结果为TRUE,则返回TRUE”。 SOME,IN和ANY作用是一致的。
词语ALL必须与比较操作符一起使用。ALL的意思是“对于子查询返回的列中的所有值,如果比较结果为TRUE,则返回TRUE”
select
*
from
table2 a
where
a.id >ALL(select id from table1 b)
<>就是!= ,可以相互替换
关于NULL的比较问题
对于大多数的编程语言而言,逻辑表达式的值只能有两种:TRUE和FALSE。但是在关系数据库中起逻辑表达式作用的并非只有两种,还有一种称为三值逻辑的表达式。这是因为在数据库中对NULL值的比较与大多数编程语言不同。在C语言中,NULL==NULL的比较返回是1,即相等,而在关系数据库中,NULL的比较则完全不是那么回事。
第一个NULL值的比较返回的是NULL而不是0,第二个NULL值的比较返回的仍然是NULL,而不是1。对于比较返回值为NULL的情况,用户应该将其视为UNKNOWN,即表示未知的。因为在某些情况下,NULL返回值可能代表1,即NULL等于NULL,而有时NULL返回值可能代表0。
对于ON过滤条件下的NULL值比较,此时的比较结果为UNKNOWN,却被视为FALSE来进行处理,即两个NULL并不相同。但是在下面两种情况下认为两个NULL值的比较是相等的:
GROUP BY 子句把所有NULL值分到同一组。
ORDER BY 子句把所有NULL值排列在一起。
集合
集合在数据库中表示记录的集合。表、视图和查询的执行结果都是记录的集合。所谓集合运算,就是对满足同一规则的记录进行的加减等四则运算。通过集合运算,可以得到两张表中的记录的集合或者公共记录的集合,又或者其中某张表中的记录的集合。
集合运算就是指上面关联的内容。
选取表中的公共部分–intersect(交集)
MySQL中不支持交集运算
对应的SQL语句应该是:
SELECT * FROM Student
WHERE Sdept='CS'
INTERSECT
SELECT * FROM Student
WHERE Sage<=19;
mysql中实现相关替代的SQL语句:
SELECT *
FROM student
WHERE Sdept = 'CS'
AND Sage <=19
记录的减法–except(差集)
MySQL中不支持差集运算
同交集一样可以通过逻辑语句实现
并集:union 和 union all
窗口函数 ***
(mysql 5.7及以前版本没有)
窗口
:记录集合窗口函数
:在满足某些条件的记录集合上执行的特殊函数,对于每条记录都要在此窗口内执行函数。有的函数随着记录的不同,窗口大小都是固定的,称为静态窗口
;有的函数则相反,不同的记录对应着不同的窗口,称为滑动窗口
。
和普通聚合函数的区别:
①聚合函数是将多条记录聚合为一条;窗口函数是每条记录都会执行,有几条记录执行完还是几条。
②聚合函数也可以用于窗口函数。
基本用法:
函数名 OVER()
over关键字用来指定函数执行的窗口范围,若后面括号中什么都不写,则意味着窗口包含满足WHERE条件的所有行,窗口函数基于所有行进行计算;如果不为空,则支持以下4中语法来设置窗口。
①window_name:给窗口指定一个别名。如果SQL中涉及的窗口较多,采用别名可以看起来更清晰易读;
②PARTITION BY 子句:窗口按照哪些字段进行分组,窗口函数在不同的分组上分别执行;
③ORDER BY子句:按照哪些字段进行排序,窗口函数将按照排序后的记录顺序进行编号;
④FRAME子句:FRAME是当前分区的一个子集,子句用来定义子集的规则,通常用来作为滑动窗口使用。
分类
按功能划分可将MySQL支持的窗口函数分为如下几类:
序号函数
ROW_NUMBER()
、RANK()
、DENSE_RANK()
ROW_NUMBER() OVER (PARTITION BY stu_id ORDER BY score)
mysql> SELECT *
-> FROM(
-> SELECT stu_id
-> ,ROW_NUMBER() OVER (PARTITION BY stu_id ORDER BY score DESC) AS score_order
-> ,lesson_id
-> , score
-> FROM t_score) t
-> WHERE score_order <= 3
-> ;
+--------+-------------+-----------+-------+
| stu_id | score_order | lesson_id | score |
+--------+-------------+-----------+-------+
| 1 | 1 | L005 | 98 |
| 1 | 2 | L001 | 98 |
| 1 | 3 | L004 | 88 |
| 2 | 1 | L002 | 90 |
| 2 | 2 | L003 | 86 |
| 2 | 3 | L001 | 84 |
| 3 | 1 | L001 | 100 |
| 3 | 2 | L002 | 91 |
| 3 | 3 | L003 | 85 |
| 4 | 1 | L001 | 99 |
| 4 | 2 | L005 | 98 |
| 4 | 3 | L002 | 88 |
+--------+-------------+-----------+-------+
对于stu_id=1的同学,有两门课程的成绩均为98,序号随机排了1和2。但很多情况下二者应该是并列第一,则他的成绩为88的这门课的序号可能是第2名,也可能为第3名。
这时候,ROW_NUMBER()就不能满足需求,需要RANK()和DENSE_RANK()出场,它们和ROW_NUMBER()非常类似,只是在出现重复值时处理逻辑有所不同。
mysql> SELECT *
-> FROM(
-> SELECT
-> ROW_NUMBER() OVER (PARTITION BY stu_id ORDER BY score DESC) AS score_order1,
-> RANK() OVER (PARTITION BY stu_id ORDER BY score DESC) AS score_order2,
-> DENSE_RANK() OVER (PARTITION BY stu_id ORDER BY score DESC) AS score_order3,
-> stu_id, lesson_id, score
-> FROM t_score) t
-> WHERE stu_id = 1 AND score_order1 <= 3 AND score_order2 <= 3 AND score_order3 <= 3
-> ;
+--------------+--------------+--------------+--------+-----------+-------+
| score_order1 | score_order2 | score_order3 | stu_id | lesson_id | score |
+--------------+--------------+--------------+--------+-----------+-------+
| 1 | 1 | 1 | 1 | L005 | 98 |
| 2 | 1 | 1 | 1 | L001 | 98 |
| 3 | 3 | 2 | 1 | L004 | 88 |
+--------------+--------------+--------------+--------+-----------+-------+
ROW_NUMBER():顺序排序——1、2、3
RANK():并列排序,跳过重复序号——1、1、3
DENSE_RANK():并列排序,不跳过重复序号——1、1、2
分布函数(不太常用)
PERCENT_RANK()
、CUME_DIST()
PERCENT_RANK() 百分位数排名
用途:每行按照公式(rank-1) / (rows-1)进行计算。其中,rank为RANK()函数产生的序号,rows为当前窗口的记录总行数给窗口指定别名:WINDOW w AS (PARTITION BY stu_id ORDER BY score)
rows = 5
mysql> SELECT
-> RANK() OVER(PARTITION BY stu_id ORDER BY score) AS rk,
-> PERCENT_RANK() OVER(PARTITION BY stu_id ORDER BY score) AS prk,
-> stu_id, lesson_id, score
-> FROM t_score
-> WHERE stu_id = 1
-> ;
+----+------+--------+-----------+-------+
| rk | prk | stu_id | lesson_id | score |
+----+------+--------+-----------+-------+
| 1 | 0 | 1 | L003 | 79 |
| 2 | 0.25 | 1 | L002 | 86 |
| 3 | 0.5 | 1 | L004 | 88 |
| 4 | 0.75 | 1 | L005 | 98 |
| 4 | 0.75 | 1 | L001 | 98 |
+----+------+--------+-----------+-------+
CUME_DIST()
用途:分组内小于、等于当前rank值的行数 / 分组内总行数
应用场景:查询小于等于当前成绩(score)的比例
cd1:没有分区,则所有数据均为一组,总行数为8
cd2:按照lesson_id分成了两组,行数各为4
mysql> SELECT stu_id, lesson_id, score,
-> CUME_DIST() OVER (ORDER BY score) AS cd1,
-> CUME_DIST() OVER (PARTITION BY lesson_id ORDER BY score) AS cd2
-> FROM t_score
-> WHERE lesson_id IN ('L001','L002')
-> ;
+--------+-----------+-------+-------+------+
| stu_id | lesson_id | score | cd1 | cd2 |
+--------+-----------+-------+-------+------+
| 2 | L001 | 84 | 0.125 | 0.25 |
| 1 | L001 | 98 | 0.75 | 0.5 |
| 4 | L001 | 99 | 0.875 | 0.75 |
| 3 | L001 | 100 | 1 | 1 |
| 1 | L002 | 86 | 0.25 | 0.25 |
| 4 | L002 | 88 | 0.375 | 0.5 |
| 2 | L002 | 90 | 0.5 | 0.75 |
| 3 | L002 | 91 | 0.625 | 1 |
+--------+-----------+-------+-------+------+
前后函数
LAG(expr,n)
、LEAD(expr,n)
- 用途:返回位于当前行的前n行(
LAG(expr,n)
)或后n行(LEAD(expr,n)
)的expr的值 - 应用场景:查询前1名同学的成绩和当前同学成绩的差值,同比环比
内层SQL先通过
LAG()函数
得到前1名同学的成绩,外层SQL再将当前同学和前1名同学的成绩做差得到成绩差值diff
。
mysql> SELECT stu_id, lesson_id, score, pre_score,
-> score-pre_score AS diff
-> FROM(
-> SELECT stu_id, lesson_id, score,
-> LAG(score,1) OVER (PARTITION BY lesson_id ORDER BY score) AS pre_score
-> FROM t_score
-> WHERE lesson_id IN ('L001','L002'))t
-> ;
+--------+-----------+-------+-----------+------+
| stu_id | lesson_id | score | pre_score | diff |
+--------+-----------+-------+-----------+------+
| 2 | L001 | 84 | NULL | NULL |
| 1 | L001 | 98 | 84 | 14 |
| 4 | L001 | 99 | 98 | 1 |
| 3 | L001 | 100 | 99 | 1 |
| 1 | L002 | 86 | NULL | NULL |
| 4 | L002 | 88 | 86 | 2 |
| 2 | L002 | 90 | 88 | 2 |
| 3 | L002 | 91 | 90 | 1 |
+--------+-----------+-------+-----------+------+
其它函数
NTH_VALUE(expr, n)
、NTILE(n)
NTH_VALUE(expr,n)
- 用途:返回窗口中第n个expr的值。expr可以是表达式,也可以是列名
- 应用场景:截止到当前成绩,显示每个同学的成绩中排名第2和第3的成绩的分数
mysql> SELECT stu_id, lesson_id, score,
-> NTH_VALUE(score,2) OVER (PARTITION BY stu_id ORDER BY score) AS second_score,
-> NTH_VALUE(score,3) OVER (PARTITION BY stu_id ORDER BY score) AS third_score
-> FROM t_score
-> WHERE stu_id IN (1,2)
-> ;
+--------+-----------+-------+--------------+-------------+
| stu_id | lesson_id | score | second_score | third_score |
+--------+-----------+-------+--------------+-------------+
| 1 | L003 | 79 | NULL | NULL |
| 1 | L002 | 86 | 86 | NULL |
| 1 | L004 | 88 | 86 | 88 |
| 1 | L001 | 98 | 86 | 88 |
| 1 | L005 | 98 | 86 | 88 |
| 2 | L004 | 75 | NULL | NULL |
| 2 | L005 | 77 | 77 | NULL |
| 2 | L001 | 84 | 77 | 84 |
| 2 | L003 | 86 | 77 | 84 |
| 2 | L002 | 90 | 77 | 84 |
+--------+-----------+-------+--------------+-------------+
NTILE(n)
用途:将分区中的有序数据分为n个等级,记录等级数
应用场景:将每门课程按照成绩分成3组
mysql> SELECT
-> NTILE(3) OVER (PARTITION BY lesson_id ORDER BY score) AS nf,
-> stu_id, lesson_id, score
-> FROM t_score
-> WHERE lesson_id IN ('L001','L002')
-> ;
+------+--------+-----------+-------+
| nf | stu_id | lesson_id | score |
+------+--------+-----------+-------+
| 1 | 2 | L001 | 84 |
| 1 | 1 | L001 | 98 |
| 2 | 4 | L001 | 99 |
| 3 | 3 | L001 | 100 |
| 1 | 1 | L002 | 86 |
| 1 | 4 | L002 | 88 |
| 2 | 2 | L002 | 90 |
| 3 | 3 | L002 | 91 |
+------+--------+-----------+-------+
NTILE(n)函数在数据分析中应用较多,比如由于数据量大,需要将数据平均分配到n个并行的进程分别计算,此时就可以用NTILE(n)对数据进行分组(由于记录数不一定被n整除,所以数据不一定完全平均),然后将不同桶号的数据再分配。
聚合函数作为窗口函数
- 用途:在窗口中每条记录动态地应用聚合函数(SUM()、AVG()、MAX()、MIN()、COUNT()),可以动态计算在指定的窗口内的各种聚合函数值
- 应用场景:截止到当前时间,查询stu_id=1的学生的累计分数、分数最高的科目、分数最低的科目
mysql> SELECT stu_id, lesson_id, score, create_time,
-> SUM(score) OVER (PARTITION BY stu_id ORDER BY create_time) AS score_sum,
-> MAX(score) OVER (PARTITION BY stu_id ORDER BY create_time) AS score_max,
-> MIN(score) OVER (PARTITION BY stu_id ORDER BY create_time) AS score_min
-> FROM t_score
-> WHERE stu_id = 1
-> ;
+--------+-----------+-------+-------------+-----------+-----------+-----------+
| stu_id | lesson_id | score | create_time | score_sum | score_max | score_min |
+--------+-----------+-------+-------------+-----------+-----------+-----------+
| 1 | L001 | 98 | 2018-08-08 | 184 | 98 | 86 |
| 1 | L002 | 86 | 2018-08-08 | 184 | 98 | 86 |
| 1 | L003 | 79 | 2018-08-09 | 263 | 98 | 79 |
| 1 | L004 | 88 | 2018-08-10 | 449 | 98 | 79 |
| 1 | L005 | 98 | 2018-08-10 | 449 | 98 | 79 |
+--------+-----------+-------+-------------+-----------+-----------+-----------+