零基础学 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 条件。

相关推荐
程序员Agions2 小时前
N+1 查询:那个让你 API 慢成 PPT 的隐形杀手
数据库·后端
banjin2 小时前
轻量化时序数据库新选择:KaiwuDB-Lite 实战体验
数据库·oracle·边缘计算·时序数据库·kaiwudb·kwdb
阳光九叶草LXGZXJ2 小时前
达梦数据库-学习-43-定时备份模式和删除备份(Python+Crontab)
linux·运维·开发语言·数据库·python·学习
独自破碎E2 小时前
包含min函数的栈
android·java·开发语言·leetcode
DemonAvenger2 小时前
Redis监控系统搭建:关键指标与预警机制实现
数据库·redis·性能优化
沛沛老爹2 小时前
基于Spring Retry实现的退避重试机制
java·开发语言·后端·spring·架构
_F_y2 小时前
数据库基础
数据库·adb
zgl_200537792 小时前
源代码:ZGLanguage 解析SQL数据血缘 之 显示 UNION SQL 结构图
大数据·数据库·数据仓库·sql·数据治理·sql解析·数据血缘
柚几哥哥2 小时前
Redis 优化实践:高性能设备缓存系统设计
数据库·redis·缓存
没有bug.的程序员2 小时前
Java IO 与 NIO:从 BIO 阻塞陷阱到 NIO 万级并发
java·开发语言·nio·并发编程·io流·bio