做自助分析的人大概都遇到过这种场景:看板上的毛利率比财务口径少了两个点,来回查了半天,最后发现分母的聚合层级错了,SUM 和 SUM_AGG 用反了。FineBI 里可用的函数数量不算多,但每一个都有自己的脾气:有的对数据类型极敏感,有的在明细表和指标卡里返回完全不同的结果,还有的在筛选器一动之后名次就全变了。这些问题官方文档不会逐条写,只能靠自己一条一条踩出来、记下来。
我这份清单最开始只有三行,全是报错信息,后来慢慢长成了现在这个样子:每个函数记三样东西,签名、边界、搭配。它解决的不是“会不会写公式”的问题,而是“写完敢不敢用”的问题。适合两类人看:一类是刚接手自助报表、被字段类型和聚合方式绕晕的新手;另一类是用了一两年、能写出公式但偶尔被奇怪的空值和排名坑到的老手。下面这些内容都是围绕 FineBI 公式编辑器里的常见函数展开的,涉及版本差异的地方我会明确标出来,你照着试一遍就知道自己那边是哪一种。
1. 我为什么把函数写成带"坑位"的清单
1.1 函数在自助分析里的真实位置
很多人对 BI 里函数的期待是"做炫酷计算",实际上日常 90% 的公式都在干三件事:补口径、清脏数据、对齐时间粒度。补口径是把业务定义的算法落到字段上,比如"有效订单"要排除测试单和已取消单;清脏数据是处理从上游系统导出来的各种奇葩字符串;对齐粒度则是让明细行和汇总行能在同一个组件里算得通。这三件事都不需要多高级的函数,但对函数的边界条件要求极高,一旦出错,看板上的数字就是错的,而且错得很安静。
我在清单里给每个函数都留了一个"边界"位置,就是专门写它在什么情况下会给你错误答案而不是报错。这一栏的价值远大于"作用说明"那一栏——报错你至少知道自己错了,返回一个看起来合理的错值才最要命。比如文本转日期时格式串不匹配,很多版本不会抛异常,而是直接返回空值,整列变成空白,你在图表上看到的是"数据缺失",而不是"格式写错了"。
1.2 一条函数笔记该记什么:签名、边界、搭配
我用的结构很简单,三条:签名(参数顺序和类型)、边界(空值、零值、负数、超长文本、格式不匹配时分别返回什么)、搭配(它常和谁一起用,组合起来能解决什么)。第三条是最容易被忽略的,但恰恰最有用。因为实际工作中你几乎不会单独用一个函数,绝大多数公式都是三层嵌套起步。
举个我记了很久的例子:LEFT单看没什么可说的,但它和LEN、FIND搭配就能做出"按分隔符取第 N 段"的效果;再和IF搭配就能做容错——找不到分隔符时返回原值而不是截断出半截脏数据。这种搭配记忆比背参数表有用得多,因为它直接对应一个能复用的解决方案。
| 记录项 | 写什么 | 为什么重要 |
|---|---|---|
| 签名 | 参数顺序、必填项、类型要求 | 少一个参数就报错,类型不对可能静默失败 |
| 边界 | 空值、0、负数、超长、格式不匹配 | 静默返回错值是最难排查的一类问题 |
| 搭配 | 常见组合写法 | 直接对应可复用的解决方案 |
| 验证 | 一组能跑出期望值的最小数据 | 版本升级或改公式后能快速回归 |
1.3 一个被低估的前置动作:把字段类型设对
在写任何公式之前,有一个动作比公式本身更影响结果:确认参与计算的字段到底是什么类型。BI 工具里日期可能是文本、数字可能是文本、ID 可能是数字,这些都会在你写出公式之后才暴露出来。文本型数字参与求和时,有的版本会隐式转换,有的版本直接返回空或者 0;文本型日期做日期运算更是直接翻车。
我的习惯是新建计算字段时先给它定类型,数值就是数值、日期就是日期、标识类字段一律设为文本。ID 类字段设成数值是个典型的自找麻烦——订单号、工单号这类字段经常有前导零,转成数值之后前导零就没了,你在明细表里看到的号和业务系统里的号对不上,排查起来能耗掉半天。
2. 数值与聚合:算得对比赛得巧重要
2.1 取整家族的四种口径,选错就返工
取整这件事看着简单,但我见过太多因为取整口径不一致导致对不上账的案例。常见的几个是ROUND(四舍五入到指定小数位)、ROUNDUP/ROUNDDOWN(向上/向下舍入到指定小数位)、CEILING/FLOOR(向上/向下取到指定基数的倍数)、INT(向下取整到整数)。名字都带"取整",行为完全不同,尤其是碰到负数的时候。
关键差异在于"向哪个方向"。INT(-1.5)在多数实现里得到 -2,因为是向数轴左侧取;ROUNDDOWN(-1.5)得到 -1,因为是向零的方向截断。这两个在正数区间结果一样,一旦数据里出现负数(退款、冲销、负库存),结果就分叉了。做金额调账和分档的时候,这个分叉会让你算出来的档位和业务方对不上。
| 函数 | 典型写法 | 3.7 的结果 | -3.7 的结果 | 适合场景 |
|---|---|---|---|---|
| ROUND | ROUND(值, 0) | 4 | -4 | 金额展示、常规四舍五入 |
| ROUNDDOWN | ROUNDDOWN(值, 0) | 3 | -3 | 向零截断,避免多算 |
| INT | INT(值) | 3 | -4 | 向下取整,分箱下界 |
| CEILING | CEILING(值, 1) | 4 | -3 | 向上取整,容量估算 |
| FLOOR | FLOOR(值, 5) | 0 | -5 | 按固定步长分档 |
我自己的取舍原则是:展示层用ROUND,因为人眼习惯四舍五入;分档层用FLOOR或CEILING配合明确基数,因为业务规则通常是"每满 50 元一档"这种左闭右开区间;调账和结算层一律用ROUNDDOWN,宁可少算不可多算。这三条定下来之后,同一份数据在不同报表里的口径就不会打架。
注意:金额计算不要在明细层就取整,取整只在最终展示层做一次。中间过程每取一次整,误差就累积一次,几百行数据累下来能差出几块钱,这种差异对不上账最难解释。
2.2 聚合函数的层级差:SUM 和 SUM_AGG 到底差在哪
这是 FineBI 里最需要先搞清楚的一对概念。SUM(销售额)是对当前分析粒度做汇总,把很多行压成一个值;而SUM_AGG(销售额)是在明细级别做聚合,保留每一行,同时对整列或指定范围做聚合。听起来绕,用一句话概括:前者改变行数,后者不改变行数。
这个差别在做占比、排名、明细对比时是决定性的。你在明细表里直接写SUM(销售额),每一行显示的都是整列合计值,看起来像"每行都一样",很多人第一次遇到会以为是组件出问题。而如果公式本身已经是聚合函数,字段的求和方式又设成了"求和",有的版本会直接报聚合嵌套错误。
我的经验是先确定这个组件的"分析粒度"是什么——一行代表一个订单、还是一个客户、还是一个品类。粒度定了,才知道公式里该用哪一层。粒度是订单,要算"该订单占客户总额的比例",分子用明细值,分母就必须用SUM_AGG按客户分组;如果分母也用SUM,整张表会被压缩成一行。
2.3 占比、环比里最容易翻车的分母
占比公式的经典错误是分子分母同粒度。写SUM(销售额) / SUM(销售额)得到的一定是 1,因为两边的计算范围完全一致。正确的分母必须是"更大的范围",也就是把分组维度清空之后的总额。FineBI 里实现这件事有三条路:用组件自带的"快速计算→占比"(最省事,推荐)、用DEF_ADD(SUM(销售额), [])这种把维度置空的写法、或者单独建一个合计字段做关联。
我更推荐第一条,因为它是工具内置的语义,切换维度之后会自动跟着变。手写分母的问题在于它是硬编码的,你今天按品类分组,明天想看按区域分组,公式里的维度得手改,改漏了就是错的数字。用DEF_ADD的时候尤其要注意第二个参数——维度列表传空和传当前维度是两回事,传空才是全表。
提示:任何"占比加起来等于 100%"的检查都应该做成固定动作。如果加起来是 98% 或者 102%,大概率是某一类的值为空被排除了,而不是计算错误。
3. 字符串函数:脏数据的第一道过滤器
3.1 截取、拼接与按分隔符拆列
文本处理的基本盘是LEFT、RIGHT、MID、LEN、CONCATENATE、SPLIT。MID(文本, 起始位置, 长度)需要特别注意起始位置是从 1 开始还是从 0 开始,FineBI 沿用的是从 1 开始的习惯,和 Excel 一致,但你如果脑子里想着编程语言的索引就会差一位,截出来的字符串整体偏移一个字符,肉眼还不容易发现。
按分隔符拆列是高频需求,比如"省-市-区"这种合在一起的字段。稳妥的写法是配FIND定位分隔符,再用LEFT/MID去截。但更省事的是SPLIT(文本, 分隔符, 序号),直接取第几段。代价是它对异常值不友好——如果某行只有两段而你要第三段,有的版本返回空,有的返回原值,需要自己测一遍。我在清单里给SPLIT标了个红:上线前必须用"段数不足"的样本验一遍。
CONCATENATE拼接时要注意空值。多数实现里空值参与拼接会得到空,而不是"忽略空值拼其他的"。所以拼地址这类字段时,正确写法是先用IFNULL把空值兜成空字符串,再拼接,否则一个区县字段为空,整条地址就全空了。
拼接容错写法示例 CONCATENATE(IFNULL(省,""), IFNULL(市,""), IFNULL(区,""), IFNULL(详细地址,""))3.2 替换、去空格与看不见的字符
TRIM只清首尾的空格,中间连续的空格不处理,而且对全角空格和制表符经常无能为力。从各类系统导出来的数据里,全角空格、不换行空格、\t、回车符是常客,肉眼在明细表里看不出来,但做分组汇总时,"北京"和"北京 "会变成两个不同的组,图上就多出来一根莫名其妙的柱子。
我的处理链条固定三步:先REPLACE掉已知的特殊字符,再TRIM,最后如果不是关键字段就统一做一次规范化。全角空格的处理可以直接写替换,把它换成空字符串。
文本清洗常用组合 TRIM(REPLACE(REPLACE(REPLACE(字段, " ", ""), "\t", ""), "\r", ""))REPLACE(文本, 旧串, 新串)是全量替换,SUBSTITUTE在某些实现里支持指定替换第几次出现,做"只替换最后一个分隔符"这类操作时用得上。这两个的差别不大,但如果你需要精确控制替换次数,就得用后者。
注意:清洗类公式改完之后,务必用
COUNTD(字段)对比清洗前后的去重计数。数字没变说明洗的是同一个值,数字变小说明确实合并了之前被误判为两个的值,这个对比是最快的验证手段。
3.3 把明细压成一行:分组内文本聚合
给客户做汇总表时经常会遇到"想看每个客户下所有订单号拼成一行"的需求。这类需求在公式层面不一定有现成函数,取决于你用的版本——有的版本提供了分组内字符串拼接的聚合函数,参数是字段和分隔符;没有的话,走自助数据集的分组汇总配合自关联,或者干脆在 SQL 数据集那层用字符串聚合函数处理好再进来。
我的判断标准是:如果拼接后的字段只用于展示(比如在明细表里给业务看"该客户涉及哪些品类"),就在数据集层做;如果还需要参与后续计算,就不要拼接,老实用一对多关联。拼接出来的长字符串在后续计算里几乎没法用,还容易超长截断。
另外提醒一句,拼接后的长度是有上限的,客户下挂几千个订单号拼起来,超出限制会被截断,而且截断是静默的,你在表里看到的就是"少了几个"。需要完整清单的场景直接导出明细,别指望一个单元格装下。
4. 日期函数:时间序列分析的地基
4.1 文本转日期与格式化的双向路
TODATE(文本, 格式)和FORMAT(日期, 格式)是一对反向操作,几乎所有日期相关的坑都出在这两个身上。第一条铁律:格式串必须和文本的实际长相完全一致,包括分隔符和位数。2024/3/5和2024-03-05是两个完全不同的格式串,混在一列里就会有一部分转不出来。
第二条:格式串是区分大小写的。MM是月份,mm是分钟,HH是 24 小时制,hh是 12 小时制,yyyy是四位年份,YY是两位。这个大小写规则来自底层的 Java 日期格式,写错了不会报错,只会给你一个看起来很像但不是你要的结果——比如把月份的MM写成mm,格式化出来的"月"其实取的是当前时间的分钟数,整列数据的月份就成了 0 到 59 之间的随机数。
| 格式串 | 含义 | 容易写错的点 |
|---|---|---|
| yyyy-MM-dd | 四位年-月-日 | 写成 YYYY 在某些实现里是周年 |
| yyyy-MM | 年-月 | 用 mm 会变成分钟 |
| MM-dd | 月-日 | 排序时需要补零 |
| HH:mm:ss | 24 小时时分秒 | 用 hh 会变成 12 小时制 |
| yyyy-MM-dd HH:mm:ss | 完整时间戳 | 分隔符 T 或空格必须一致 |
4.2 年月周粒度的对齐:截断和偏移要分清
做月度趋势时最常见的错误是:把日期格式化成yyyy-MM之后直接用来排序,结果2024-10排在了2024-2前面——因为格式化之后它是文本,文本排序是按字符比大小的。解决办法有两个:一是格式化时用补零的yyyy-MM(月份两位,1会变成01),文本排序就正确了;二是保留日期类型,用构造月初日期的方式做截断,DATE(YEAR(日期), MONTH(日期), 1)出来的始终是日期型,排序和图表的横轴都不会出问题。
我倾向第二种。因为文本型的"月份"虽然能排序,但你在它上面做日期运算(比如往前推一个月做同比)就做不了了,还得再转回去。多一次转换就多一个出错点。日期型字段从进入模型到画成图,最好全程都保持日期类型,只在最终的标签展示上用FORMAT转一次文本。
周粒度的坑更多。一周从周几开始,不同系统不一样,有的从周日算,有的从周一开始。WEEKNUM这类函数通常有第二个参数控制起始日,但默认值未必符合你们业务的习惯。如果业务是按自然周(周一到周日)结算,而函数默认从周日开始,你算出来的每一周都会多含一天上周末的数据,做周环比时数字会持续偏移。
提示:周粒度上线前,拿一个已知的完整周(比如跨月的那一周)单独验证一遍,把结果和业务方手里的周报对一下。十分钟的验证能省掉后面无数次的解释。
4.3 同比环比:日期怎么挪
同比是把本期和"去年同一时期"比,环比是和"上一个相邻周期"比。实现上都需要把日期做位移,常用的是DATEDELTA(日期, 偏移量, 单位)和DATESUBDATE(日期, 偏移量, 单位),一个加一个减,单位可以是天、月、年。
做同比时最稳的写法不是"今年 3 月比去年 3 月",而是先确定一个比较基准,比如"本期累计到 3 月 15 日,去年同期也取到 3 月 15 日"。如果去年同期直接取整个 3 月,你比的就是 15 天的量和 31 天的量,出来的增长率毫无意义。这个细节在月度看板上尤其重要——月初打开看板,本期只有 5 天数据,同比却拿了去年整月,图上的线一定是断崖式下跌。
差值计算用DATEDIF(开始, 结束, 单位),返回两个日期之间的间隔数。用它算"距今天数"是高频写法:
距今天数与客户账龄 DATEDIF(下单日期, TODAY(), "D") DATEDIF(首次成交日, TODAY(), "M") / 124.4 三个高频的粒度陷阱
第一个是月末。用DATEDELTA(日期, 1, "M")去推下个月的同一天,如果当前是 1 月 31 日,推出来可能是 2 月 28 日或者 3 月 3 日,取决于实现。需要精确控制月末的场景,要用构造月末的逻辑而不是简单加一个月。
第二个是跨年。按月做序列时,只取月份数字会漏掉年份,2023 年 1 月和 2024 年 1 月会被归到同一组。所有按月聚合的字段都必须带上年份,yyyy-MM或者DATE(YEAR(日期), MONTH(日期), 1)都可以,别只取MONTH。
第三个是时区与时间戳。如果数据源里的时间是带时分秒的字符串,直接按日期分组会和你想的不一样——2024-03-15 23:30和2024-03-16 00:30在按天截断后分属两天,但如果原始数据用的是别的时区,边界上的数据就会串天。日汇总类看板建议在数据集层就统一截断到天,不要留到组件里再处理。
5. 逻辑与空值:最容易被低估的一段
5.1 IF 的嵌套和更适合它的替代品
IF(条件, 真值, 假值)是最常写的函数,也是最快失控的函数。三层以上嵌套之后,公式变成一坨括号,改一个条件要花十分钟找匹配,而且嵌套的求值顺序是从内向外,你读的时候是从左向右,两边对不上就容易改错分支。
我的做法是超过三档就用SWITCH或者改成"分段字段 + 排序"的思路:先建一个数值型的分档字段,用多个简单的IF逐条判断并赋值(1、2、3、4),再用这个分档字段去关联一张维表拿到中文名称。这样每个IF都是单层、可独立验证的,出问题只影响一个档位,不会全盘错乱。
条件里还要注意AND/OR的优先级和括号。IF(A=1 OR B=1 AND C=1, ...)这类写法在不同实现里的结合顺序可能不同,括号不加就是给自己埋雷。我现在的习惯是,只要条件里出现两个以上的逻辑运算符,一律把每个子条件用小括号包起来,可读性也一起解决了。
5.2 NULL 不是 0,也不是空字符串
这三个东西在 BI 里是完全不同的:NULL是"没有值",0是"值为零",""是"值为空字符串"。它们在明细表里可能都显示成空白,但参与计算时行为完全不同。最常见的翻车是求和:NULL参与算术运算,结果还是NULL,一个空值能让整个计算结果变成空白;而0参与运算不影响结果。
ISNULL(字段)用来判断是不是空,返回布尔;IFNULL(字段, 兜底值)或者NVL(字段, 兜底值)用来给空值兜底。后两个在很多版本里是等价的,用哪个看你那边函数列表里有哪个。兜底值的选择也有讲究:数值字段兜0,文本字段兜空字符串,日期字段不要硬兜,因为日期兜一个假值(比如 1970 年)会污染时间序列分析,宁可让它空着并用ISNULL单独标记。
注意:
COUNT(字段)和COUNT(*)的区别就在这里。前者统计非空值的个数,后者统计行数。算"有手机号的客户数"时要用前者,算"客户总数"时要用后者或者COUNTD(客户ID)。用错了,两个口径的差异能达到几十个百分点,而且数字看起来都很合理,不容易发现。
5.3 除零保护和异常兜底
除法是 BI 里最容易出问题的一类计算,因为分母为 0 或者为NULL时,有的版本返回NULL,有的版本直接报错导致整个组件加载失败。保单写法是把分母包起来判断:
安全的比率计算 IF(ISNULL(分母) OR 分母 = 0, 0, 分子 / 分母)如果业务上"分母为 0"和"分母为空"需要区分展示(比如一个显示 0%,一个显示"—"),就把兜底值分开写,别统一兜成 0。因为这两件事的含义不一样,一个是确实没有量,一个是数据没采到。混在一起看,会让人误判某个区域"业绩为零",实际只是那天没传数据。
另外,异常兜底不要过度。我见过有人给每个公式都套三层IF防错,结果公式本身变成了一团乱麻,真正出问题时根本没法定位。防错的重点应该放在分母、日期转换、类型转换这三个高危动作上,其他地方的错就让它错,报出来反而好查。
6. 窗口类函数:在明细和汇总之间来回跳
6.1 三种排名口径的差别
排名是 BI 里最日常的需求,也是口径分歧最大的地方。三种常见口径:并列名次且后续跳号(1、1、3)、并列名次且不跳号(1、1、2)、不并列直接按行编号(1、2、3)。同样一批数据,三种口径出来的名次全不一样,业务方说"我们排第五",你得先问清楚是哪种。
| 口径 | 出现并列时 | 示例 | 适用场景 |
|---|---|---|---|
| 跳号排名 | 同名次,后续跳号 | 1、1、3 | 排行榜、竞赛名次 |
| 不跳号排名 | 同名次,后续连号 | 1、1、2 | 分档、等级划分 |
| 行编号 | 不并列,强行区分 | 1、2、3 | 取 TopN 明细行 |
FineBI 里这类排名通常通过组件的"快速计算→排名"配置,可以选跳号或不跳号。如果你的需求是"取排名前三的产品",用不跳号会得到可能超过三个的结果(因为并列的都会被选进来),用行编号则严格三个但并列的顺序是随机的,翻页刷新可能变。这两种结果都能用,关键是先跟需求方确认他要哪一种。
6.2 分组占比与累计:筛选器一动,数字就变
分组内占比和累计求和这类计算,最容易踩的坑是"它跟着当前视图走"。你在组件里放了一个区域筛选器,排名和占比就会在被筛选后的范围内重算,看起来名次变了、占比超过 100% 了,实际公式没错,是计算范围变了。
判断方法很简单:把筛选器去掉,看数字是否回到预期。如果回去了,说明是范围问题不是公式问题。要固定范围,就得用能锁维度的写法,把计算范围显式声明出来,而不是依赖组件当前的视图范围。这类"固定维度"的函数在 FineBI 里是DEF_ADD这一族,第二个参数传维度列表,传空表示全表。
我的经验是把"跟着视图走"设成默认预期,因为大多数交互式看板就是希望筛选后重算。只有当你需要"全局占比"或者"全公司排名"这种固定口径时才去锁维度,而且要在组件标题或者备注里写清楚"此指标为全局口径,不受筛选影响",不然一定有人来问为什么筛了区域之后占比没变。
6.3 明细级参与汇总计算怎么组织
最复杂的一类需求是"每一行都要展示,但每一行的值又依赖它所在分组的汇总"。比如明细表里每个订单一行,要同时显示订单金额、该客户的总金额、该订单占客户总额的比例。这三列的计算层级分别是明细、客户分组、两者相除。
做法上,订单金额就是原始字段,客户总金额用SUM_AGG(订单金额)并按客户分组,占比则是两者相除。这里的关键是SUM_AGG不会压缩行数,所以明细行的数量保持不变。如果你用的是SUM,整张表会被压成客户粒度,订单行就没了。
还有一层要注意:SUM_AGG这类函数的计算是在组件渲染时做的,数据量大(十万行以上明细)时会有明显性能损耗。我的处理是能预聚合就先预聚合,把客户总额在数据集层算好存成一列,组件里直接取,比每次渲染都算一遍快得多。明细表超过五万行时,这个优化基本是必须的。
7. 三个可以直接抄的组合模板
7.1 客户价值分层:把连续金额切成档
分层需求随处可见,做法是先用明细级聚合算出每个客户的核心指标,再用IF或分档逻辑切段。下面是按累计消费金额分四档的写法,档位边界用业务定的实际值替换。
客户分层(金额单位:元) IF(ISNULL(SUM_AGG(消费金额)), "未消费", IF(SUM_AGG(消费金额) >= 10000, "高价值", IF(SUM_AGG(消费金额) >= 3000, "中价值", IF(SUM_AGG(消费金额) > 0, "低价值", "未消费"))))写这类公式有两个细节值得注意。第一,判断顺序必须从大到小或者从小到大单向走,不能交叉,否则某个区间的数据会被先命中的条件吃掉。第二,边界值用>=还是>要和业务确认,尤其是"刚好等于 3000"这种卡在边界上的客户归到哪一档,事后争论起来很费劲,不如一开始就写进公式注释里。
分层字段做完之后建议不要直接删掉中间的计算字段,保留一列"原始金额",方便业务方核对。我踩过一次坑:分层结果和财务的名单对不上,查了半天发现是分层用的金额口径(含不含运费)和财务不一样,但因为中间字段被删了,只能从头重算一遍。
7.2 一列搞定同比环比
同比环比也可以用组件的快速计算直接出,但如果要放在同一个表格里对比多个指标,手写公式更灵活。核心思路是把本期和同期分别算出来再相除,同期的取值靠日期偏移或者靠时间维表的关联。
同比增长率(同期口径) IF(ISNULL(同期销售额) OR 同期销售额 = 0, NULL, (本期销售额 - 同期销售额) / 同期销售额)分母为 0 时返回NULL而不是 0,这是有意的:增长率在基数为 0 时没有数学意义,显示 0% 会误导人以为"没增长",显示空白或者"—"更诚实。这个取舍我在好几个项目里都坚持,业务方一开始会问为什么这里是空的,解释一次之后就都认可了。
7.3 敏感字段的展示脱敏
手机号、身份证号这类字段在明细表里直接展示是有风险的,常见的处理是保留头尾、中间打码。用LEFT加RIGHT拼接就能做,注意长度不足时要兜底,别把短号也按固定位数截。
手机号脱敏(保留前3后4) IF(ISNULL(手机号) OR LEN(手机号) < 7, "—", CONCATENATE(LEFT(手机号, 3), "****", RIGHT(手机号, 4)))脱敏要在展示层做,不要在数据集层把原值覆盖掉。原因很实际:业务方迟早会需要原始号码去做回访,覆盖掉之后就得重新接一次数据源。更好的做法是同一张表里同时保留原字段和脱敏字段,原字段设权限,只有特定角色可见,脱敏字段给所有人看。
8. 让这份清单"不断更新"下来的维护方法
8.1 我给每条函数笔记定的固定格式
清单能不能长期用下去,取决于格式是否统一。我的模板是五栏:函数名、一句话作用、最小可用写法、踩过的坑、验证数据。第三栏强制要求写成能直接复制粘贴的完整公式,不要写"参数填字段名"这种废话,因为真正需要抄的时候你根本不想再想一遍参数顺序。第四栏只写亲身遇到的,不抄文档,抄来的坑记不住。
| 栏目 | 内容示例 | 填写要求 |
|---|---|---|
| 函数名 | SPLIT | 写全名,别名另起一行 |
| 一句话作用 | 按分隔符取第 N 段文本 | 不超过 20 字 |
| 最小写法 | SPLIT(地址, "-", 2) | 可直接复制运行 |
| 踩过的坑 | 段数不足时返回原值不是空 | 只写亲测过的 |
| 验证数据 | "京-沪-穗" / "京-沪" / "京" | 至少三条边界样本 |
这个格式最大的好处是逼着你去验证。填第五栏的时候你会发现,很多你以为理所当然的行为其实没测过,一测就露馅。我给每个函数都留了三条样本:正常值、边界值、异常值。这三条样本本身也成了回归测试集,版本升级或者公式改动之后跑一遍,几分钟就能确认没问题。
8.2 踩雷登记:比函数清单更值钱的部分
单独拉一张表记"踩过的雷",格式是:现象、定位过程、根因、修复写法。这张表的价值比函数清单本身还高,因为函数行为查文档总能查到,而"为什么这个报错看起来完全不相干"只有踩过的人知道。
举几个我记下来的典型:一是文本型数字参与求和返回空,现象是"这个指标一片空白",根因是字段类型被识别成文本,修复是在数据集层改类型或者用转换函数强转;二是明细表里出现"每行都一样"的值,根因是用了SUM而不是SUM_AGG,分析粒度被压缩了;三是排名在刷新页面后变了,根因是并列值的排序不稳定,需要加一个唯一的次级排序字段。
第三条特别值得说一句:给排名加次级排序是个小动作,但能解决"刷新一次换一个结果"这种让人怀疑人生的现象。次级排序字段选主键或者时间戳都行,目的是让并列项之间有一个确定的先后,这样每次渲染出来的顺序都是同一个。
维护这件事没什么技巧,就是顺手记。每次排查完一个公式问题,花两分钟把现象和根因敲进表里,三个月之后你会感谢当时的自己。我现在这份清单已经长到需要分页了,但每次接手新的分析需求,翻一遍就能找到七八成的参考答案,比重新摸索快太多。最后分享一个小习惯:每次改动公式,把旧写法在注释里留一行,写上改的原因和日期。半年后回头看,那一行注释往往比公式本身更能帮你回忆起当时的业务背景。