上图sql语句的顺序是什么呢?
答案:执行顺序是下面代码中的数字标记:
(8) SELECT (9)DISTINCT
(1) FROM (3) JOIN (2) ON (4) WHERE (5) GROUP BY (6) WITH {CUBE|ROLLUP} (7) HAVING (10) ORDER BY (11) LIMIT
对FROM子句中的左表
对虚拟表VT1 应用ON筛选,只有那些符合
如果指定了OUTER JOIN保留表中未找到匹配的行将作为外部行添加到虚拟表 VT2,生成虚拟表 VT3。<保留表>如下:
对虚拟表 VT3应用WHERE过滤条件,只有符合
根据GROUP BY 子句中的列,对VT4中的记录进行分组操作,产生VT5.
对表VT5进行CUBE或ROLLUP操作,产生表VT6
对虚拟表VT6应用HAVING过滤,只有符合条件
第二次执行SELECT操作,选择指定的列,插入到虚拟表VT8中
去除重复数据,产生虚拟表VT9
将虚拟表VT9中的记录按照
取出指定行的记录,产生虚拟表VT11,并返回给查询用户。
CREATE TABLE `customers` (
`customer_id` varchar(10) NOT NULL,
`city` varchar(20) NOT NULL,
PRIMARY KEY (`customer_id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
INSERT INTO `customers` VALUES ('Ali','HangZhou'),
('Pinxixi','ShangHai'),
('Baidu','BeiJing'),
('TX','ShenZhen');
# 数据如下
+-------------+----------+
| customer_id | city |
+-------------+----------+
| Ali | HangZhou |
| Pinxixi | ShangHai |
| Baidu | HangZhou |
| TX | HangZhou |
+-------------+----------+
CREATE TABLE `orders` (
`order_id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` varchar(10) DEFAULT NULL,
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1;
INSERT INTO `orders` VALUES
(1,'Ali'),
(2,'Ali'),
(3,'Pinxixi'),
(4,'Pinxixi'),
(5,'Pinxixi'),
(6,'TX'),
(7,NULL);
# 数据如下
+----------+-------------+
| order_id | customer_id |
+----------+-------------+
| 1 | Ali |
| 2 | Ali |
| 3 | Pinxixi |
| 4 | Pinxixi |
| 5 | Pinxixi |
| 6 | TX |
| 7 | NULL |
+----------+-------------+
SELECT c.customer_id, COUNT(o.order_id) AS total_orders
FROM customers AS c
LEFT JOIN orders AS o
ON c.customer_id = o.customer_id
WHERE c.city = 'HangZhou'
GROUP BY c.customer_id
HAVING count(o.order_id) < 2
ORDER BY total_orders DESC;
# 执行结果如下
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| TX | 1 |
| Baidu | 0 |
+-------------+--------------+
第一步需要做的是对FROM子句前后的两张表进行笛卡尔积操作,也称作交叉连接,生成虚拟表VT1。如果FROM子句前的表包含a行数据,FROM子句后的表中包含b行数据,那么虚拟表VT1将包含a*b行数据。对于前面的SQL查询语句,会先执行表orders 和 customers的笛卡尔积操作:
# 即语句 select * from customers,orders; 即可以获取customers表和orders表的笛卡尔积
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Pinxixi | ShangHai | 1 | Ali |
| Baidu | HangZhou | 1 | Ali |
| TX | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| Pinxixi | ShangHai | 2 | Ali |
| Baidu | HangZhou | 2 | Ali |
| TX | HangZhou | 2 | Ali |
| Ali | HangZhou | 3 | Pinxixi |
| Pinxixi | ShangHai | 3 | Pinxixi |
| Baidu | HangZhou | 3 | Pinxixi |
| TX | HangZhou | 3 | Pinxixi |
| Ali | HangZhou | 4 | Pinxixi |
| Pinxixi | ShangHai | 4 | Pinxixi |
| Baidu | HangZhou | 4 | Pinxixi |
| TX | HangZhou | 4 | Pinxixi |
| Ali | HangZhou | 5 | Pinxixi |
| Pinxixi | ShangHai | 5 | Pinxixi |
| Baidu | HangZhou | 5 | Pinxixi |
| TX | HangZhou | 5 | Pinxixi |
| Ali | HangZhou | 6 | TX |
| Pinxixi | ShangHai | 6 | TX |
| Baidu | HangZhou | 6 | TX |
| TX | HangZhou | 6 | TX |
| Ali | HangZhou | 7 | NULL |
| Pinxixi | ShangHai | 7 | NULL |
| Baidu | HangZhou | 7 | NULL |
| TX | HangZhou | 7 | NULL |
+-------------+----------+----------+-------------+
SELECT查询共有3个过滤流程,分别是ON、WHERE、HAVING。ON是最先执行的过滤流程。根据上一小节产生的表VT1,过滤条件: ON c.customer_id = o.customer_id
在大多数的编程语言中,逻辑表达式的值只有两种:TRUE 和 FALSE。但是在关系数据库中起逻辑表达式作用的并非只有两种,还有一种称为三值逻辑的表达式。这是因为在数据库中对NULL值的比较与大多编程语言不同。在C语言中,NULL == NULL的比较返回的是1,即相等,而在关系型数据库中,NULL的比较就不一样了,如下:
mysql> select 1 = NULL\G
+----------+
| 1 = NULL |
+----------+
| NULL |
+----------+
#--------------------------
mysql> select NULL = NULL;
+-------------+
| NULL = NULL |
+-------------+
| NULL |
+-------------+
#-----------------
mysql> select 2 = 2;
+-------+
| 2 = 2 |
+-------+
| 1 |
+-------+
#------------------------------
mysql> select 1 = 2;
+-------+
| 1 = 2 |
+-------+
| 0 |
+-------+
第一个NULL值的比较返回的是NULL而不是0,第二个NULL值的比较返回的仍然是NULL,而不是1.对于比较返回值为NULL的情况,用户应该将其视为UNKNOWN,即表示未知的。因为在某些情况下,NULL返回值可能表示1,即NULL等于NULL,而有时候NULL返回值可能代表0.
对于在ON过滤条件下的NULL值比较,此时的比较结果为UNKNOWN,却被视为FALSE来处理,即两个NULL并不相同。但是在下面两种情况认为两个NULL值的比较是相等的(示例如附录01):
因此在生成虚拟表VT2的时候,会增加一个额外的列来表示ON过滤条件的返回值,返回值有FALSE 、 TRUE 、 UNKNOWN,如下图:
+-------------+-------------+----------+----------+-------------+
| Match | customer_id | city | order_id | customer_id |
+-------------+-------------+----------+----------+-------------+
| TRUE | Ali | HangZhou | 1 | Ali |
| FALSE | Pinxixi | ShangHai | 1 | Ali |
| FALSE | Baidu | HangZhou | 1 | Ali |
| FALSE | TX | HangZhou | 1 | Ali |
| TRUE | Ali | HangZhou | 2 | Ali |
| FALSE | Pinxixi | ShangHai | 2 | Ali |
| FALSE | Baidu | HangZhou | 2 | Ali |
| FALSE | TX | HangZhou | 2 | Ali |
| FALSE | Ali | HangZhou | 3 | Pinxixi |
| TRUE | Pinxixi | ShangHai | 3 | Pinxixi |
| FALSE | Baidu | HangZhou | 3 | Pinxixi |
| FALSE | TX | HangZhou | 3 | Pinxixi |
| FALSE | Ali | HangZhou | 4 | Pinxixi |
| TRUE | Pinxixi | ShangHai | 4 | Pinxixi |
| FALSE | Baidu | HangZhou | 4 | Pinxixi |
| FALSE | TX | HangZhou | 4 | Pinxixi |
| FALSE | Ali | HangZhou | 5 | Pinxixi |
| TRUE | Pinxixi | ShangHai | 5 | Pinxixi |
| FALSE | Baidu | HangZhou | 5 | Pinxixi |
| FALSE | TX | HangZhou | 5 | Pinxixi |
| FALSE | Ali | HangZhou | 6 | TX |
| FALSE | Pinxixi | ShangHai | 6 | TX |
| FALSE | Baidu | HangZhou | 6 | TX |
| TRUE | TX | HangZhou | 6 | TX |
| UNKNOWN | Ali | HangZhou | 7 | NULL |
| UNKNOWN | Pinxixi | ShangHai | 7 | NULL |
| UNKNOWN | Baidu | HangZhou | 7 | NULL |
| UNKNOWN | TX | HangZhou | 7 | NULL |
+-------------+-------------+----------+----------+-------------+
取出比较值为TRUE的记录,产生虚拟表VT2,结果如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| Pinxixi | ShangHai | 3 | Pinxixi |
| Pinxixi | ShangHai | 4 | Pinxixi |
| Pinxixi | ShangHai | 5 | Pinxixi |
| TX | HangZhou | 6 | TX |
+-------------+----------+----------+-------------+
这一步只有在连接类型是OUTER JOIN时才发生,如LEFT OUTER JOIN , RIGHT OUTER JOIN , FULL OUTER JOIN.虽然在大多数时候可以省略OUTER关键字,但OUTER代表的就是外部行。关于保留表:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| Pinxixi | ShangHai | 3 | Pinxixi |
| Pinxixi | ShangHai | 4 | Pinxixi |
| Pinxixi | ShangHai | 5 | Pinxixi |
| TX | HangZhou | 6 | TX |
| Baidu | HangZhou | NULL | NULL |
+-------------+----------+----------+-------------+
对上一步骤产生的虚拟表VT3进行WHERE条件过滤,只有符合
在当前应用WHERE过滤器时,有两种过滤是不被允许的:
如下查询以及产生的虚拟表VT4:
## 应用WHERE过滤,产生虚拟表VT4
SELECT * FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id WHERE c.city='HangZhou';
## 虚拟表VT4数据如下
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| TX | HangZhou | 6 | TX |
| Baidu | HangZhou | NULL | NULL |
+-------------+----------+----------+-------------+
此外,在WHERE过滤器中进行的过滤和在ON过滤器中进行的过滤是有所不同的:
## 应用ON 过滤,仅做举例
SELECT * FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id AND c.city='HangZhou';
## 结果如下:
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| TX | HangZhou | 6 | TX |
| Pinxixi | ShangHai | NULL | NULL |
| Baidu | HangZhou | NULL | NULL |
+-------------+----------+----------+-------------+
# 对比该数据和表VT4中的数据,customer_id为9you的记录被加入到前者的
(当前示例)的查询中, 因为ON过滤条件虽然过滤掉了city不等于'HangZhou'的记录,
但是由于查询是OUTER JOIN ,因此会对保留表中被排除的记录进行再次的添加操作。
在本步骤中根据指定的列对上个步骤中产生的虚拟表进行分组,最后得到虚拟表VT5,如下:
SELECT * FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.city='HangZhou'
GROUP BY c.customer_id;
## 虚拟表VT5数据如下(这是虚拟表,如果在终端执行SQL,实际输出并不是这个):
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
| TX | HangZhou | 6 | TX |
| Baidu | HangZhou | NULL | NULL |
+-------------+----------+----------+-------------+
## 或者将数据整理一下更好理解,如下,将数据分为了三个组,即c.customer_id为163,TX,baidu的数据各为一组
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Ali | HangZhou | 1 | Ali |
| Ali | HangZhou | 2 | Ali |
|------------分组分界线-----------------------------|
| TX | HangZhou | 6 | TX |
|------------分组分界线-----------------------------|
| Baidu | HangZhou | NULL | NULL |
+-------------+----------+----------+-------------+
分组与别名(AS)
MySQL对查询做了加强,使得在GROUP BY 后面可以使用SELECT中定义的别名。如下SQL:
SELECT DATE_FORMAT(column_a,'%Y-%m-%d') AS RiQi , COUNT(*)
FROM table_A
WHERE
GROUP BY ;
这条SQL是可以正常执行的(已测试),结果也是正确的。通过查询资料,得出如下结论:
ROLLUP
如果指定了ROLLUP选项,那么将创建一个额外的记录添加到虚拟表VT5的最后,并生成虚拟表VT6
CUBE
对于CUBE选项,MySQL数据库虽然支持该关键字的解析,但是并未实现功能。
在该步骤中对于上一步产生的虚拟表应用HAVING过滤器,HAVING是对分组条件进行过滤的筛选器,对于示例的查询语句,其分组条件为: HAVING count(o.order_id) < 2,因此将customer_id=163的订单从虚拟表中删除,生成的虚拟表VT7如下:
## SQL语句如下
SELECT * FROM customers c
LEFT JOIN orders o
ON c.customer_id = o.customer_id
WHERE c.city='HangZhou'
GROUP BY c.customer_id
HAVING count(o.order_id) < 2;
## VT6数据如下
+-------------+----------+----------+-------------+
| customer_id | city | order_id | customer_id |
+-------------+----------+----------+-------------+
| Baidu | HangZhou | NULL | NULL |
| TX | HangZhou | 6 | TX |
+-------------+----------+----------+-------------+
需要注意的是,在这个分组中不能使用count(1) 或者 count(*) , 因为这会把通过OUTER JOIN添加的行统计入内而导致最终查询结果和预期不一致。
虽然SELECT是查询中最先被指定的部分,但是知道步骤8时才真正进行处理,在这一步中,将SELECT中指定的列从上一步产生的虚拟表中选出。
如上,示例SQL的SELECT部分为:
最后得到的虚拟表VT8如下
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| Baidu | 0 |
| TX | 1 |
+-------------+--------------+
如果在查询中指定了DISTINCT子句,则会创建一张内存临时表VT9(如果内存放不下就放到磁盘上)。这张临时表的表结构和上一步产生的虚拟表一样,不同的是对进行DISTINCT操作的列增加了一个唯一索引,以此来去除重复的数据。
根据ORDER BY子句中指定的列上对上一步输出的虚拟表进行排列,返回新的虚拟表。对于示例,ORDER BY子句: ORDER BY total_orders DESC ,最后得到的虚拟表VT10:
+-------------+--------------+
| customer_id | total_orders |
+-------------+--------------+
| TX | 1 |
| Baidu | 0 |
+-------------+--------------+
在该步骤中应用LIMIT子句,从上一步骤的虚拟表中选出从指定位置开始的指定行数据。对于没有应用ORDER BY 的LIMIT子句,结果同样可能是无序的,因此LIMIT语句通常和ORDER BY子句一起使用。
Step1. 造数据
# 建表语句
Create Table: CREATE TABLE `t` (
`a` char(5) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1
# 数据
insert into t select 'a';
insert into t select NULL;
insert into t select 'b';
insert into t select 'c';
insert into t select NULL;
+------+
| a |
+------+
| a |
| NULL |
| b |
| c |
| NULL |
+------+
Step2 对表t中的列a进行ORDER BY操作,结果如下
#-------------------------------ORDER BY 之前
mysql> select * from t \G
*************************** 1. row ***************************
a: a
*************************** 2. row ***************************
a: NULL
*************************** 3. row ***************************
a: b
*************************** 4. row ***************************
a: c
*************************** 5. row ***************************
a: NULL
5 rows in set (0.00 sec)
#-------------------------------ORDER BY 之后
mysql> select * from t order by a \G
*************************** 1. row ***************************
a: NULL
*************************** 2. row ***************************
a: NULL
*************************** 3. row ***************************
a: a
*************************** 4. row ***************************
a: b
*************************** 5. row ***************************
a: c
5 rows in set (0.00 sec)
Step3. 对表t中的列a进行GROUP BY操作,结果如下
*************************** 1. row ***************************
a: NULL
*************************** 2. row ***************************
a: a
*************************** 3. row ***************************
a: b
*************************** 4. row ***************************
a: c
4 rows in set (0.00 sec)
Step4. 总结
可见,对于ORDER BY的SQL查询语句,返回结果将两个NULL值先返回并排列在一起,对于GROUP BY的查询语句,返回NULL值的有两条记录
您可能感兴趣的:
存储过程批量创建数据案例--mysql索引如何优化搭建MySQL主从架构--详细学习笔记和搞懂MySQL读写分离
Redis详细总结笔记
MySQL进阶--存储引擎InnoDB与索引