在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);
现在要查询,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条数据,但奇怪的是一条也没有:
执行计划:
这个执行计划中,嵌套循环所采用的是 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 server优化器把not in转成left join+Anti+Semi 的执行计划也不太相同,这里直接用的left join,通过o.emp_id is null来过滤关联的数据。