某天的工作是修复某个项目的bug,接着就发现,其sql极其混乱,有非常多的left join和in操作,还有嵌套查询(只有一个表的嵌套查询)。不知道看到过哪里的资料说,嵌套查询速度慢,于是我把全部嵌套查询都改成join的形式,嵌 套查询里面的where条件,我都写到join...on后面去了。突然一个想法冒出来:筛选条件跟在join...on后面 和 跟在整个sql语句最后面的where后面有什么区别呢?还有嵌套查询真的慢么?于是便有下面的测试产生,数据库环境为MS SQL 2005

一,inner join

  先看看非嵌套查询


非嵌套inner join

a.select * from t1
inner join t2 on t1.id = t2.id
inner join t3 on t1.id = t3.id
where t1.a=1 and t2.b=1 and t3.c=1 b.select * from t1
inner join t2 on t1.id = t2.id and t2.b=1 
inner join t3 on t1.id = t3.id
where t1.a=1 and t3.c=1 c.select * from t1
inner join t2 on t1.id = t2.id and t2.b=1 
inner join t3 on t1.id = t3.id and t3.c=1
where t1.a=1

  在上面三个非嵌套查询,让“and t2.b=1”和“and t3.c=1”分别在join...on和where之间游走,用Management Studio选中“包含实际的执行计划”并执行这三条语句,都得出下面这个执行计划。

  三个“聚集索引扫描”的谓词从上到下分别是:

    1.t3.c=1

    2.t1.a=1 (seek谓词:t1.id=t3.id)

    3.t2.b=1 (seek谓词:t2.id=t3.id)

  故可以认为:在MS SQL2005中,条件跟在inner join...on后面 和 跟在where后面是等价的。

  接着看嵌套查询


嵌套inner join

d.select * from t1
inner join (select * from t2 where t2.b=1)a on t1.id=a.id
inner join t3 on t1.id = t3.id
where t1.a=1 and t3.c=1 e.select * from t1
inner join (select * from t2 where t2.b=1)a on t1.id=a.id
inner join (select * from t3 where t3.c=1)b on t1.id=b.id
where t1.a=1 f.elect * from t1
inner join (select t3.id,t2.b,t3.c from t3 inner join t2 on t2.id = t3.id where t2.b=1 and t3.c=1)a on t1.id=a.id
where t1.a=1

  第一句sql语句把t2的查询变成子查询,第二句sql语句把t2,t3分别变成子查询,第三句把t2和t3的查询合成一个子查询,再看看实际的执行计划:

  跟上面非嵌套查询的执行计划一模一样。

  故可以认为:简单(注意是简单的,复杂的情况得另外考虑)嵌套查询和其相对应的非嵌套查询形式,执行效率是一样的(网上一些文章指出这是MS SQL优化器针对这些嵌套查询进行了优化)。

--------------------------------------------------------------------------------------------

  接着,在上面两个执行计划的图中又发现一个小问题,为什么明明是select t1 inner join t2 inner join
t3,执行计划却把t1和t3先inner join(t1.id = t3.id)再跟t2 inner join(t2.id =
t3.id)起来?

  经过三个表,四个表,五个表进行连接测试,发现这些顺序都是不确定的。很可能这些顺序是根据SQL优化器内的算法所决定的,由于没有源代码,所以无从考究。

(感谢Keep Walking的补充:

“可以指定顺序,force order选项,和keep plan选项
数量级,索引,统计的不同都可以导致顺序变化”。
我Google了一下option force order和option keep plan,发现SQL优化器做了很多事情,在这文章就不列出来了,大家有兴趣可以Goo一下。)

----------------------------------------

PS1:

  1.经测试,在inner join on后面t1.id = t2.id与t2.id = t1.id等价

  如果发现这文章有错误,欢迎指出。

----------------------------------------------------

PS2:经过songmc指出,发现了一些问题。补充对left join的测试。
二,left join

  附上songmc的代码:


Code

DECLARE @a TABLE(id INT IDENTITY,NAME VARCHAR(20)) 
INSERT INTO @a SELECT 'aa' 
UNION ALL SELECT 'bb' 
UNION ALL SELECT 'cc' 
UNION ALL SELECT 'dd'  DECLARE @b TABLE(id INT IDENTITY,NAME VARCHAR(20)) 
INSERT INTO @b SELECT 'ee' 
UNION ALL SELECT 'ff' 
UNION ALL SELECT 'gg' 
UNION ALL SELECT 'hh'  DECLARE @c TABLE (id INT IDENTITY,NAME VARCHAR(20)) 
INSERT INTO @c SELECT 'ii' 
UNION ALL SELECT 'jj' 
UNION ALL SELECT 'kk' 
UNION ALL SELECT 'll'  --a. 
SELECT 

