改写优化SQL(2):not in改为left join

在sql语句中,not in是经常会用到的一种写法,因为这种写法很直观,容易理解。

但如果不注意的话,很容易写出错误的sql,而且性能存在严重问题,所以,不建议使用not in,要尽量把 not in写法,改为left join。

一、建表

CREATE TABLE tb_emp
(
emp_id INT NOT NULL PRIMARY KEY CLUSTERED,
emp_name VARCHAR(20) NOT NULL,
tel VARCHAR(20) null
)

INSERT INTO dbo.tb_emp
VALUES
(1, '张三','1234567890'),
(2, '李四','9876543210'),
(3, '王五','12345'),
(4, '孙六','666666666'),
(5, '半泽直树','8888888888');


CREATE TABLE tb_org
(
org_id INT NOT NULL PRIMARY KEY CLUSTERED,
org_name VARCHAR(30) NOT NULL,
parent_org_id INT NULL,
emp_id INT NULL
)


INSERT INTO tb_org(org_id,org_name,parent_org_id,emp_id)
VALUES
(1, '行长', NULL,1),
(2, '常务', 1,2),
(3, '专务', 1,3),
(5, '营业二部次长', 4,null);

二、not in写法

现在要查询,tb_emp表中有,但是在tb_org中不存在的emp_id,代码如下:

SELECT e.emp_id,
       e.emp_name
FROM tb_emp e
WHERE e.emp_id NOT IN (SELECT emp_id FROM tb_org)
貌似,不用多想,代码就是上面这样子。

照理,查询结果应该返回2条数据,但奇怪的是一条也没有:

改写优化SQL(2):not in改为left join_第1张图片

执行计划:

改写优化SQL(2):not in改为left join_第2张图片

这个执行计划中,嵌套循环所采用的是 Left Anti Semi join,本质上是 Left join,其中 Anti表示反连接,Semi表示半开连接,合在一起就是 tb_emp表每条记录中的emp_id,如果在tb_org中有一个对应上的,那么tb_emp表的这条记录就不返回。

这里,sql server优化器把not in转成left join+Anti+Semi 了。


其实,这里涉及到not in写法的一个坑,就是当not in 子查询中有null值,那么整个查询的结果就是空的。

因为这里tb_org表中,org_id=5的这条记录的emp_id是null,所以返回结果就是空集。

正确的写法是:

SELECT e.emp_id,
       e.emp_name
FROM tb_emp e
WHERE e.emp_id NOT IN (SELECT emp_id FROM tb_org WHERE emp_id IS NOT null)

三、把not in转成 left join写法

SELECT e.emp_id,
       e.emp_name
FROM tb_emp e
LEFT JOIN tb_org o
ON o.emp_id = e.emp_id
WHERE o.emp_id IS NULL

查询结果:

改写优化SQL(2):not in改为left join_第3张图片

执行计划:

改写优化SQL(2):not in改为left join_第4张图片

这个执行计划,和上面sql server优化器把not in转成left join+Anti+Semi 的执行计划也不太相同,这里直接用的left join,通过o.emp_id is null来过滤关联的数据。

你可能感兴趣的:(【SQL优化】)