INDIRECT:把文本变成引用
绝大多数 Excel 函数的参数都是“引用”——你写 A1、写 B2:D500,Excel 就知道你要的是哪片格子。但有一类需求用普通引用根本写不出来:区域地址本身是算出来的。比如你有十二张工作表,名字是一月到十二月,想在汇总表里根据某个单元格里的月份名去取对应表的同一个位置;又比如你想让公式引用的列号由另一个单元格的数字决定,改一个数字就换一列。这些需求的核心都是“地址是字符串,需要临时变成真正的引用”,而 INDIRECT 就是唯一能干这件事的函数。
它是一个非常老的函数,从 Excel 早期版本就有,也是公认的“危险函数”之一,原因有二:第一,它是易失函数,只要工作表发生任何重算它都会跟着重算,用多了会让文件明显变慢;第二,它接受的是文本,而文本不会被 Excel 追踪,所以你插入行、删除行、移动区域之后,INDIRECT 里的字符串不会跟着调整,公式会指到错误的地方去。这一篇会把这两条讲透,并给出在什么情况下该用、什么情况下必须用别的函数替代。
一、它到底在做什么

INDIRECT 做的事情只有一件:把一个看起来像地址的文本字符串,当成真正的地址去取值。写成 =INDIRECT("B2") 和直接写 =B2 的结果完全一样。区别是前者里那两个字符 B2 是字符串,可以被拼接、被计算、被替换——比如 =INDIRECT("B"&A1) 会根据 A1 里的数字决定取 B 列的哪一行,A1 写 5 就取 B5,写 10 就取 B10。这个“地址可变”的能力是其他任何函数都不提供的。
它最常见的用途有三类。第一类是动态引用不同的工作表:把工作表名放在一个单元格里,用 & 拼成完整的地址。第二类是动态改变区域大小或者位置:把行号、列号、行列数放在单元格里,拼出 A1 样式的区域字符串。第三类是把别处维护的“地址配置”拿来用:比如一张配置表里写着每个指标对应的数据区域,主表用 INDIRECT 去读配置再取值,改配置就等于改公式。
需要特别说明的是,INDIRECT 返回的是引用,不是值。这意味着它可以出现在任何接受引用的位置,比如 =SUM(INDIRECT("D2:D"&A1)) 是合法的,会对 D2 到 D 某行求和;=AVERAGE(INDIRECT(H1)) 也合法,只要 H1 里写着一个合法的区域地址字符串。这个特性让它非常灵活,也让它很容易被滥用——很多人拿它来做本可以用 INDEX 或者 OFFSET 解决的问题,白白付出性能代价。
二、语法:第二个参数容易被忽略
INDIRECT 只有两个参数,写在下面的代码块里。
INDIRECT(ref_text, [a1])
第一个参数 ref_text 是地址文本,可以是直接写死的字符串,也可以是拼接出来的结果,甚至可以是对另一个单元格的引用(那个单元格里放着地址文本,也就是“间接引用”这个名字的由来)。第二个参数 a1 决定地址文本采用哪种写法:省略或者写 TRUE 时按 A1 样式解析,也就是我们日常习惯的 B2、D2:F500 这种;写 FALSE 时按 R1C1 样式解析,写成 R2C3 这种“第 2 行第 3 列”的形式。
绝大多数人一辈子只用 A1 样式,但 R1C1 样式在某些场景下更方便,因为行号列号都是数字,拼接起来不用做列号转字母的转换。比如 =INDIRECT("R"&A1&"C"&B1, FALSE) 中,A1 和 B1 分别放行号和列号,直接拼就行;如果用 A1 样式,还需要把列号 3 转成字母 C,得多写一个转换公式。做“按行列号动态取值”的需求时,R1C1 样式往往更省事。
两个写法还有一点实用区别:A1 样式里如果要引用别的工作表,字符串要写成 Sheet2!B2 这种形式,工作表名里有空格时必须加单引号,写成 '销售 明细'!B2;R1C1 样式同样支持跨表。很多跨表引用报错的原因就是忘了给带空格的表名加单引号,排查时先看一眼这里。
理解易失性有个很直观的测试方法:在一个空表里写两百个 INDIRECT 公式,然后在任意单元格里反复输入数字,你会明显感到输入有延迟;把公式换成 INDEX 之后延迟立刻消失。这个差异在几十个公式时不明显,在上千个公式时会让人无法忍受。所以关于 INDIRECT 的使用原则其实很简单:能用别的函数实现就不用它,必须用的时候把数量压到最少,并且把所有地址字符串集中在一两个配置单元格里,避免散落各处无法维护。
三、五个能直接抄的写法
下表列出了五种最常见的动态引用需求。需要说明的是,其中有一部分用 INDEX 或者 OFFSET 实现更合适,右边的要点栏标注了推荐程度,实际选择时请优先考虑非易失的替代方案。
| 需求 | 可复制的写法 | 要点 |
|---|---|---|
| 引用固定地址文本 | =INDIRECT("B2") |
等价于 =B2,一般没必要 |
| 行号由单元格决定 | =INDIRECT("B"&A1) |
A1 放行号,简单直接 |
| 动态求和区域 | =SUM(INDIRECT("D2:D"&A1)) |
区域末尾行号可变 |
| 跨表按名称引用 | =INDIRECT(H1&"!B2") |
H1 放工作表名,带空格要加单引号 |
| 按行列号取值 | =INDIRECT("R"&A1&"C"&B1, FALSE) |
R1C1 样式,免去列号转字母 |
第二行和第四行是最典型的两种用法。第二行适合“取某一列里第 N 行的值”,但如果只是这个需求,更推荐用 =INDEX(B:B, A1),因为 INDEX 不是易失函数,性能更好而且插入行时会自动调整。第四行适合“按月份名取不同工作表的同一个位置”,这个需求 INDEX 做不了,INDIRECT 几乎是唯一解,也是它最有价值的场景。
跨表引用时有个稳妥写法值得记住:把单引号无条件加上,写成 =INDIRECT("'"&H1&"'!B2")。即使工作表名没有空格,加上单引号也是合法的,这样以后有人改了表名加了空格,公式也不会崩。这个小小的防御性写法能省掉很多莫名其妙的 #REF! 排查时间。
还有一个组合用法:配合名称管理器使用。你可以先定义一个名称,比如把“本月数据表”定义成 =INDIRECT(汇总表!$H$1&"!A1:F500"),之后所有公式都引用“本月数据表”这个名称,改 H1 就等于切换数据源。这比在每个公式里都写一遍 INDIRECT 要好维护得多,也更容易让别人看懂。
四、真实场景
第一个场景是十二张月表的汇总。假设工作簿里有“一月”到“十二月”十二张结构完全相同的表,汇总表里要取“当前月”那张表的 B10。写成 =INDIRECT($H$1&"!B10"),H1 里放“三月”,公式就取三月表的 B10,改成“五月”就自动换成五月表。这比写十二个 IF 判断月份要优雅得多,也比手工切换引用省事。如果还要对整张表求和,写成 =SUM(INDIRECT("'"&$H$1&"'!D2:D500"))。
第二个场景是二级联动下拉菜单。做省市联动时,先定义几个名称:把“广东省”定义为该省城市列表的区域,“湖南省”定义为另一个区域,然后在数据验证的来源里写 =INDIRECT($A$2),A2 是省份选择。用户选了广东省,城市下拉就自动指向广东省对应的区域。这是 INDIRECT 最经典的应用之一,几乎所有讲二级下拉的教程都会用到它,因为数据验证的来源不支持直接写动态数组公式,只能靠名称加 INDIRECT 来实现。
第三个场景是动态调整打印区域或者图表数据源。图表的数据源要求是固定区域,想让它跟着某个单元格变,可以定义一个名称 =INDIRECT("图表数据!$B$2:$B$"&图表数据!$H$1),再把图表的数据源设成这个名称。改 H1 就能改变图表显示多少行。虽然现在有了动态数组,很多这类需求可以用溢出区域配合井号引用解决,但在图表这个场景下 INDIRECT 仍有价值,因为图表对动态数组的支持并不完整。
第四个场景是做模板时保留“可配置性”。比如一张用于多个分公司的模板,各分公司的数据区域位置不同,可以在一张配置表里维护“起始行”“结束行”“数据表名”三个参数,主表公式统一写成 =SUM(INDIRECT("'"&配置!$B$1&"'!D"&配置!$B$2&":D"&配置!$B$3))。换一家分公司只需要改配置表的三个格子,不用动任何公式。这类“配置驱动”的设计在模板复用时非常实用。
第五个场景是反向引用,也就是从别的单元格里读地址。假设 A1 里写着文本 D100,那么 =INDIRECT(A1) 会取 D100 的值。这种写法在“让用户手工指定数据来源”的场景下有用,但因为用户填错一个字符就会报错,实际使用时要配合 IFERROR 给出友好提示。更稳妥的做法是不让用户填地址,而是填月份、部门这类业务字段,由公式负责拼地址。
第六个场景是做可切换的对比分析。假设你有若干个方案表,分别叫“方案一”“方案二”“方案三”,想在同一张看板上切换对比。做法是在看板顶部放一个下拉选择方案名,下面所有公式都写成 =INDIRECT("'"&$H$1&"'!D10") 这种形式,一次切换整张看板的数据源全部换掉。这类需求如果用普通的跨表引用,就得为每个方案各做一张看板;用 INDIRECT 则只需一张。代价是重算变慢和插入行时不会自动调整,所以方案表的结构要尽量固定下来。
五、翻车提醒、性能陷阱与替代方案