FROM @a a 
LEFT JOIN @b b ON a.id = b.id 
LEFT JOIN @c c ON b.id = c.id 
WHERE a.NAME != 'aa'  --b. 
SELECT 

FROM @a a 
LEFT JOIN @b b ON a.id = b.id 
LEFT JOIN @c c ON b.id = c.id AND 
a.NAME != 'aa'

  在这里,把a.NAME != 'aa' 放在where后面以及放在c的on后面,查询出来的结果:

  发现sql语句b比sql语句a的结果多了一行记录,这里c.id和c.name为NULL,到底为什么呢?再做一个实验:


--c.

SELECT
*
FROM @a a
LEFT JOIN @b b ON a.id = b.id AND
a.[NAME] != 'aa'
LEFT JOIN @c c ON b.id = c.id

  这次把条件放到b后面了,看b和c的查询结果的对比图:

  发现不仅是c.id和c.name,连b.id和b.name为NULL,这是为什么呢?
      首先要理解left jion就是去掉不符合条件的,保留左表的行。

  

  分析sql语句b中的 “LEFT JOIN @c c ON b.id = c.id AND a.NAME != 'aa'”,表c和x(x为表a和表b连结后的中间结果)连结后的中间结果x1中,去掉a.NAME不等于'aa'的行,并保留左表,因此c.id和c.name为NULL。

  分析sql语句c中的 “LEFT JOIN @b b ON
b.id = c.id AND a.NAME !=
'aa'”,表b和表a连结后的中间结果x2中,去掉a.NAME不等于'aa'的行,并保留左表,因此b.id和b.name为NULL。又因为
“LEFT JOIN @c c ON b.id = c.id”,b.id为NULL,保留左表,所以c.id和c.name为NULL。

  inner join和left join不一样,inner join左边或右边的结果为空,该行记录就不显示了。而left join会以左边的表为保留表,就算右边的结果为空,该行仍然显示。

于是得出的结论是:条件放在on与放在where后面的作用是不一样的。on对中间结果进行筛选,再由where对最终结果进行筛选。

PS3:下面是Left Join on+where的执行过程(附上songmc给我的文档的精华部分,根据《Inside Microsoft® SQL Server™ 2005 T-SQL Querying》进行基于自己理解的修改)

sql:

SELECT * 
FROM a 
LEFT JOIN b ON a.id = b.id AND b.Name != 'ff'
WHERE a.NAME != 'aa' 

步骤1:FROM后面的两个表a,b进行笛卡尔积,生成虚拟表VT1。

步骤2:应用ON筛选器到VT1,只有条件(当前的条件为a.id = b.id AND b.Name != 'ff')为真的行,插入到VT2。

步骤3:添加外部行(OUTER (join))
  这一步只对OUTER JOIN起作用,如果是LEFT JOIN会以左边的表为保留表,如果是RIGHT JOIN会以右边的表为保留表。所谓外部行是指,保留表中的行。即使第二步的ON过滤掉了一些行,在这一步,会根据保留表添加第二步过滤掉的行,并生成VT3。

步骤4.应用WHERE筛选器到VT3,只有条件(当前是Name != ‘aa’)为真的行,插入到VT4。

