mysql-4:SQL的解析顺序

SQL语句的解析顺序

编写顺序与解析顺序

  • 编写顺序
SELECT DISTINCT
< select_list >
FROM
< left_table > < join_type >
JOIN < right_table > ON < join_condition >
WHERE
< where_condition >
GROUP BY
< group_by_list >
HAVING
< having_condition >
ORDER BY
< order_by_condition >
LIMIT < limit_number >
  • 解析顺序
-- 行过滤
1 FROM <left_table>
2 ON <join_condition>
3 <join_type> JOIN <right_table> 第二步和第三步会循环执行
4 WHERE <where_condition> 第四步会循环执行,多个条件的执行顺序是从左往右的。
5 GROUP BY <group_by_list>
6 HAVING <having_condition>
--列过滤
7 SELECT 分组之后才会执行SELECT
8 DISTINCT <select_list>
--排序
9 ORDER BY <order_by_condition>
-- MySQL附加
10 LIMIT <limit_number>9步都是SQL92标准语法。limit是MySQL的独有语法。

解析顺序关键字

FROM

  • 对FROM的左边的表和右边的表计算 笛卡尔积(CROSS JOIN) 。产生虚表VT1:
mysql> select * from product,category;
+-----+-----------------+-------+-------+------+----+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+-----------------+-------+-------+------+----+--------------+
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 1 | 国外食品 |
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 2 | 国内食品 |
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 3 | 国内服装 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 2 | 国内食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 3 | 国内服装 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 1 | 国外食品 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 2 | 国内食品 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 3 | 国内服装 |
| 4 | 十三香 | 10 | NULL | 2 | 1 | 国外食品 |
| 4 | 十三香 | 10 | NULL | 2 | 2 | 国内食品 |
| 4 | 十三香 | 10 | NULL | 2 | 3 | 国内服装 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 1 | 国外食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 3 | 国内服装 |
| 6 | 泰国大枣 | 98 | NULL | 20 | 1 | 国外食品 |
| 6 | 泰国大枣 | 98 | NULL | 20 | 2 | 国内食品 |
| 6 | 泰国大枣 | 98 | NULL | 20 | 3 | 国内服装 |
| 7 | iPhone手机 | 800 | NULL | 30 | 1 | 国外食品 |
| 7 | iPhone手机 | 800 | NULL | 30 | 2 | 国内食品 |
| 7 | iPhone手机 | 800 | NULL | 30 | 3 | 国内服装 |
+-----+-----------------+-------+-------+------+----+--------------+
21 rows in set (0.00 sec)

通过在 FROM 子句中列出多个表格,你可以执行一个叫做交叉连接(Cross Join)的操作,它会将product,category两个表的每一行进行组合,生成一个包含所有可能组合的结果集。

ON

  • 对虚表VT1进行ON筛选,只有那些符合的行才会被记录在虚表VT2中:
mysql> select * from product a, category b where a.cid=b.id;
+-----+-----------------+-------+-------+------+----+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+-----------------+-------+-------+------+----+--------------+
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 1 | 国外食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 2 | 国内食品 |
| 4 | 十三香 | 10 | NULL | 2 | 2 | 国内食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
+-----+-----------------+-------+-------+------+----+--------------+
5 rows in set (0.00 sec)

OUTER JOIN

  • 如果指定了 OUTER JOIN(比如left join、 right join) ,那么保留表中未匹配的行就会作为外部行添加到虚拟表VT2 中,产生虚拟表VT3
  • 如果FROM子句中包含两个以上的表的话,那么就会对上一个join连接产生的结果VT3和下一个表重复执行步骤1~3这三个步骤,一直到处理完所有的表为止
  • 以左表数据为准
