把一列 ID 粘进 SQL IN 之前:数量上限、Excel 吃掉的前导零,和更好的替代

· 约 4 分钟 📋 列表处理

从 Excel 里选中一列订单号,复制,拼成 IN ('A1', 'A2', ...) 粘进查询——这是最省事的做法,日常大概占了临时查数场景的一半。

它也是最容易悄悄出错的做法,因为出错的地方几乎都不在 SQL 里。

一、数据在到达你手上之前就可能坏了

Excel 会对粘进去的内容做类型推断,而这件事发生在你复制之前:

损坏表现谁中招
前导零消失00123 → 123订单号、员工号、邮编、银行卡号
科学计数法18 位身份证 → 1.23457E+17长数字串
被识别成日期2026-1-5、3-15 → 日期序列号带横杠或斜杠的编码

第二种最危险:它不只是显示成那样,低位是真的被截断了——Excel 的数值只保留 15 位有效数字。粘出来的值即使改回文本格式,后面几位也已经变成了 0。

避免办法:

  • 在 Excel 里先把那一列设成文本格式,再粘入数据(顺序不能反)
  • 从数据库或系统直接导出 CSV,用文本编辑器打开,不要中途用 Excel 过一手
  • 已经坏了的数据补不回来,只能重新导出

二、看不见的字符

从网页、PDF、聊天记录里复制的内容,经常带着这些:

字符来源
U+00A0 不换行空格网页里的  
零宽字符 U+200B / U+FEFF反爬、编辑器
BOM文件开头
全角空格 U+3000中文输入法

它们在 SQL 里就是普通字符,参与字符串比较。结果是查询返回零行,而你盯着屏幕看不出任何问题——两个字符串看起来一模一样。

所以清洗这一步不能省:去首尾空格 → 去空行 → 去重,顺序就是这个。列表处理 把这三步和输出格式分开,也是为了让清洗先于拼接。

三、引号和类型

引号转义:值里含单引号时必须写成两个(O''Brien),否则轻则语法错误,重则成为注入点。

加不加引号,要看列的真实类型——这一条的代价比想象中大:

-- id 列是 BIGINT
WHERE id IN ('1001', '1002')   -- ✗ 触发隐式类型转换
WHERE id IN (1001, 1002)       -- ✓

给数字列传字符串,多数数据库会做隐式转换,而转换发生在列上时会放弃走索引,查询从毫秒变成全表扫描。反过来给字符串列传数字同理。

这个坑在数据量小的测试库上完全看不出来,上了生产才发现。

四、数量:上限和计划缓存

硬限制

数据库限制
OracleIN 列表最多 1000 个表达式
SQL Server参数化查询最多 2100 个参数
MySQL无 IN 上限,受 max_allowed_packet 约束整条 SQL 长度
PostgreSQL无硬上限,解析和规划开销随长度增长

比上限更值得注意的是执行计划缓存。

数据库按 SQL 文本缓存执行计划,而 IN (?, ?) 和 IN (?, ?, ?) 是两条不同的语句:

  • 每种长度都要重新解析和规划一次
  • 长度千变万化时,缓存被大量只用一次的计划塞满,把真正的热点查询挤出去

缓解办法:把个数补齐到固定档位(一律凑到 10、50、100、500,不足的用重复值填),让不同长度收敛成几条语句。或者干脆换做法。

经验分档:

数量建议
几十个随便用
几百个能用,注意计划缓存
上千个换做法

五、三种替代

1. 临时表 + JOIN

把 ID 批量插进临时表,再和目标表关联。

适合值很多、且同一批 ID 要被多次使用的场景。额外的好处是优化器能用上统计信息,选择的连接方式通常比长 IN 更合理。

2. 数组参数(PostgreSQL)

WHERE id = ANY($1::bigint[])

一个参数装下整个列表,SQL 文本恒定,执行计划可复用。PG 上最干净的做法。

3. VALUES 派生表

JOIN (VALUES (1001),(1002),(1003)) AS t(id) ON x.id = t.id

多数数据库都支持,规划开销比超长 IN 小。

还有一条常被忽略的路

