在Dataiku DSS里摸爬滚打了一段时间后,你会发现一个现象:不管打开Prepare、Group还是Window,这些可视化recipe的编辑界面里,总有几个功能长得特别像——预过滤(Pre-filter)、后过滤(Post-filter)、计算列(Computed columns)。很多人最初以为这只是几个零散的功能按钮,直到我在某个用户行为项目里反复调整数据流程时,才意识到它们其实是所有可视化recipe共用的“三步走”骨架。这篇内容就围绕这三个通用步骤展开,说清楚它们各自管什么、按什么顺序执行、以及如何组合起来派上用场。如果你刚接触DSS,或者已经会用但总觉得流程写得不够顺手,这篇值得花十分钟读完。
1. 三个通用步骤的定位与配合逻辑
1.1 为什么可视化recipe都要有这三步
DSS的可视化recipe遵循一个非常朴素的管线思想:先框定要处理的数据范围,再做中间加工,最后验证输出结果。预过滤承担“框定范围”,计算列承担“加工”,后过滤承担“验证输出”。这不是某个recipe特有的设计,而是整个DSS在交互处理层面沉淀出来的统一模式。
相比写代码,这种可视化方式把每个动作都变成一个可观察、可回退的步骤,尤其适合团队协作——旁边的人打开你的recipe,不需要读一堆脚本就能看出你干了什么。我在实际项目里发现,很多新人以为预过滤就是把SQL里的WHERE子句搬到界面里,后过滤就是把HAVING搬过来,计算列就是SELECT里加一个新字段。这么理解不算错,但忽略了三者的层级关系和执行时机,等数据量一上来、逻辑一复杂,很容易在排查问题时绕进死胡同。
1.2 执行顺序与数据流视角
从数据流顺序来看,三个步骤的固定执行顺序是:输入数据集 -> 预过滤 -> 中间处理(包括各类计算列)-> 后过滤 -> 输出数据集。
预过滤一定先于计算列,后过滤最后执行。这个顺序看起来理所当然,实际影响却很大。比如你的数据里有event_type = 'test'的测试流量,如果不在预过滤阶段清掉,后面的计算列会照样给这些行计算,统计口径就会被污染;更麻烦的是,下游的Group或Join也会把这些行算进去,等到汇总时发现问题,再回头追根溯源就很费劲。
另一个值得留意的点是DSS对预过滤的下推优化。当数据源是SQL数据库时,预过滤条件会被尝试翻译成SQL里的WHERE条件,直接推到数据库执行,从而减少读入DSS引擎的数据量。理解这个机制对排查性能问题很有帮助——如果某个预过滤条件用到了自定义函数或特殊的表达式,下推可能失败,数据就会全量拉到引擎端过滤,任务时间会肉眼可见地变长。
2. 预过滤:在数据进门之前先做减法
2.1 预过滤的核心使用场景
预过滤解决的是“有些行根本不该进入后续处理”的问题。我在项目里总结出几类最常用的场景:
- 字段缺失且无意义的行,比如
user_id is null的记录,在用户分析中注定无法归因,留着只会污染后续join。 - 测试流量或标记数据,比如
event_type = 'test',这类数据在开发环境里有用,在正式分析里必须剔除。 - 与本次分析无关的时间范围,比如只分析近30天,就把30天之前的数据挡在门外。
- 明显异常的值域,比如页面停留时长超过24小时、金额为负等不合理数据。
这些都是典型的预过滤场景。核心判断标准是:如果某类行在后续所有步骤里都不需要,那就把它挡在入口。我见过不少项目把所有清洗逻辑都压在预过滤上,条件越堆越多,最后变得很难维护;也见过完全不用预过滤、把过滤散落在各个下游步骤里的情况,那会导致同一个过滤条件在多个recipe里重复出现,改一处漏一处。
2.2 预过滤的配置方法与参数选择
在DSS中,打开任意可视化recipe后,你能在输入数据的面板上看到Filters配置区。点击Add Filter,就可以添加条件。配置时要注意“Match all conditions”与“Match any conditions”的区别:
- Match all:行必须满足所有条件才保留,逻辑上相当于AND。
- Match any:行只要满足其中一个条件就保留,逻辑上相当于OR。
我见过一个真实案例:某同学在预过滤里写了user_id is not null和event_type != 'test'两个条件,本来想表达“排除无效用户也排除测试事件”,结果切成了Match any模式,导致只要满足其中一个条件就被保留了——测试流量全都漏了进来。这类错误在界面操作中特别容易忽略,因为预览结果不会立刻显眼地报错,只有看统计指标波动时才会发现。
操作层面,DSS的过滤条件支持简单的字段条件,也可以切到Advanced模式写表达式。我个人的建议是:能用普通条件就先用普通条件,因为它可读性更强;表达式模式适合日期偏移、多字段联动等复杂判断。例如保留近7天数据,可以写成event_time >= today() - 7之类的表达式,这比每次手工改日期高效得多。
2.3 预过滤的注意事项
第一,预过滤会改变数据口径,这是必然的。下游看到的行数会比原始数据少,做数据对比时不要惊讶,也不要为了让数字好看而放松过滤。
第二,预过滤应该在真正需要时使用。如果某个条件只在个别下游分析里需要,它就属于该下游的后过滤或查询筛选,而不是所有数据管线的公共预过滤。放进预过滤意味着所有基于此recipe的数据都会被影响。
第三,如果数据源在SQL数据库里,尽量让预过滤条件保持简单,以支持下推。条件里频繁使用regex、自定义函数、跨字段复杂运算时,可能要谨慎评估下推能力,否则全表扫描会拖慢任务。
第四,注意采样与预过滤的先后关系。DSS界面中默认会对超大数据集做采样预览,采样行为可能发生在预过滤之前或之后,这取决于数据源类型和配置。因此你看到的预览行数与实际运行结果可能不一致,跑任务前最好用全量数据或调整采样策略确认一遍。
3. 后过滤:在结果出门之前把好最后一道关
3.1 后过滤和预过滤的本质差异
预过滤针对输入行,后过滤针对输出行,这是两者最核心的区别。很多人在学习时会忽略“输出行”这三个字的分量——它意味着后过滤能看到中间处理后的结果,包括由计算列生成的新字段。在Prepare这类行级转换recipe中,后过滤作用在每一行上,本质上就是“先算完,再筛选”。
而在Group这类聚合recipe里,后过滤作用的是聚合结果行。这时候输出字段已经变成了分组键和聚合指标,后过滤条件只能引用这些字段。举个具体例子:按渠道统计用户数后,后过滤写count_distinct(user_id) >= 100,就可以把活跃度不足的小渠道一次性清掉,这种用法在SQL里等价于HAVING子句。
我一直用生活化的类比来记这两个步骤:预过滤是进货验货,后过滤是发货前终检,中间的转换步骤是加工线。进货的时候把石头挑出去,加工完再检查成品有没有瑕疵,顺序错了就会导致加工资源浪费,或者不合格产品流向市场。
3.2 后过滤的配置方法与典型用法
后过滤在DSS中的操作位置一般在recipe的输出区域配置。界面里会列出“输出前筛选”之类的选项,你可以像设置预过滤一样添加条件。配置逻辑完全一致,仍然要注意Match all和Match any的区别。
典型的后过滤场景有三种:
- 聚合后的阈值筛选:Group recipe里按维度分组后,只保留满足条件的组,比如用户数、订单数、总额达到某个门槛。
- 基于计算列的行筛选:Prepare里先算出一个标记字段,再通过后过滤只保留标记字段符合条件的行。这样做的好处是让筛选口径和计算口径集中在同一处,修改时只需改计算列。
- 窗口计算后的截断处理:Window recipe算完排名或累计值后,用后过滤截取Top N或累计占比80%以内的行。
这些用法里,都体现了一个共同的设计思路:先让统计逻辑在计算列或聚合指标里完成,再把筛选放在最后一步,让过滤条件尽量简单、直白。
3.3 后过滤容易踩的几个坑
后过滤第一个坑是“引用不存在的字段”。在聚合recipe中,如果后过滤条件里有某个原始字段,而这个字段没有出现在聚合结果里,运行时会直接报字段不存在。解决方法是把该字段加进分组键,或者先用聚合函数把它变成一个输出指标。
第二个坑是空值导致的组别消失。比如按channel分组后过滤channel is not null,看起来合理,实际上null渠道会被过滤掉。有些业务场景里,渠道为空恰恰代表自然流量或未知来源,反而需要单独统计。所以在写后过滤前,先想清楚null值在当前分析里的业务含义。
第三个坑是Sysy预览与执行结果不一致。后过滤配置好之后,如果输出数据量很大,界面显示的可能仍是采样后的预览。想确认全量结果,需要实际运行一次recipe或查看运行后的dataset统计。
第四个坑是后过滤无法访问未输出的计算列。假如你在Prepare里算了一个score列,但输出时没有把它包含进来,那么后过滤虽然可能在当前步骤还能引用它,但下游recipe完全看不见这个字段。维护时很容易忘记这一点,导致下游想再筛选score时找不到字段。我的习惯是:需要用于后续筛选的辅助列,要么保留在输出集中,要么明确在文档里注明它已在该steps完成后被丢弃。
4. 计算列:把“想算的值”变成“可用的列”
4.1 计算列在可视化recipe中的位置
计算列是可视化recipe里最常见的一类中间加工步骤。从概念上讲,它解决的是“数据里没有但分析需要”的问题——时间戳需要拆成年月日、设备类型需要归并成移动端和PC端、订单金额需要扣除退款、高价值用户需要打标,这些都靠计算列完成。
在DSS中,计算列出现的形式不完全一样。Prepare recipe里有专门的Compute Formula步骤;Group recipe里可以添加聚合计算指标;Window recipe里可以添加窗口函数结果。不管形式怎么变,核心逻辑都是:基于已有字段、通过公式或表达式生成新字段。理解这一点后,你在不同recipe之间切换时会发现思路完全可以复用。
我比较推荐在流程早期就把计算列集中建立。比如分析用户行为,先把hour、weekday、is_mobile、is_new_user这些衍生维度算好,后续所有Group、Join、过滤都能直接引用,整个流程会清爽很多。如果每个下游recipe各自重复计算同样的逻辑,等口径需要调整时,工作量会成倍增加。
4.2 计算列公式写法与常用函数示例
DSS的公式编辑器采用类似Excel的函数风格,对大多数从业者来说上手很快。我在实际项目里最常用的几类写法:
时间字段提取:
hour(event_time) weekday(event_time) date(event_time)条件字段归类:
if(device in ('iOS', 'Android'), 'Mobile', 'Other')空值兜底:
ifNull(channel, 'unknown')数值处理:
round(total_amount, 2)这些公式在编辑器里会实时显示返回类型和部分预览值,写起来比SQL盲写要直观得多。建议新手先点开函数列表看几遍,DSS里的日期函数、字符串函数、聚合函数分类很清晰,很多功能你以为是SQL里才能写的,其实公式编辑器都内置了。
写计算列时有一个细节容易忽略:列名如果包含空格、中文或特殊字符,在公式里需要用引号包裹。我见过有人在公式里写if(列名 = 1, 'a', 'b'),结果一直解析报错,加上引号之后就正常了。另一个细节是公式结果的类型要尽量保持稳定。有的函数在输入为null时返回的类型和正常值不一致,导致字段类型变成混合型,后续排序或过滤时会出现很诡异的错误。
4.3 计算列的性能与可读性
计算列本质上是逐行计算,数据量大时成本不低。我在处理亿级行数的日志时,对计算列的使用有一条原则:能用简单算术和内置函数解决的,绝不引入正则;能拆分成多个简单计算列的,绝不写一个超长公式。正则和字符串复杂运算在行数大的表上会显著拖慢任务,而且排查问题时很难定位是哪一段逻辑出了问题。
可读性方面,给计算列命名时最好带统一前缀,比如calc_、flag_、dim_,这样下游引用时一眼就能看出字段来源。也可以选中计算列后在DSS里补充字段描述,把计算口径写清楚。这个习惯在项目交接时特别有价值——别人看到flag_high_value_user,再点开描述看到“近30天消费金额大于1000且下单次数大于5次”,就不用再翻文档了。
5. 实战案例:从原始点击日志到可分析样本
5.1 业务需求与原始数据情况
用一个虚构但很典型的需求来说明白这三步的组合用法。假设某项目需要统计App的日活跃用户数,并对比工作日和周末的差异,但只看移动端设备,同时要把测试流量排除在外。
原始数据是一份点击日志表,字段大致如下:user_id、event_time、event_type、device、page、duration。数据质量并不完美,存在三类明显问题:部分行的user_id为空;有event_type = 'test'的测试流量;少量device不在目标设备列表内。
5.2 第一步:配置预过滤
在Prepare recipe里配置预过滤条件:
user_id is not nullevent_type != 'test'
这两个条件都放在预过滤里,原因很简单:后续所有分析都不需要这些行,早剔除既能减少计算量,也能避免测试数据污染统计口径。特别是user_id is not null这个条件,如果留到后面Group时再过滤,聚合计算就会白跑大量无效行。
配置完成后,可以先看一眼过滤后的行数变化。如果原始数据有几千万行,这一步通常能砍掉一小部分“坏数据”。这里有个小技巧:把预过滤和数据质量视图结合起来,先查看各字段的空值比例和取值分布,再决定过滤条件,比盲目添加条件更靠谱。
5.3 第二步:添加计算列
接着添加三个计算列,为下游聚合做好准备。
第一列,提取日期,作为后续按天聚合的分组键:
date(event_time)第二列,标记工作日还是周末。注意不同DSS版本中weekday()的返回值可能是0到6或1到7,可以直接在公式编辑器里看预览确认:
if(weekday(event_time) in (6, 7), 'Weekend', 'Weekday')第三列,标记是否为移动端设备:
if(device in ('iOS', 'Android'), 'Mobile', 'Other')这三个计算列有清晰的分工:一个服务于日期分组,一个服务于工作日/周末对比,一个服务于设备类型筛选。把它们放在一起计算,下游就可以直接用这些字段,不必重复推导。
5.4 第三步:配置后过滤
在Prepare recipe的输出侧配置后过滤,条件为:
is_mobile == 'Mobile'可能有人会问,既然最终只看移动端,为什么不直接在预过滤里写device in ('iOS', 'Android')?我特意在案例里这么设计,是想说明一个细节:当“是否移动端”的标准可能需要调整时(比如后续把webview也归为移动端),把口径放在计算列里,只需要改一处if条件,预过滤不用动。这比在多个过滤条件里同步修改更不容易出错。
后过滤还承担了一个任务:它能看到计算列的结果。也就是说,这个过滤执行在is_mobile生成之后,所以条件可以直接引用该字段。这一层逻辑放到后过滤,比放到下游的SQL查询里更规范。
5.5 第四步:接Group recipe做聚合后过滤
从上述Prepare输出新建一个Group recipe,按calc_date和is_weekend分组,计算:
distinct(user_id)得到每个日期、每天是工作日还是周末的去重用户数。这时候如果只想看活跃度达到一定规模的日子,就在Group recipe的后过滤添加条件:
distinct_user_count >= 100这个案例展示了两层后过滤:在Prepare里对行级结果过滤,在Group里对组级结果过滤。两者的执行位置不同,但配置方式几乎一样,理解了这个区别,就不会再把行级和组级条件混在一起写。
6. 常见问题排查与避坑速查
6.1 高频问题与排查思路
我在实际使用DSS的过程中,整理了几个出现频率较高的问题,做成速查表供大家参考。
| 现象 | 可能原因 | 排查与解决 |
|---|---|---|
| 预过滤后行数没有变化 | 条件逻辑写成了Match any,或配置后未保存 | 检查过滤模式,确认recipe已保存 |
| 后过滤报字段不存在 | 引用了聚合输出中没有的字段 | 打开输出字段列表,改用分组键或聚合指标 |
| 计算列结果出现大量空值 | 源字段类型不匹配,或包含空字符串 | 先用数据质量视图检查源字段,再用ifNull兜底 |
| 任务运行速度突然变慢 | 预过滤下推失败,或计算列用了复杂表达式 | 简化条件,避免正则和高成本函数 |
| 预览结果和运行结果不一致 | 采样导致预览数据范围不同 | 改用全量数据运行一次,确认实际统计 |
| 后过滤条件对null组无效 | null值在过滤中直接被排除 | 显式处理null,或用ifNull先转换 |
这张表里的问题,我基本都踩过。特别是“预览结果和运行结果不一致”这条,最容易被新人误判为DSS的bug。其实绝大多数情况下只是采样机制在起作用,而不是计算逻辑出错。
6.2 我踩过的一些坑与习惯做法
分享几个我后来固化成习惯的做法。第一个习惯是,新建任何recipe之前,先花两分钟看输入数据的字段质量和分布。DSS的数据集浏览页面能直接展示每列的类型、空值率、取值分布,这些信息比直接开写recipe重要得多。很多过滤条件、计算列的设计,其实在看了一眼数据质量之后就自然清楚了。
第二个习惯是,所有过滤条件尽量集中到recipe中同一片区域。预过滤就放在预过滤区,后过滤就放在后过滤区,不要一会儿在Prepare里过滤,一会儿又跑到下游SQL里过滤同一批条件。集中管理的最大好处是,维护口径时有一个确定的修改位置。
第三个习惯是,计算列命名一定要带上语义前缀。我用calc_表示计算字段,flag_表示标记字段,dim_表示维度规整字段。这样下游在引用时不会把原始字段和衍生字段搞混。尤其当同一个源表被多个recipe使用时,字段命名规范直接影响协作者的理解成本。
第四个习惯是,每次修改过滤条件或计算列后,顺手记一笔变更原因。不用写长篇,只要在字段描述里写一句“排除测试流量,口径与某需求一致”就够。几个月后回来看,这些备注能帮你快速回忆当初为什么这么设计,避免因为忘记口径而误改。
最后分享一点我的个人体会
在实际项目里摸爬滚打之后,我越来越觉得预过滤、后过滤和计算列不是三个孤立的按钮,而是一套围绕数据流的“三段式”控制逻辑。能前置的判断放到预过滤,让进入计算环节的数据更干净;衍生逻辑集中到计算列,让口径修改有一个确定的地方;对最终结果的把关放到后过滤,让输出质量最后被守一遍。
至少在我维护的这个点击日志项目里,后来新增的所有清洗需求,几乎都能顺着这三段快速定位到修改点。刚开始接触DSS的朋友,可以先分别把这三个步骤的用法练熟,再组合到同一个流程里,你会发现它比零散地用各种SQL查询要直观得多,也更适合在团队里传阅和复用。