mysql> select * from product a left outer join category b on a.cid=b.id; 
+-----+-----------------+-------+-------+------+------+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+-----------------+-------+-------+------+------+--------------+
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 1 | 国外食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 2 | 国内食品 |
| 4 | 十三香 | 10 | NULL | 2 | 2 | 国内食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| 6 | 泰国大枣 | 98 | NULL | 20 | NULL | NULL |
| 7 | iPhone手机 | 800 | NULL | 30 | NULL | NULL |
+-----+-----------------+-------+-------+------+------+--------------+
7 rows in set (0.00 sec)
  • 以右表数据为准
mysql> select * from product a right outer join category b on a.cid=b.id; 
+------+-----------------+-------+-------+------+----+--------------+
| pid | pname | price | pdate | cid | id | cname |
+------+-----------------+-------+-------+------+----+--------------+
| 1 | 泰国大榴莲 | 98 | NULL | 1 | 1 | 国外食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 3 | 新疆切糕 | 68 | NULL | 2 | 2 | 国内食品 |
| 4 | 十三香 | 10 | NULL | 2 | 2 | 国内食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| NULL | NULL | NULL | NULL | NULL | 3 | 国内服装 |
+------+-----------------+-------+-------+------+----+--------------+
6 rows in set (0.00 sec)

WHERE

  • 对虚拟表VT3 进行WHERE条件过滤。只有符合的记录才会被插入到虚拟表VT4 中

此时因为分组,不能使用聚合运算;也不能使用SELECT中创建的别名;

  • 与ON的区别
    • 如果有外部列,ON针对过滤的是关联表,主表(保留表)会返回所有的列;
    • 如果没有添加外部列,两者的效果是一样的;
  • 应用
    • 对主表的过滤应该放在WHERE;
    • 对于关联表,先条件查询后连接则用ON,先连接后条件查询则用WHERE;
mysql> select * from product a left outer join category b on a.cid=b.id where
a.pname='泰国大枣';
+-----+--------------+-------+-------+------+------+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+--------------+-------+-------+------+------+--------------+
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| 6 | 泰国大枣 | 98 | NULL | 20 | NULL | NULL |
+-----+--------------+-------+-------+------+------+--------------+
3 rows in set (0.00 sec)

GROUP BY

  • 根据group by子句中的列,对VT4中的记录进行分组操作,产生虚拟表VT5。

注意:其后处理过程的语句,如SELECT,HAVING,所用到的列必须包含在GROUP BY中。对于没有出现的,得用聚合函数;

  • 原因:
    • GROUP BY改变了对表的引用,将其转换为新的引用方式,能够对其进行下一级逻辑操作的列会减少;
  • 理解
    • 根据分组字段,将具有相同分组字段的记录归并成一条记录,因为每一个分组只能返回一条记录,除非是被过滤掉了,而不在分组字段里面的字段可能会有多个值,多个值是无法放进一条记录的,所以必须通过聚合函数将这些具有多值的列转换成单值;
mysql> select * from product a left outer join category b on a.cid=b.id where
a.pname='泰国大枣' group by a.price;
+-----+--------------+-------+-------+------+------+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+--------------+-------+-------+------+------+--------------+
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 6 | 泰国大枣 | 98 | NULL | 20 | NULL | NULL |
+-----+--------------+-------+-------+------+------+--------------+
3 rows in set (0.01 sec)

HAVING

  • 对虚拟表VT5 应用having过滤,只有符合的记录才会被插入到虚拟表VT6 中。
mysql> select * from product a left outer join category b on a.cid=b.id where
a.pname='泰国大枣' group by a.price having b.id <=2;
+-----+--------------+-------+-------+------+------+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+--------------+-------+-------+------+------+--------------+
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
+-----+--------------+-------+-------+------+------+--------------+
2 rows in set (0.00 sec)

SELECT

  • 这个子句对SELECT子句中的元素进行处理,生成VT5表。
  • (5-J1)计算表达式计算SELECT 子句中的表达式,生成VT5-J1