如果这批 ID 本来就来自另一张表的查询结果,直接写成子查询或 JOIN。

「导出成一列 → 粘进 IN」这个动作本身,很多时候是可以完全跳过的。

六、顺手的几个清洗选项

拼 IN 之外,同一份数据常常还要换成别的形状:

目标输出格式
SQL IN(字符串)('a', 'b', 'c'),单引号转义
SQL IN(数字)(1, 2, 3),不加引号
JS / TS 数组["a", "b"]
每行一个贴回配置文件

顺带一个容易被忽略的细节:排序用自然序。普通字符串排序会把 item10 排在 item2 前面(因为逐字符比较时 1 < 2),这几乎不是任何人想要的结果。

相关

❓ 常见问题

IN 子句最多能放多少个值?

各库不同,但真正的限制往往不是语法上限。硬限制:Oracle 的 IN 列表最多 1000 个表达式,超出直接报错;SQL Server 对参数化查询有 2100 个参数的上限。没有硬限制但有实际限制的:(1) MySQL —— 受 max_allowed_packet 约束,整条 SQL 文本太长会被拒;(2) PostgreSQL —— 语法上不限,但解析和规划的开销随列表长度增长,上万个值时明显变慢。经验分档:(1) 几十个 —— 随便用;(2) 几百个 —— 能用,注意下面说的计划缓存问题;(3) 上千个 —— 换做法,见正文的三种替代。一个提醒:能跑通不等于该这么写,一条几百 KB 的 SQL 在日志、监控和慢查询分析里都很难看。

为什么同一条查询,IN 里值的个数变了性能就不一样?

因为 SQL 文本变了,执行计划缓存认不出它是同一条。机制:数据库按 SQL 文本(或其指纹)缓存执行计划。IN (?, ?) 和 IN (?, ?, ?) 是两条不同的语句,各自占一个缓存条目。后果:(1) 每种长度都要重新解析和规划一次;(2) 长度千变万化时,缓存被大量只用一次的计划塞满,把真正的热点查询挤出去;(3) 在以计划缓存为核心的数据库上这个影响尤其明显。缓解办法:(1) 把个数补齐到固定档位(比如一律凑到 10、50、100、500,不足的用重复值或 NULL 填),让不同长度的语句收敛成几条;(2) 干脆换成数组参数或临时表,语句文本恒定。

从 Excel 复制出来的订单号为什么少了前面的零?

Excel 把它当数字处理了,而这件事发生在复制之前。三种典型损坏:(1) 前导零消失 —— 00123 变成 123,订单号、员工号、邮编、银行卡号都中招;(2) 长数字变科学计数法 —— 超过 15 位有效数字的串(18 位身份证号、长订单号)显示成 1.23457E+17,而且低位真的被截断了,不是显示问题;(3) 被识别成日期 —— 2026-1-5、3-15 这类会变成日期序列号。怎么避免:(1) 在 Excel 里把那一列先设成文本格式再粘入数据(顺序不能反);(2) 从数据库或系统直接导出 CSV,用文本编辑器打开,不要中途用 Excel 过一手;(3) 已经坏了的数据补不回来——科学计数法截断掉的低位是真的没了,只能重新导出。

值超过几百个,该换成什么?

三种替代,按场景选。(1) 临时表 + JOIN —— 把 ID 批量插进临时表,再和目标表关联。适合值很多、且同一批 ID 要被多次使用的场景,优化器也能用上统计信息;(2) 数组参数 —— PostgreSQL 的 = ANY($1::text[]),一个参数装下整个列表,SQL 文本恒定,执行计划可复用,这是 PG 上最干净的做法;(3) VALUES 子句做派生表 —— JOIN (VALUES (1),(2),(3)) AS t(id) ON ...,多数库都支持,比超长 IN 的规划开销小。还有一条常被忽略的路:如果这批 ID 本来就来自另一张表的查询结果,直接写成子查询或 JOIN,根本不需要先导出成一列再粘回去。

📋 打开 列表处理 粘一列变一行·去重排序·加前后缀引号·SQL IN/JS 数组/逗号分隔一键预设 →

🔗 相关阅读

全部教程 →