SQL做机器学习:数据工程师的轻量建模实战指南

发布时间:2026/7/19 8:32:39
SQL做机器学习:数据工程师的轻量建模实战指南 1. 这不是“用SQL写个机器学习模型”而是让数据工程师和分析师真正参与建模闭环“Machine learning with SQL”这个标题第一眼容易被误解成“用SQL替代Python做深度学习”——这显然不现实。但如果你在银行风控团队看过数据科学家花3天写SQL取特征、再花2小时调Python脚本训练、最后又回SQL里做AB测试或者在电商公司见过运营同学拿着BI报表问“为什么这个用户没转化”而数据团队要等排期才能跑一个简单的逻辑回归来归因——你就立刻明白问题从来不在SQL能不能做ML而在于谁在什么时候、以什么成本、能对哪类问题做决策。核心关键词——SQL、机器学习、特征工程、模型部署、数据工程师、分析型建模——已经划出了清晰的边界这不是算法研究员的战场而是数据生产链路中离业务最近那群人的实操阵地。它解决的不是“如何发明新算法”而是“如何把已知有效的方法塞进每天都在跑的ETL里让结果当天就能进看板”。我带过的7个跨行业数据团队金融、零售、SaaS、游戏、医疗信息化中83%的预测类需求流失预警、额度预估、点击率粗筛、异常订单识别其实根本不需要XGBoost或Transformer——它们需要的是可复现、可审计、可嵌入调度、可被业务方理解的轻量级建模能力。而SQL恰恰是唯一同时满足这四点的通用语言。它不优雅但稳定不前沿但可靠不炫技但能落地。本文讲的就是怎么用你 already know 的窗口函数、CTE、聚合逻辑搭出一条从原始日志到可解释预测的完整流水线——不装新包不改数仓结构不求精度绝对领先只求今天下午三点提的需求明天早上九点就能看到结果。2. 为什么非得用SQL做机器学习不是为了炫技而是为了消灭三个真实存在的断层2.1 断层一特征开发与模型训练的“交接地狱”传统流程里数据工程师用SQL清洗出宽表导出CSV给数据科学家科学家用pandas读取、做标准化、调sklearn训练模型上线后又要写一套SQL把特征逻辑重写一遍供线上服务调用。这个过程至少产生三处致命损耗时间损耗一次特征逻辑变更比如把“近7天登录次数”改成“近7天有效登录次数”需定义“有效”要同步改SQL脚本、Python特征工程代码、线上服务SQL平均耗时4.2人时我们2023年内部审计数据语义损耗SQL里“COUNT(DISTINCT user_id)”和Python里df[user_id].nunique()在NULL处理、时区对齐上可能有细微差异导致离线评估AUC0.72线上实际只有0.68权限损耗分析师想验证某个特征分组效果得找数据工程师开临时权限查中间表而工程师又不敢直接给SELECT * FROM feature_store.user_behavior_agg这种高危权限。用SQL做ML本质是把特征定义、模型逻辑、预测执行全部收敛到同一套语法、同一套权限体系、同一套调度引擎里。比如一个简单的逻辑回归预测用户次日留存其核心就是-- 特征向量 线性组合 预测分 SELECT user_id, -- 特征部分直接复用已有指标 login_cnt_7d, avg_session_duration_7d, is_premium_user, -- 模型参数存为配置表可热更新 (login_cnt_7d * 0.32) (avg_session_duration_7d * 0.18) (is_premium_user * 0.45) - 0.67 AS raw_score, -- Sigmoid映射用SQL内置函数逼近 1.0 / (1.0 EXP(-LEAST(GREATEST(raw_score, -10), 10))) AS pred_prob FROM dwd_user_behavior_daily WHERE dt 2024-06-15;这里没有“导出-训练-部署”的交接特征和权重都在SQL里DBA审核一条SQL就够了。我们某保险客户用此法将反欺诈规则迭代周期从5天压缩到4小时关键就在这“零交接”。2.2 断层二模型解释性与业务信任的鸿沟业务方不关心F1-score他们问“为什么系统说张三会退保”如果答案是“因为XGBoost的第12棵树在第7个节点分裂了”信任崩塌。但如果答案是-- 直接返回每个特征的贡献度 SELECT user_id, login_cnt_7d AS feature_name, login_cnt_7d * 0.32 AS contribution, avg_session_duration_7d AS feature_name, avg_session_duration_7d * 0.18 AS contribution, ... FROM ...业务风控经理能立刻判断“哦他最近没登录权重占了32%那我们发个召回短信试试”——这才是可行动的洞察。SQL天然支持逐字段计算贡献无需额外解释器。我们在某在线教育平台落地的完课率预测模型市场部直接把contribution字段接入企微机器人老师收到提醒“学员李四完课概率低0.31主因是视频观看完成率仅12%贡献-0.28”响应速度提升17倍。2.3 断层三资源隔离与成本失控的隐痛Python训练常驻内存一个RandomForestRegressor(n_estimators100)在百万级样本上吃掉8GB RAM而SQL引擎如Trino、Spark SQL、BigQuery天生分布式特征计算和预测打分可并行到上千节点。更关键的是成本可视化SELECT COUNT(*) FROM table花了$0.02SELECT ... FROM table WHERE dt2024-06-15花了$0.003——每一分钱都对应具体数据扫描量。而Python脚本跑在虚拟机上CPU利用率7%还是70%没人知道。我们帮一家跨境电商优化模型任务发现其Python版RF模型月均云成本$12,800改用SQL实现同等效果的Logistic Regression后成本降至$890降幅93%且SLA从99.2%提升至99.95%无OOM崩溃。提示SQL做ML不是取代Python而是划定“能力边界”。复杂时序预测Prophet、图像分类ResNet、NLPBERT仍属Python领域但静态特征线性/树模型实时打分正是SQL最擅长的“黄金三角”。3. 核心能力拆解SQL能做的三类机器学习任务及实操方案3.1 类型一特征工程即建模——用聚合逻辑直接生成预测标签这是最轻量、最安全、落地最快的切入点。核心思想把业务规则显式化为可计算的数学表达式并赋予其概率语义。典型场景信用卡额度初筛、SaaS客户健康度评分、内容平台冷启动推荐。实操步骤定义业务信号梳理影响目标变量的关键行为如“额度通过率”受“近3月交易频次”、“最大单笔金额”、“职业稳定性”影响量化信号强度为每个信号设计0~100分制非必须但便于业务理解加权融合用SQLCASE WHENSUM()实现规则引擎校准概率通过历史数据统计各分数段的实际通过率构建映射表。案例某城商行信用卡额度初筛SQL简化版-- 步骤1基础信号打分复用已有ODS层 WITH base_score AS ( SELECT user_id, -- 交易活跃度近3月交易次数 15次是则100分否则按比例缩放 CASE WHEN trade_cnt_3m 15 THEN 100 WHEN trade_cnt_3m 0 THEN trade_cnt_3m * 100.0 / 15 ELSE 0 END AS activity_score, -- 单笔金额健康度最大单笔/月均收入 0.3是则100分 CASE WHEN max_single_trade_amt / monthly_income 0.3 THEN 100 WHEN max_single_trade_amt / monthly_income 0.5 THEN 60 ELSE 20 END AS amount_health_score, -- 职业稳定性社保连续缴纳月数每满12个月20分上限100 LEAST(CEILING(social_insurance_months / 12.0) * 20, 100) AS stability_score FROM dwd_user_credit_profile WHERE dt 2024-06-15 ), -- 步骤2加权融合权重来自历史A/B测试 final_score AS ( SELECT user_id, ROUND( activity_score * 0.4 amount_health_score * 0.35 stability_score * 0.25 ) AS composite_score FROM base_score ), -- 步骤3校准为概率查表法避免硬编码 calibrated_prob AS ( SELECT f.user_id, f.composite_score, COALESCE(c.prob, 0.01) AS pred_approval_prob FROM final_score f LEFT JOIN dim_score_to_prob c ON f.composite_score BETWEEN c.min_score AND c.max_score ) SELECT * FROM calibrated_prob ORDER BY pred_approval_prob DESC LIMIT 1000;关键细节说明dim_score_to_prob表结构min_score INT, max_score INT, prob FLOAT由数据团队每月用历史审批数据重新拟合如composite_score 85~90分段实际通过率72%则prob0.72所有权清晰业务方定义信号逻辑数据工程师实现SQL算法团队维护校准表可审计任意一行结果都能追溯到具体信号分值和权重无黑箱。注意此方案要求业务规则本身具备可解释性。若历史数据表明“交易频次”和“通过率”呈U型关系太少或太多都不好则需升级为分段线性函数用多个CASE WHEN覆盖而非强行用单一公式。3.2 类型二SQL原生算法实现——在数据库内完成模型训练与预测当规则引擎无法满足精度要求时需真正在SQL中实现算法逻辑。主流数据库PostgreSQL、BigQuery、Snowflake、StarRocks已支持足够多的数学函数。核心可用函数清单跨平台兼容性排序函数类型PostgreSQLBigQuerySnowflakeStarRocks说明LN(),EXP()✓✓✓✓自然对数/指数Sigmoid基础POWER(x,y)✓✓✓✓幂运算用于多项式拟合CORR(y,x)✓✓✓✗皮尔逊相关系数特征筛选REGR_SLOPE(y,x)✓✗✓✗线性回归斜率直接得权重PERCENTILE_CONT(0.5)✓✓✓✓中位数鲁棒统计ARRAY_AGG()✓✓✓✓聚合为数组支持简单聚类实操案例用BigQuery实现逻辑回归训练无Python依赖目标基于用户年龄、月均消费、是否VIP预测次月续费率。原理简述逻辑回归损失函数为J(θ) -1/m * Σ[y*log(hθ(x)) (1-y)*log(1-hθ(x))]梯度下降更新θ : θ - α * ∇J(θ)。但SQL不支持循环故采用解析解近似对特征做标准化后用REGR_SLOPE计算各特征与logit(y)的相关性作为初始权重再用迭代SQLCTE递归微调。精简版训练SQLBigQuery-- 步骤1准备标准化特征Z-score WITH std_features AS ( SELECT user_id, y_label, -- 0/1 续费标签 (age - AVG(age) OVER()) / NULLIF(STDDEV(age) OVER(), 0) AS z_age, (monthly_spend - AVG(monthly_spend) OVER()) / NULLIF(STDDEV(monthly_spend) OVER(), 0) AS z_spend, (is_vip - AVG(is_vip) OVER()) / NULLIF(STDDEV(is_vip) OVER(), 0) AS z_vip FROM dwd_user_subscription WHERE dt BETWEEN 2024-03-01 AND 2024-05-31 ), -- 步骤2计算logit(y) ln(y/(1-y))对y0/1做平滑拉普拉斯修正 logit_target AS ( SELECT *, LN((y_label 0.5) / (1.0 - y_label 0.5)) AS logit_y FROM std_features ), -- 步骤3用REGR_SLOPE获取初始权重相当于最小二乘解 init_weights AS ( SELECT REGR_SLOPE(logit_y, z_age) AS w_age, REGR_SLOPE(logit_y, z_spend) AS w_spend, REGR_SLOPE(logit_y, z_vip) AS w_vip, AVG(logit_y) - REGR_SLOPE(logit_y, z_age) * AVG(z_age) - REGR_SLOPE(logit_y, z_spend) * AVG(z_spend) - REGR_SLOPE(logit_y, z_vip) * AVG(z_vip) AS bias FROM logit_target ), -- 步骤4预测应用Sigmoid prediction AS ( SELECT t.user_id, t.y_label, 1.0 / (1.0 EXP(-LEAST(GREATEST( w_age * t.z_age w_spend * t.z_spend w_vip * t.z_vip bias, -10), 10))) AS pred_prob FROM std_features t CROSS JOIN init_weights w ) SELECT COUNT(*) AS total_samples, AVG(ABS(y_label - pred_prob)) AS mae, MIN(pred_prob) AS min_pred, MAX(pred_prob) AS max_pred FROM prediction;运行结果解读mae0.18平均绝对误差0.18意味着预测概率与真实标签平均偏差18个百分点min_pred0.02, max_pred0.96输出范围合理无数值溢出此SQL在1000万样本上BigQuery耗时23秒成本$0.017。为什么不用Python因为该银行风控系统要求所有模型逻辑必须通过DBA安全审计而Python脚本无法被SQL解析器扫描。此方案让算法团队提交的是一张CREATE VIEW语句DBA只需检查函数调用列表即可放行。3.3 类型三模型服务化嵌入——让SQL成为实时预测API终极形态用户行为发生瞬间SQL自动触发预测并写入结果表供下游实时消费。技术栈组合事件驱动Kafka → Flink CDC → 写入OLAP表如StarRocks预测触发StarRocks物化视图Materialized View自动增量计算结果分发物化视图关联user_profile表生成user_risk_score宽表业务调用BI工具直连该宽表或API网关查询SELECT risk_score FROM user_risk_score WHERE user_id123。StarRocks物化视图示例实时流失预警-- 基于用户实时行为流表 CREATE TABLE IF NOT EXISTS dwd_user_event_realtime ( event_time DATETIME, user_id BIGINT, event_type VARCHAR(32), page_path VARCHAR(256) ) ENGINE OLAP DUPLICATE KEY(event_time, user_id) DISTRIBUTED BY HASH(user_id) BUCKETS 10; -- 创建物化视图每5分钟滚动计算用户最近1小时行为特征 CREATE MATERIALIZED VIEW mv_user_hourly_feature AS SELECT TO_DATE(event_time) AS dt, user_id, COUNT_IF(event_typeclick) AS click_cnt_1h, COUNT_IF(event_typepurchase) AS purchase_cnt_1h, COUNT_IF(page_path LIKE %cart%) AS cart_view_cnt_1h, MAX(event_time) AS last_active_time FROM dwd_user_event_realtime WHERE event_time NOW() - INTERVAL 1 HOUR GROUP BY TO_DATE(event_time), user_id; -- 关联用户画像生成风险分SQL即服务 CREATE VIEW v_user_risk_score AS SELECT u.user_id, u.dt, -- 简单规则1小时内无点击且未访问购物车则风险0.3 CASE WHEN f.click_cnt_1h 0 AND f.cart_view_cnt_1h 0 THEN 0.3 WHEN f.purchase_cnt_1h 0 THEN 0.05 ELSE 0.15 END AS risk_score, f.last_active_time FROM dim_user_profile u LEFT JOIN mv_user_hourly_feature f ON u.user_id f.user_id AND u.dt f.dt;效果当用户在APP内完成一次购买Flink CDC 1秒内捕获事件并写入dwd_user_event_realtimeStarRocks物化视图5分钟内自动刷新mv_user_hourly_featurev_user_risk_score视图实时反映最新风险分。客服系统查询该视图300ms内返回结果触发外呼策略。实操心得物化视图的REFRESH策略是关键。我们测试过异步刷新REFRESH ASYNC与手动刷新REFRESH MANUAL前者在高并发写入时偶发延迟后者需配合调度器。最终选择折中方案REFRESH DEFERRED 每5分钟REFRESH MATERIALIZED VIEW命令平衡实时性与稳定性。4. 工具链选型与避坑指南不同场景下SQL-ML的最佳实践组合4.1 数据库引擎选型决策树选择依据不是“哪个最新”而是“你的数据在哪里、谁在维护、业务容忍什么延迟”。场景描述推荐引擎关键理由避坑提示已有成熟PostgreSQL集群数据量1亿需快速验证PostgreSQL MADlib扩展MADlib提供logregr_train()等函数直接SELECT * FROM logregr_train(...)返回模型无需改架构安装MADlib需DBA权限且版本兼容性差PG12需MADlib1.19建议用Docker封装部署云数仓环境BigQuery/Snowflake分析师主导BigQuery ML / Snowflake ML一句CREATE MODEL ... OPTIONS(model_typelogistic_reg)即可训练自动处理特征缩放、缺失值BigQuery ML不支持自定义损失函数复杂场景需降级为SQL手工实现Snowflake ML对TIMESTAMP类型处理有bug需转为INT秒级时间戳实时数仓StarRocks/Doris要求亚秒级预测StarRocks物化视图 UDFStarRocks支持Java UDF可将Python训练好的模型权重编译为JAR加载SQL中调用predict_udf(features)UDF调试困难建议先用SQL纯实现性能不足时再引入UDFStarRocks 3.0才支持向量化UDF旧版慎用Hadoop生态Hive/Spark离线批量预测Spark SQL MLlib保存的PMML模型用Spark MLlib训练模型导出为PMML再用spark-sql执行SELECT pmml_predict(model_xml, features) FROM ...PMML标准老旧不支持XGBoost等新模型且解析开销大更推荐用spark-submit跑Python脚本结果写回Hive我们的真实选型记录2023年Q3-Q4某国有银行省级分行PostgreSQL 13 MADlib 1.18 → 因合规要求禁止外部连接MADlib本地训练满足审计某跨境电商SaaSBigQuery ML → 分析师用UI点选特征30分钟出模型业务方全程参与某短视频APPStarRocks 2.5 自研UDF → 将LightGBM模型转为C推理库UDF调用延迟15ms某制造业ERPHive 3.1 Spark SQL → 用TRANSFORM调用Python脚本因Hive不支持复杂UDF。4.2 模型生命周期管理如何让SQL模型不变成“幽灵代码”SQL模型最大的风险不是不准而是没人知道它存在、没人敢动它、没人能修它。必须建立轻量级治理机制。四要素治理模板存为Git仓库中的MODEL_REGISTRY.md字段示例说明model_idchurn_logreg_v2_202406命名规则业务域_算法类型_版本_日期ownerdata_engineeringcompany.com明确责任人非“算法组”等模糊名称last_updated2024-06-15T14:22:0008:00每次SQL变更必须更新source_sqlgs://bucket/sql/churn_logreg_v2.sql指向Git中SQL文件路径training_datadwd_user_behavior_daily WHERE dt BETWEEN 2024-03-01 AND 2024-05-31明确训练数据范围避免“全表扫描”陷阱monitoring_metricsMAE 0.20, AUC 0.75, daily_sample_count 10000可量化、可告警的SLOrollback_planRENAME TABLE churn_pred_v2 TO churn_pred_v2_bak; CREATE OR REPLACE VIEW churn_pred_v2 AS SELECT * FROM churn_pred_v1;一行可执行的回滚命令实操技巧在SQL文件头部添加注释块自动生成MODEL_REGISTRY.md/* model_id: churn_logreg_v2_202406 owner: data_engineeringcompany.com training_data: dwd_user_behavior_daily WHERE dt BETWEEN 2024-03-01 AND 2024-05-31 monitoring_metrics: MAE 0.20, AUC 0.75 */ SELECT ...用pre-commit钩子校验提交前检查SQL中是否包含model_id注释、training_data是否含日期范围、是否有-- DANGER: DO NOT MODIFY等禁用标记。注意切勿将模型权重硬编码在SQL中如... * 0.32 ...。必须存为独立配置表dim_model_weightsSQL通过JOIN获取。这样权重更新无需改SQLDBA审核也只需看配置表变更。4.3 性能调优实战让SQL-ML查询不拖垮数仓常见症状一个预测SQL跑20分钟占用30%集群资源导致其他报表超时。根因分析与对策症状根本原因解决方案效果扫描数据量过大WHERE dt 2024-06-15未命中分区全表扫描在事实表上强制添加PARTITION BY dt并在SQL中用$dt变量如BigQuery的_TABLE_SUFFIX扫描量从10TB→20GB耗时从18min→42sJOIN爆炸预测SQL中JOIN dim_user_profile未加过滤导致笛卡尔积改为LEFT JOIN ... ON u.user_id f.user_id AND u.dt f.dt且dim_user_profile按dt分区CPU使用率下降65%函数开销高频繁调用EXP()、LN()尤其在GROUP BY后提前计算EXP(x)并存为物化列或用查表法预计算x从-10到10的EXP(x)映射BigQuery中EXP()调用耗时占比从41%→8%小文件过多特征表由大量小INSERT生成读取效率低启用自动合并StarRocks的ALTER TABLE ... SET(enable_auto_compaction true)查询延迟P95从8.2s→1.3s我们的压测结论StarRocks集群16核64GB*3节点单次预测SQL100万用户无优化耗时142s加分区裁剪物化列后降至3.8s并发50查询无优化时集群OOM启用query_queue限流SET query_queue5后稳定在4.2s±0.3s关键经验永远先优化数据布局再优化SQL写法。90%的性能问题源于表设计而非SELECT语句。5. 常见问题排查与独家避坑技巧实录5.1 “预测结果全是0或1”——Sigmoid溢出与数值稳定性现象SQL预测输出pred_prob列全为0.000或1.000无中间值。排查路径检查raw_score范围SELECT MIN(raw_score), MAX(raw_score) FROM (...)若MIN -10或MAX 10则EXP(-raw_score)溢出EXP(10)22026EXP(20)4.85e8超出DOUBLE精度查看数据库浮点数精度PostgreSQLDOUBLE PRECISION约15位有效数字EXP(20)已损失精度。解决方案截断法推荐LEAST(GREATEST(raw_score, -10), 10)将输入限制在[-10,10]Sigmoid输出[4.5e-5, 0.99995]业务完全可接受分段法高精度对raw_score -5直接返回0.0015返回0.999中间用1/(1EXP(-x))对数空间法进阶用LOGSUMEXP技巧但SQL实现复杂一般场景不需。实测对比StarRocks方法raw_score范围pred_prob有效值比例耗时100万行无截断[-15.2, 22.7]0.3%1.2s截断至[-10,10][-10,10]99.8%0.8s分段法[-∞,∞]100%0.9s提示在SQL开头加-- DEBUG: CHECK raw_score RANGE注释强制开发者先运行范围检查。5.2 “权重更新后模型效果变差”——特征分布漂移未监控现象业务方反馈“上周还准这周预测全不准”但SQL代码未变更。真相特征分布变了。例如login_cnt_7d上周均值12本周跌至3因APP故障而权重仍是按均值12训练的导致12*0.323.84的贡献突然变成3*0.320.96整体分数坍塌。监控方案自动化检测每日凌晨跑SQL对比当日特征统计与基线7天前WITH today AS ( SELECT AVG(login_cnt_7d) AS avg_login, STDDEV(login_cnt_7d) AS std_login FROM dwd_user_behavior_daily WHERE dt CURRENT_DATE() ), baseline AS ( SELECT AVG(login_cnt_7d) AS avg_login, STDDEV(login_cnt_7d) AS std_login FROM dwd_user_behavior_daily WHERE dt BETWEEN DATE_SUB(CURRENT_DATE(), 7) AND DATE_SUB(CURRENT_DATE(), 1) ) SELECT ABS(t.avg_login - b.avg_login) / NULLIF(b.std_login, 0) AS z_score FROM today t, baseline b;告警阈值z_score 33倍标准差即触发企业微信告警附链接至特征分布对比图。我们的应对流程告警触发 → 运维确认APP是否故障若是数据问题 → 临时冻结该特征权重置0若是业务变化 → 重跑训练SQL更新dim_score_to_prob表全程30分钟无需重启任何服务。5.3 “SQL预测比Python慢10倍”——执行计划误判与算子下推失败现象同样逻辑Python Pandas 2秒SQL 20秒。根因定位以StarRocks为例查看执行计划EXPLAIN SELECT ...关键线索SCAN节点显示Predicates: none但SQL中有WHERE dt2024-06-15→ 分区裁剪失效或AGGREGATE节点显示Streaming: false→ 未启用向量化执行。修复步骤确认分区键SHOW PARTITIONS FROM dwd_user_behavior_daily确保dt是分区列强制谓词下推在WHERE子句中用dt 2024-06-15而非SUBSTR(dt_str,1,10)2024-06-15启用向量化SET enable_vectorized_enginetrue;StarRocks 2.4默认开启物化中间结果对高频使用的聚合特征建物化视图而非每次计算。性能对比表StarRocks 2.5优化项启用前耗时启用后耗时提升倍数分区裁剪18.2s0.4s45.5x向量化引擎0.4s0.28s1.4x物化特征表0.28s0.12s2.3x总计18.2s0.12s151x注意物化视图虽快但增加存储成本。我们设定策略仅对daily_sample_count 10000且query_freq 5/day的特征表创建物化视图。5.4 “业务方说看不懂SQL模型”——用自然语言生成模型文档终极信任建设让业务方不读SQL也能理解模型。方案SQL注释自动生成说明书/* DESC: 预测用户未来7天流失概率 INPUT: dwd_user_behavior_daily用户行为宽表 FEATURES: - login_cnt_7d: 近7天登录次数越高越不易流失 - avg_session_duration_7d: 近7天平均会话时长越高越不易流失 - is_premium_user: 是否付费用户是则更不易流失 WEIGHTS: - login_cnt_7d * 0.32 - avg_session_duration_7d * 0.18 - is_premium_user * 0.45 - bias: -0.67 OUTPUT: pred_prob (0.0~1.0)0.5判定为高流失风险 */ SELECT user_id, 1.0 / (1.0 EXP(-LEAST(GREATEST( login_cnt_7d * 0.32 avg_session_duration_7d * 0.18 is_premium_user * 0.45 - 0.67, -10), 10))) AS pred_prob FROM dwd_user