第一,它是易失函数。INDIRECT 会在每次工作表重算时重新计算,而且它的存在会连带让依赖它的所有公式一起重算。在一两个公式里用没问题,但如果一张表里有几百个 INDIRECT,每输入一个数字都会有明显卡顿。判断文件是不是被它拖慢,可以试着把 INDIRECT 全换成静态引用,对比一下输入时的响应速度。
第二,引用不会被自动调整。这是它最危险的地方。你在 A1 写 =INDIRECT("D10"),然后在第 5 行插入一行,D10 实际上变成了 D11,但公式里的字符串仍然是 D10 这三个字符,于是它取到了原本 D9 的内容,而且不报任何错误。这类错误极其隐蔽,往往是几个月后对账时才发现。凡是要插入删除行的表,尽量不要用 INDIRECT 引用行号。
第三,跨表引用时表名带空格或者特殊字符必须加单引号,否则返回 #REF!。前面给的无条件加单引号的写法可以规避。第四,引用的目标被删除会直接返回 #REF!,而且没法恢复。第五,它不能引用已关闭的工作簿,只要源文件关着就返回 #REF!,这也是很多“链接表”用着用着就失效的原因。
替代方案方面,值得记住三条。第一条,只需要在某一列里取第 N 行,用 =INDEX(B:B, A1) 替代 =INDIRECT("B"&A1),INDEX 不是易失函数而且会随插入行自动调整。第二条,需要动态改变区域大小,可以用 =OFFSET(B2, 0, 0, A1, 1) 或者 INDEX 配 SEQUENCE,虽然 OFFSET 也是易失函数,但它至少会随结构调整。第三条,需要做二级联动下拉,现代做法是用动态数组定义名称,比如把名称定义为 =FILTER(城市列, 省份列=汇总表!$A$2),再在数据验证来源里写这个名称,既不需要 INDIRECT 也不怕插入行。本书 OFFSET:会动的区域引用 和 CHOOSE 和 INDEX:不写 IF 也能分支 会分别展开这两条路。
版本方面,INDIRECT 是老函数,所有版本都支持,包括 Excel 2007 甚至更早。这一点是它相对动态数组函数的优势:如果你做的模板要在 Excel 2016 上打开,INDIRECT 依然能用,而 XLOOKUP 和 FILTER 都不能。所以在“需要兼容老版本”和“需要动态引用”两个条件同时成立时,INDIRECT 往往是唯一选择,此时要做的就是控制用量、接受它的性能代价,并把地址字符串集中在一处便于维护。做清单类需求时也可以配合 UNIQUE:去重不用再点删除重复项 生成的名称来减少硬编码。
常见问题
INDIRECT 引用的是别的单元格里的地址,这种用法有什么讲究?
这种写法叫间接引用,也是函数名字的来源。假设 A1 里放着文本 D100,那么 =INDIRECT(A1) 会取 D100 的值。它的价值在于把“要引用哪儿”这件事交给数据来控制,改数据就能改引用,不用改公式。讲究有三点:一是地址文本必须完全合法,多一个空格都会报错,建议用 TRIM 清理;二是一定要配 IFERROR,否则用户填错一个字整张表就红了;三是尽量别让用户手工填地址,改成填月份、部门这类业务词,由公式负责拼接,出错概率会低很多。
为什么我的 INDIRECT 返回 #REF!?
三个最常见的原因。第一,跨表引用时工作表名里有空格、括号或者中文标点,却没加单引号,解决办法是无条件加上,写成 =INDIRECT("'"&H1&"'!B2")。第二,H1 里填的工作表名在文件里根本不存在,或者多了空格,检查办法是用 =H1&"|" 看一眼实际内容。第三,引用的是另一个已关闭的工作簿,INDIRECT 不支持跨工作簿引用关闭的文件,必须打开源文件或改用别的方案。
INDIRECT 和 INDEX 都能按行号取值,该用哪个?
优先用 INDEX。=INDEX(B:B, A1) 和 =INDIRECT("B"&A1) 效果一样,但 INDEX 不是易失函数,不会拖慢重算,而且当你在 B 列上方插入一行时,INDEX 的引用范围会自动跟着调整,INDIRECT 不会。只有在“要引用的工作表名也是动态的”这个 INDEX 做不到的场景下,才必须用 INDIRECT。简单判断:地址里的表名固定就用 INDEX,表名也要变才用 INDIRECT。
用 INDIRECT 做二级下拉菜单,插入行之后选项错位了怎么办?
这是 INDIRECT 的经典问题。因为它引用的是写死的字符串区域,插入行之后区域不会自动扩展,于是下拉选项会少一个或者多一个空值。解决办法有两条:一是把下拉的来源区域建成超级表,用结构化引用定义名称,超级表会自动扩展;二是改用动态数组定义名称,写成 =FILTER(城市列, 省份列=汇总表!$A$2),完全不依赖固定地址。两条都比传统做法稳,第二条在新版本里最推荐。
我的文件有很多 INDIRECT,变得很卡,怎么优化?
先确认是不是它的问题:把 INDIRECT 换成静态值测试一下响应速度。确认之后有三条优化路径。第一,把能换成 INDEX 的全换掉,这是收益最大的一步。第二,把 INDIRECT 的结果集中放在少数几个辅助单元格里,其他公式都引用这几个单元格,而不是每个公式各写一遍 INDIRECT,把几百次易失计算降到几次。第三,把计算模式临时改成手动,批量改完数据后按一次 F9,适合数据录入型的场景。
INDIRECT 能不能配合名称管理器做动态数据源?
可以,而且这是它最推荐的用法之一。做法是:在名称管理器里新建一个名称,引用位置写成 =INDIRECT(汇总表!$H$1&"!A1:F500"),之后所有公式都用这个名称而不是直接写 INDIRECT。好处有三个:公式里看不到复杂的拼接逻辑,可读性更好;需要修改时只改名称定义一处;换数据源时只要改 H1 一个单元格。注意名称定义里的 INDIRECT 依然是易失的,所以数量也要控制。