SQL简单嵌套查询与非嵌套查询的比较(MSSQL2005)的更多相关文章

  1. mysql的缓冲查询和非缓冲查询

    最近在开发一个PHP程序时遇到了下面的错误: PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted 错误信息显示允许的 ...

  2. ElasticSearch的高级复杂查询:非聚合查询和聚合查询

    一.非聚合复杂查询(这儿展示了非聚合复杂查询的常用流程) 查询条件QueryBuilder的构建方法 1.1 精确查询(必须完全匹配上,相当于SQL语句中的“=”) ① 单个匹配 termQuery ...

  3. oracle 相关查询和非相关查询,oracle 去除重复数据,以及oracle的分页查询!

    一.oracle中的相关查询?和非相关查询? 二.oracle去除重复数据 1. 2. 3.oracle 实现分页? 利用rownum的唯一性,和子查询,将rownum从伪列变成实际列!

  4. php 非缓冲查询

    最近在开发一个PHP程序时遇到了下面的错误: PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted 错误信息显示允许的 ...

  5. Solr4.8.0源码分析(6)之非排序查询

    Solr4.8.0源码分析(6)之非排序查询 上篇文章简单介绍了Solr的查询流程,本文开始将详细介绍下查询的细节.查询主要分为排序查询和非排序查询,由于两者走的是两个分支,所以本文先介绍下非排序的查 ...

  6. (24)ASP.NET Core EF查询(查询的工作原理、跟踪与非跟踪查询)

    1.查询生命周期 在进入正题时候,我们先来了解EF Core查询的生命周期. 1.1LINQ查询会由Entity Framework Core处理并生成给数据库提供程序可处理的表示形式(说白了就是生成 ...

  7. [SQL SERVER系列]之嵌套子查询和相关子查询

    子查询有两种类型,一种是只返回一个单值的子查询,这时它可以用在一个单值可以使用的地方,这时子查询可以看作是一个拥有返回值的函数:另外一种是返回一列值的子查询,这时子查询可以看作是一个在内存中临时存在的 ...

  8. SQL嵌套子查询和相关子查询的执行过程有什么区别(推荐)

    SQLServer子查询可以分为 相关子查询 和 嵌套子查询 两类.前提, 假设Books表如下: 类编号 图书名 出版社 价格 ----------------------------------- ...

  9. ylb:子查询(嵌套子查询)和子查询(相关子查询)

    ylbtech-SQL Server:SQL Server-子查询(嵌套子查询)和子查询(相关子查询) SQL Server 子查询(嵌套子查询)和子查询(相关子查询). 1,ylb:1,子查询(嵌套 ...

随机推荐

  1. C/C++ 字符串操作函数 思维导图梳理

    这些常用的字符串操作函数都是包在string.h头文件中. 分享此图,方便大家记忆 <(^-^)> 选中图片点击右键,在新标签页中打开图片会更清晰

  2. 写makefile时候的cc和gcc

    Linux 下 的 cc 和 gcc     Linux 下 的 cc 和 gcc 周银辉 在Linux下一会看到cc,另一会又看到gcc,感觉又点混乱的样子.它们是同一个东西么,有啥区别呢 一分为二 ...

  3. 定时任务 Linux cron job 初步使用

     查看定时任务的命令为:crontab -l   编辑定时任务的命令为:crontab -e   (编辑后立即生效 若注释可在行首加#  同vi)         定时任务说明       每一行为一 ...

  4. C++中随机数的生成

    1.随机数由生成器和分布器结合产生 生成器generator:能够产生离散的等可能分布数值 分布器distributions: 能够把generator产生的均匀分布值映射到其他常见分布,如均匀分布u ...

  5. lockingModel in log4net 日志文件不能被其他进程写入

    http://logging.apache.org/log4net/release/faq.html#single-file How do I get multiple process to log ...

  6. POJ 3667 & HDU 3308 & HDU 3397 线段树的区间合并

    看到讲课安排上 线段树有一节课"区间合并" 我是迷茫的 因为并没有见过 然后了解了一下题目 发现以前写过 还是很麻烦的树链剖分 大概是 解决带修改的区间查询"连续问题&q ...

  7. 获得一个list中某元素的索引值

    list = [1,2,3,3,2,1] list.index(1) # 只能获得首个1的索引值 如果要获得所有该元素的索引值 import numpy as np arr = np.array(li ...

  8. JQuery小知识点

    //get() : 就是把JQ转成原生JS,可以让通过jquery获得元素使用JS的innerHTML方法. $(function(){ //document.getElementById('div1 ...

  9. 机器学习(二十五)— 极大似然估计(MLE)、贝叶斯估计、最大后验概率估计(MAP)区别

    最大似然估计(Maximum likelihood estimation, 简称MLE)和最大后验概率估计(Maximum aposteriori estimation, 简称MAP)是很常用的两种参 ...

  10. 机器学习(四)—逻辑回归LR

    逻辑回归常见问题:https://www.cnblogs.com/ModifyRong/p/7739955.html 推导在笔记上,现在摘取部分要点如下: (0) LR回归是在线性回归模型的基础上,使 ...