零基础学 SQL:IN、EXISTS、ON 用法详解(附示例 + 避坑)
一、IN 运算符
1、IN 运算符的作用
IN 是 SQL 中最常用的成员判断运算符 ,用于判断某个字段的值是否存在于指定的集合中,本质上是多个 OR 条件的简洁写法,能大幅简化 SQL 语句。
2、IN 的基础用法(开发中最常用)
(1) 基础语法
sql
SELECT 字段列表
FROM 表名
WHERE 字段 IN (值1, 值2, 值3, ...);
(2) 基础示例
比如查询用户 ID 为 1、3、5 的用户信息,用 OR 写和用 IN 写的对比:
sql
-- 用OR(繁琐,值多了易出错)
SELECT * FROM user WHERE user_id = 1 OR user_id = 3 OR user_id = 5;
-- 用IN(简洁,推荐)
SELECT * FROM user WHERE user_id IN (1, 3, 5);
(3) 搭配子查询使用(开发中高频场景)
IN 最实用的场景是搭配子查询,动态获取判断集合,比如:
sql
-- 需求:查询"已下单的用户"信息(先从订单表拿到用户ID,再查用户表)
SELECT * FROM user
WHERE user_id IN (SELECT DISTINCT user_id FROM `order`);
3、IN 的进阶用法与注意事项(开发中避坑关键)
(1) NOT IN:反向判断(排除指定集合)
用于判断字段值不在指定集合中,比如查询非 1、3、5 号的用户:
sql
SELECT * FROM user WHERE user_id NOT IN (1, 3, 5);
重要坑点 :NOT IN 搭配子查询时,如果子查询结果包含 NULL,会导致整个查询返回空!
sql
-- 错误示例:子查询返回的user_id包含NULL,此查询会返回空
SELECT * FROM user
WHERE user_id NOT IN (SELECT user_id FROM `order`); -- 若order表user_id有NULL,结果为空
-- 正确写法:子查询排除NULL
SELECT * FROM user
WHERE user_id NOT IN (SELECT user_id FROM `order` WHERE user_id IS NOT NULL);
(2) IN 集合的长度限制
不同数据库对 IN 后的集合长度有隐性限制(比如 MySQL 建议不超过 1000 个值),如果值太多(比如上万条),会导致 SQL 执行效率极低。
解决方案:
少量值(<1000):直接用 IN;
大量值:将值存入临时表,用 JOIN替代 IN:
sql
-- 1. 创建临时表并插入批量值
CREATE TEMPORARY TABLE temp_ids (id INT);
INSERT INTO temp_ids VALUES (1), (2), ..., (10000);
-- 2. 用JOIN替代IN,效率更高
SELECT u.* FROM user u
JOIN temp_ids t ON u.user_id = t.id;
(3) IN 与 = ANY 的等价性
IN 等价于 = ANY,可以互换使用,比如:
sql
-- 以下两条语句效果完全一致
SELECT * FROM user WHERE user_id IN (1, 3, 5);
SELECT * FROM user WHERE user_id = ANY (SELECT user_id FROM temp_ids);
4、IN vs EXISTS:性能对比(选型)
当 IN 搭配子查询时,常和 EXISTS 做对比,核心选型原则:
| 场景 | 推荐用 IN |
推荐用 EXISTS |
|---|---|---|
| 子查询结果集 | 小(<1000 条) | 大(上万条) |
| 主表 vs 子表 | 主表数据少,子表数据多 | 主表数据多,子表数据少 |
示例对比:
sql
-- IN:子查询结果小时用
SELECT * FROM user u WHERE u.user_id IN (SELECT o.user_id FROM `order` o WHERE o.amount > 100);
-- EXISTS:子查询结果大时用(效率更高)
SELECT * FROM user u WHERE EXISTS (SELECT 1 FROM `order` o WHERE o.user_id = u.user_id AND o.amount > 100);
二、EXISTS运算符
1、EXISTS 的核心作用
EXISTS 是 SQL 中用于判断子查询是否返回结果集的布尔型运算符:
如果子查询返回至少一行数据 ,EXISTS 返回 TRUE;
如果子查询返回空集 ,EXISTS 返回 FALSE。
它的核心价值是 "存在性校验" ,无需关心子查询返回的具体数据,只关心 "有没有",因此执行效率通常比 IN 更高(尤其是子查询结果集大时)。
2、EXISTS 的基础语法
sql
SELECT 字段列表
FROM 主表
WHERE EXISTS (子查询);
特性:
子查询通常是关联子查询(子查询中引用主表字段,建立关联);
子查询返回的字段无意义(习惯用 SELECT 1 而非 SELECT *,减少数据传输);
EXISTS 一旦找到满足条件的行就会停止遍历(短路求值,性能优化)。
3、示例(高频场景)
场景 1:基础存在性判断(查询 "有订单的用户")
sql
-- 需求:查询至少有1笔订单的用户信息
SELECT *
FROM user u
WHERE EXISTS (
SELECT 1 -- 字段无意义,用1更高效
FROM `order` o
WHERE o.user_id = u.user_id -- 关联主表user的user_id
);
场景 2:NOT EXISTS(反向判断,查询 "无订单的用户")
sql
-- 需求:查询从未下过单的用户
SELECT *
FROM user u
WHERE NOT EXISTS (
SELECT 1
FROM `order` o
WHERE o.user_id = u.user_id
);
场景 3:多条件存在性判断(查询 "有 2026 年订单的 VIP 用户")
sql
SELECT *
FROM user u
WHERE u.is_vip = 1
AND EXISTS (
SELECT 1
FROM `order` o
WHERE o.user_id = u.user_id
AND o.order_date >= '2026-01-01' -- 多条件过滤
);
4、EXISTS vs IN(核心对比 + 选型原则)
这是开发中最易混淆的点,直接看对比表和选型建议:
| 维度 | EXISTS |
IN |
|---|---|---|
| 核心逻辑 | 判断 "是否存在满足条件的行"(布尔值) | 判断 "字段值是否在集合中"(值匹配) |
| 子查询类型 | 通常是关联子查询(依赖主表) | 通常是非关联子查询(独立执行) |
| 空值处理 | 不受子查询 NULL 影响 |
NOT IN 遇 NULL 会返回空结果 |
| 性能(子查询结果大) | 高效(短路求值,无需遍历所有结果) | 低效(需先生成完整集合,再匹配) |
| 性能(子查询结果小) | 效率接近 | 效率接近 |
选型原则:
子查询结果集大(上万条) → 用 EXISTS(比如查询 "有订单的用户",订单表百万级);
子查询结果集小(<1000 条) → 用 IN(比如查询 "ID 在 1,3,5 的用户");
反向判断(NOT) → 优先用 NOT EXISTS(避免 NOT IN 遇 NULL 踩坑)。
IN vs EXISTS:性能对比(选型)
当 IN 搭配子查询时,常和 EXISTS 做对比,核心选型原则:
| 场景 | 推荐用 IN |
推荐用 EXISTS |
|---|---|---|
| 子查询结果集 | 小(<1000 条) | 大(上万条) |
| 主表 vs 子表 | 主表数据少,子表数据多 | 主表数据多,子表数据少 |
示例对比:
sql
-- IN:子查询结果小时用
SELECT * FROM user u WHERE u.user_id IN (SELECT o.user_id FROM `order` o WHERE o.amount > 100);
-- EXISTS:子查询结果大时用(效率更高)
SELECT * FROM user u WHERE EXISTS (SELECT 1 FROM `order` o WHERE o.user_id = u.user_id AND o.amount > 100);
5、性能优化要点
子查询用 SELECT 1 而非 SELECT \* :EXISTS 只关心行是否存在,SELECT 1 减少数据库读取字段的开销;
给关联字段建索引 :比如示例中 order.user_id 建索引,EXISTS 关联查询时能快速匹配;
避免子查询中用复杂函数 :比如 WHERE o.user_id = CONCAT(u.user_id, ''),会导致索引失效,降低 EXISTS 效率;
EXISTS 嵌套优化 :多层 EXISTS 可拆解为 JOIN,减少嵌套层级(比如查询 "有订单且有退款的用户")。
6、常见误区纠正
误区:EXISTS 子查询必须返回主表字段 → 子查询字段无意义,SELECT 1/NULL/* 效果一致;
误区:EXISTS 比 IN 一定快 → 子查询 结果小 时,两者效率接近;
误区:NOT EXISTS 和 NOT IN 等价 → NOT IN 遇子查询 NULL 会返回空,NOT EXISTS 不受影响。
三、ON 关键字
1、ON 关键字的作用
ON 是 SQL 中多表连接(JOIN) 时的核心关键字,用于指定表之间的关联条件 (比如用户表和订单表通过 user_id 关联),决定两个表如何 "匹配行"。
简单来说:JOIN 负责指定 "连接类型"(内连接 / 左连接等),ON 负责指定 "连接规则",两者必须搭配使用(除了笛卡尔积连接)。
2、ON 的常用场景(多表连接)
(1) 基础语法结构
sql
SELECT 字段列表
FROM 表1
[INNER/LEFT/RIGHT/FULL] JOIN 表2
ON 表1.关联字段 = 表2.关联字段 -- 核心:指定连接条件
[WHERE 筛选条件]; -- 可选:筛选最终结果
(2) 不同连接类型下的 ON 示例
以 "用户表(user)+ 订单表(order)" 为例,先准备测试数据:
sql
-- 用户表
CREATE TABLE user (
user_id INT PRIMARY KEY,
name VARCHAR(20)
);
INSERT INTO user VALUES (1, '张三'), (2, '李四'), (3, '王五');
-- 订单表
CREATE TABLE `order` (
order_id INT PRIMARY KEY,
user_id INT,
amount DECIMAL(10,2)
);
INSERT INTO `order` VALUES (1, 1, 100.00), (2, 1, 200.00), (3, 2, 150.00);
场景 1:内连接(INNER JOIN)+ ON
只返回两个表中匹配连接条件的行(无匹配则不返回):
sql
-- 查询有订单的用户及订单信息
SELECT u.user_id, u.name, o.order_id, o.amount
FROM user u
INNER JOIN `order` o
ON u.user_id = o.user_id; -- 关联条件:用户ID一致
结果:只返回张三(user_id=1)、李四(user_id=2)的信息,王五(无订单)不显示。
场景 2:左连接(LEFT JOIN)+ ON
返回左表所有行 ,右表匹配连接条件的行显示,无匹配则右表字段为 NULL:
sql
-- 查询所有用户及他们的订单(无订单的用户也显示)
SELECT u.user_id, u.name, o.order_id, o.amount
FROM user u
LEFT JOIN `order` o
ON u.user_id = o.user_id;
结果 :王五(user_id=3)的 order_id 和 amount 为 NULL。
场景 3:ON 加额外条件(区分 WHERE)
ON 是 "连接时的条件",WHERE 是 "连接后筛选条件",两者作用阶段不同:
sql
-- 错误写法:用WHERE筛选左连接的右表条件,会把左表无匹配的行过滤掉
SELECT u.user_id, u.name, o.order_id, o.amount
FROM user u
LEFT JOIN `order` o
ON u.user_id = o.user_id
WHERE o.amount > 150; -- 过滤后,只有张三的200元订单、李四的150元订单?不,李四的150不满足>150,最终只有张三的200元订单
-- 正确写法:用ON筛选右表,保留左表所有行
SELECT u.user_id, u.name, o.order_id, o.amount
FROM user u
LEFT JOIN `order` o
ON u.user_id = o.user_id AND o.amount > 150; -- 连接时只匹配金额>150的订单
结果 :张三显示 200 元订单,李四、王五的订单字段为 NULL(保留左表所有行)。
(3) 多表连接(3 表及以上)的 ON 用法
多个表连接时,ON 依次指定两两表的关联条件:
sql
-- 新增商品表
CREATE TABLE product (
product_id INT PRIMARY KEY,
product_name VARCHAR(50)
);
-- 订单表新增product_id字段:ALTER TABLE `order` ADD product_id INT;
INSERT INTO `order` VALUES (1, 1, 100.00, 1), (2, 1, 200.00, 2), (3, 2, 150.00, 1);
INSERT INTO product VALUES (1, '手机'), (2, '耳机');
-- 3表连接:用户+订单+商品
SELECT u.name, o.order_id, p.product_name
FROM user u
LEFT JOIN `order` o ON u.user_id = o.user_id
LEFT JOIN product p ON o.product_id = p.product_id; -- 订单和商品的关联条件
3、ON vs WHERE
这是开发中最易混淆的点,直接看对比表:
| 维度 | ON |
WHERE |
|---|---|---|
| 作用阶段 | 连接表时生效(先匹配行,再生成连接结果) | 连接完成后生效(先得到连接结果,再筛选行) |
| 左 / 右连接影响 | 不过滤左 / 右表的行,仅控制右 / 左表的匹配行 | 会过滤最终结果的行(包括左 / 右表的行) |
| 适用场景 | 指定表之间的关联条件(比如 user_id 匹配) | 筛选最终结果的业务条件(比如 amount>100) |
4、ON 的特殊用法
(1) USING 简化 ON(字段名相同时)
如果两个表的关联字段名完全相同(比如都叫 user_id),可用 USING(字段名) 替代 ON 表1.字段=表2.字段:
sql
-- 等价于 ON u.user_id = o.user_id
SELECT u.name, o.order_id
FROM user u
INNER JOIN `order` o
USING(user_id);
(2) 自连接的 ON 用法
同一张表自己连接自己(比如查询员工和其上级),ON 指定自关联条件:
sql
-- 员工表(含上级ID)
CREATE TABLE emp (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(20),
manager_id INT -- 上级ID,关联emp_id
);
INSERT INTO emp VALUES (1, '老板', NULL), (2, '张三', 1), (3, '李四', 2);
-- 自连接查询员工和上级姓名
SELECT e.emp_name, m.emp_name AS manager_name
FROM emp e
LEFT JOIN emp m
ON e.manager_id = m.emp_id; -- 员工的上级ID = 上级的员工ID
5、避坑
避免笛卡尔积:忘记写 ON 条件会导致两个表的笛卡尔积(行数 = 表 1 行数 × 表 2 行数),数据量爆炸,查询极慢;
左连接后用 WHERE 过滤右表:左连接后如果用 WHERE 过滤右表字段(比如 o.user_id IS NOT NULL),会等价于内连接,丢失左表无匹配的行;
关联字段类型一致:ON 中关联的字段类型必须一致(比如 user.user_id 是 INT,order.user_id 不能是 VARCHAR),否则会导致索引失效、查询变慢;
多表连接顺序:把数据量小的表放在左边,且优先连接关联条件筛选性强的表,提升查询效率。
总结
EXISTS :
逻辑:判断子查询是否有结果,返回布尔值,短路求值效率高;
选型:子查询结果大用 EXISTS,小用 IN,反向判断优先 NOT EXISTS;
避坑:NOT IN 遇 NULL 踩坑,优先用 NOT EXISTS 替代。
IN :
作用:简化多个 OR 条件,判断字段是否在指定集合中,支持直接值集合和子查询集合;
避坑:NOT IN 搭配子查询时必须排除 NULL,IN 集合值过多时用临时表 + JOIN 替代;
ON :
作用:指定表之间的关联条件,决定连接时如何匹配行;
和 WHERE 的区别:ON 连接时生效(保留左 / 右表行),WHERE 连接后生效(过滤行);
避坑:左连接后避免用 WHERE 过滤右表,关联字段类型保持一致,不要漏写 ON 条件。