DISTINCT

  • 寻找VT5-1中的重复列,并删掉,生成VT5-J2
  • 如果在查询中指定了DISTINCT子句,则会创建一张内存临时表(如果内存放不下,就需要存放在硬盘了)。这张临时表的表结构和上一步产生的虚拟表VT5是一样的,不同的是对进行DISTINCT操作的列增
    加了一个唯一索引,以此来除重复数据。
mysql> select distinct a.pname from product a left outer join category b on
a.cid=b.id where a.pname='泰国大枣' group by a.price ;
+--------------+
| pname |
+--------------+
| 泰国大枣 |
+--------------+
1 row in set (0.00 sec)

ORDER BY

  • 从 VT5-J2 中的表中,根据ORDER BY 子句的条件对结果进行排序,生成VT6表

唯一可使用SELECT中别名的地方

mysql> select * from product a left outer join category b on a.cid=b.id where
a.pname='泰国大枣' group by a.price having b.id <=2 order by b.id;
+-----+--------------+-------+-------+------+------+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+--------------+-------+-------+------+------+--------------+
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
| 5 | 泰国大枣 | 20 | NULL | 2 | 2 | 国内食品 |
+-----+--------------+-------+-------+------+------+--------------+
2 rows in set (0.00 sec)

LIMIT

  • LIMIT子句从上一步得到的 VT6虚拟表 中选出从指定位置开始的指定行数据

MySQL特有
offset 和 rows 的正负带来的影响;

  • 当偏移量很大时效率是很低的,可以这么做:
    • 采用子查询的方式优化 ,在子查询里先从索引获取到最大id,然后倒序排,再取N行结果集
    • 采用INNER JOIN优化 ,JOIN子句里也优先从索引获取ID列表,然后直接关联查询获得最终结果
mysql> select * from product a left outer join category b on a.cid=b.id where
a.pname='泰国大枣' group by a.price having b.id <=2 order by b.id limit 1;
+-----+--------------+-------+-------+------+-
-----+--------------+
| pid | pname | price | pdate | cid | id | cname |
+-----+--------------+-------+-------+------+------+--------------+
| 2 | 泰国大枣 | 38 | NULL | 1 | 1 | 国外食品 |
+-----+--------------+-------+-------+------+------+--------------+
1 row in set (0.00 sec)

解析流程

在这里插入图片描述

流程分析

    1. FROM(将最近的两张表,进行笛卡尔积)—VT1
    1. ON(将VT1按照它的条件进行过滤)—VT2
    1. LEFT JOIN(保留左表的记录)—VT3
    1. WHERE(过滤VT3中的记录)–VT4…VTn
    1. GROUP BY(对VT4的记录进行分组)—VT5
    1. HAVING(对VT5中的记录进行过滤)—VT6
    1. SELECT(对VT6中的记录,选取指定的列)–VT7
    1. ORDER BY(对VT7的记录进行排序)–VT8
    1. LIMIT(对排序之后的值进行分页)–MySQL特有的语法
流程说明
  • 单表查询:根据 WHERE 条件过滤表中的记录,形成中间表(这个中间表对用户是不可见的);然后根据 SELECT 的选择列选择相应的列进行返回最终结果。
  • 两表连接查询:对两表求积(笛卡尔积)并用 ON 条件和连接类型进行过滤形成中间表;然后根据WHERE条件过滤中间表的记录,并根据 SELECT 指定的列返回查询结果。

笛卡尔积:行相乘、列相加。

  • 多表连接查询:先对第一个和第二个表按照两表连接做查询,然后用查询结果和第三个表做连接查询,以此类推,直到所有的表都连接上为止,最终形成一个中间的结果表,然后根据WHERE条件过滤中间表的记录,并根据SELECT指定的列返回查询结果。
WHERE条件解析顺序
  • MySQL :从左往右去执行 WHERE 条件的。
  • Oracle :从右往左去执行 WHERE 条件的。

写WHERE条件的时候,优先级高的部分要去编写过滤力度最大的条件语句

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值