Laravel中使用DB::raw通过SQL实现用户last_login的Min-max归一化
实现SQL内的Min-Max归一化并筛选指定用户集合
嘿,刚好能帮你搞定这个需求!你想要筛选出已登录且未禁用登录的用户,同时直接在SQL里完成last_login的Min-Max归一化,不用拉回PHP再计算对吧?下面分两种场景给你解决方案:
1. 纯SQL语句实现
先明确Min-Max归一化的核心公式:normalized_value = (current_value - min_value) / (max_value - min_value)
这里要注意处理max_value = min_value的情况,避免除以0报错,我们可以用CASE语句兜底。
窗口函数写法(高效简洁,适用于MySQL 8.0+、PostgreSQL等)
SELECT id, name, logged_in, login_disabled, last_login, -- 处理除以0的边界情况 CASE WHEN (max_last - min_last) = 0 THEN 0.0 ELSE (last_login - min_last) / (max_last - min_last) END AS normalized_last_login FROM ( -- 先筛选目标用户,同时用窗口函数获取该集合的last_login最值 SELECT u.*, MIN(u.last_login) OVER () AS min_last, MAX(u.last_login) OVER () AS max_last FROM users u WHERE u.logged_in = 1 AND u.login_disabled = 0 ) AS filtered_users;
窗口函数OVER ()会给筛选后的每一行都返回整个结果集的最小和最大last_login值,不用额外写多次子查询。
兼容老版本数据库的子查询写法
如果你的数据库不支持窗口函数(比如MySQL 5.x),可以用子查询单独获取最值:
SELECT u.id, u.name, u.logged_in, u.login_disabled, u.last_login, CASE WHEN (stats.max_val - stats.min_val) = 0 THEN 0.0 ELSE (u.last_login - stats.min_val) / (stats.max_val - stats.min_val) END AS normalized_last_login FROM users u CROSS JOIN ( -- 单独计算目标用户的last_login最值 SELECT MIN(last_login) AS min_val, MAX(last_login) AS max_val FROM users WHERE logged_in = 1 AND login_disabled = 0 ) AS stats WHERE u.logged_in = 1 AND u.login_disabled = 0;
2. 结合Laravel Eloquent Scope的实现
既然你提到已经有Scope的思路,我们可以把这个逻辑封装到User模型的查询作用域里,让代码更复用:
在app/Models/User.php里添加Scope:
public function scopeActiveLoggedIn($query) { return $query->where('logged_in', 1) ->where('login_disabled', 0) // 先获取目标用户集合的last_login最值 ->selectRaw('*, MIN(last_login) OVER () AS min_last, MAX(last_login) OVER () AS max_last') // 计算归一化值并作为新字段返回 ->selectRaw('CASE WHEN (max_last - min_last) = 0 THEN 0.0 ELSE (last_login - min_last) / (max_last - min_last) END AS normalized_last_login'); }
之后在业务代码里就能直接调用这个Scope:
// 获取已登录且未禁用的用户,带归一化后的last_login字段 $users = User::activeLoggedIn()->get(); // 可以直接访问归一化字段 foreach ($users as $user) { echo $user->name . ' 的归一化登录时间:' . $user->normalized_last_login; }
小提示
- 如果需要将归一化值缩放到特定区间(比如0-100),只需要修改公式:
((last_login - min_last)/(max_last - min_last)) * 100 - 窗口函数的写法性能更优,尤其是数据量较大的时候,因为它只需要扫描一次表
内容的提问来源于stack exchange,提问作者dbatten
相关产品推荐
相关产品推荐

