You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 04:21:10