我抓交易欺诈用的六个 SQL 套路
文章摘要
作者在某政府福利项目的”项目合规(program integrity)”团队做数据分析。开篇就把热门技术全否了:抓欺诈”不需要机器学习、不需要图数据库,也不需要 Gartner 今年在吹什么”,绝大多数欺诈检测就是 SQL——对着正确的表,做正确的 join,找正确形状的数据。文章给出六个他每接手一个新数据集就会立刻搭起来的模式,按照构建顺序排列。
第一个是速度(velocity)——偷到信用卡的人最怕持卡人发现,于是会拼命刷。基本写法是按小时桶聚合统计交易数,超过阈值的就报警;进阶版用窗口函数做 5 分钟滑窗(QUALIFY tx_in_last_5min >= 5)。同时跑 1 分钟、5 分钟、1 小时三个版本,因为不同欺诈的节奏不一样:测卡机器人几秒一波,福利倒卖团伙可能慢悠悠刷一下午。第二个是不可能的旅行(impossible travel)——同一张卡七分钟前在芝加哥、七分钟后在洛杉矶,必定有一笔是克隆卡。用 LAG() 取前一笔的时间和地点,配合 haversine 函数算大圆距离,速度超过 600 mph(”比飞机还快”)就报警。
第三个是金额异常。两类金额在欺诈里超常见、在正常消费里几乎不出现:$1、$5、$10 等小额整数(测卡用——咖啡是 $4.73、加油是 $52.81,整数本身就是信号),以及刚好低于阈值的金额($99.99 卡在”$100 要查 ID”的线下、$499.99 卡在 ATM 日限的线下,作案者知道规则)。第四个是可疑商户——加油站读卡器被装了 skimmer 后,所有刷过的卡都进了黑库。症状是:短时间内大量陌生卡在同一个商户消费偏高。直接用静态阈值不行(Costco 90 秒就过),所以要用每个商户的 168 小时(一周)滚动平均作为基线,做 3 倍突刺检测。
第五个是反作息时间——朝九晚五的人不会半夜买汽油,所以先用 90 天历史给每个持卡人算个人的正常活跃小时段(用 FILTER (WHERE tx_count >= 2) 过滤掉偶发深夜消费,避免一次性事件污染基线),落在区间外的交易报警。新账户没有历史就跳过这条规则。第六个不是模式而是基础设施:用窗口函数把上面五个信号拼成可组合的链。作者强调 QUALIFY 在 Snowflake / BigQuery / Databricks / Teradata 里都能用,Postgres 没有就外面包一层 CTE。整体哲学是 KISS——能用 SQL 做完的就不要上 ML,可解释性远比 F1 分数重要。
HN 评论精华
-
reconnecting:扔了颗炸弹——”Fixel Smith”这位作者疑似 AI 生成的人设:同一个名字几天之内同时以音乐家、小说家、欺诈分析师身份发文,让整个内容的可信度被严重打问号。这条评论让讨论区的画风急转,下面很多人开始重新审视文章细节。
-
bob1029:从合规角度论证为什么金融机构必须用确定性规则——拒一笔交易要在几分钟内向监管解释清楚,黑箱 ML 模型给不出这种解释。所以 SQL 启发式不是”低端”做法,而是合规的硬约束。
-
tdeck(业内人士):实操经验背书——公司一般都是先用批处理 SQL 跑每日标记,等数据积累够再上 ML 提升 precision、但 recall 不会退步。SQL 是合法的起点,文章方向没错。
-
aleph_minus_one:观察很犀利——真正高水平的犯罪分子早去做白领犯罪了,能被这套启发式抓到的多半是业余作案者,所以这套规则对付小偷小摸有效,但别幻想能抓到内行。
-
jwr(受害者视角):美国银行对国际交易过度敏感,加上家里几个孩子用 Apple Pay 共享父母卡,”不可能的旅行”规则在他家庭里天天误报。enoent 补充:真正的实时反欺诈要毫秒级延迟,SQL 查仓库根本来不及,必须上内存数据库和流处理——文章没提这一条限制。
-
jstanley(方法论质疑):作者那条”$X.99 是欺诈信号”的逻辑有问题——很多国家本来就常用 .99 定价;”半夜买汽油”在轮班工人、医护、网约车司机里完全正常,简单按小时切会扫到一半用户。
-
nswango:从用户体验角度补刀——凌晨 2 点买药、买东西的”少数合法场景”,往往就是用户最需要顺利刷卡的紧急时刻,被误拦的伤害远大于一笔小额欺诈。