零基础学 SQL:IN、EXISTS、ON 用法详解(附示例 + 避坑)

零基础学 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 INNULL 会返回空结果
性能(子查询结果大) 高效(短路求值,无需遍历所有结果) 低效(需先生成完整集合,再匹配)
性能(子查询结果小) 效率接近 效率接近
选型原则:

子查询结果集大(上万条) → 用 EXISTS(比如查询 "有订单的用户",订单表百万级);

子查询结果集小(<1000 条) → 用 IN(比如查询 "ID 在 1,3,5 的用户");

反向判断(NOT) → 优先用 NOT EXISTS(避免 NOT INNULL 踩坑)。

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/* 效果一致;

误区:EXISTSIN 一定快 → 子查询 结果小 时,两者效率接近;

误区:NOT EXISTSNOT 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_idamountNULL

场景 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 INNULL 踩坑,优先用 NOT EXISTS 替代。

IN

作用:简化多个 OR 条件,判断字段是否在指定集合中,支持直接值集合和子查询集合;

避坑:NOT IN 搭配子查询时必须排除 NULLIN 集合值过多时用临时表 + JOIN 替代;

ON

作用:指定表之间的关联条件,决定连接时如何匹配行;

和 WHERE 的区别:ON 连接时生效(保留左 / 右表行),WHERE 连接后生效(过滤行);

避坑:左连接后避免用 WHERE 过滤右表,关联字段类型保持一致,不要漏写 ON 条件。

相关推荐
ITenderL2 小时前
MySQL联合索引遇到范围查询
数据库·索引
白鸽(二般)6 小时前
MinIO Java Client API
java·开发语言
敲个大西瓜7 小时前
Redis一百道核心面试题
数据库·redis·缓存
阿里云大数据AI技术7 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
软萌萌的17 小时前
Java Spring Boot 修改yml配置&加载顺序规则
java·spring boot·python
IT小盘8 小时前
13-企业Prompt模板-角色任务约束与输出格式
java·前端·prompt
爱敲键盘的猴子8 小时前
Java并发编程 -- volatile
java·java并发
plainGeekDev10 小时前
工厂模式 → Hilt
android·java·kotlin
plainGeekDev10 小时前
Application 单例 → Hilt Singleton
android·java·kotlin
888CC++10 小时前
C语言与C++的区别:从面向过程到面向对象
java·c语言·c++