【问题标题】:Getting unique values GROUPBY, DISTINCT, UNIQUE - what to choose? PHP Laravel获取唯一值 GROUPBY、DISTINCT、UNIQUE - 选择什么? PHP Laravel
【发布时间】:2020-07-24 12:21:39
【问题描述】:

我要做的是从数据库中获取前 5 名球员的分数。我将玩家数据保存在 3 个表中,所以比较复杂。

现在我正在获得分数,但无法让每个用户都独一无二。

在前 5 名中,一名球员只能出现一次(即使他自己拥有前 5 名)

当前查询:

DB::table('gameplays', 'gameplay')
            ->join('users', 'gameplay.user_id', '=', 'users.id')
            ->join('playerdatas', 'gameplay.user_id', '=', 'playerdatas.user_id')
            ->select([ DB::raw('DISTINCT(gameplay.user_id)'), 'users.name', 'gameplay.score', 'gameplay.duration', 'gameplay.created_at', 'playerdatas.appearance'])
            ->where('gameplay.created_at', '>', Carbon::now()->subDays($type))
            ->where('gameplay.game_id', '=', $game_id)
            ->orderBy('gameplay.score', 'DESC')
            ->limit(5)
            ->get();

所以DISTINCT 不适合我,每个用户的所有分数都不是唯一的。

也尝试过:

->distinct('gameplay.user_id')
->groupBy('gameplay.user_id')
->unique('gameplay.user_id')

它在收集时与->unique('gameplay.user_id') 一起工作,但我想在查询中而不是在收集中使用它,因为我目前将结果限制为 5,所以我会错过一些记录。

Tables:

games
|id|name |
|1 |game1|

users
|id|name|
| 1|test|
| 2|elo |
| 3| me |
| 4| 3f2|
| 5| 123|

playerdatas
|id|user_id|game_id|appereance|
| 1| 1     |     1 |  null    |
| 1| 2     |     1 |  null    |
| 1| 3     |     1 |  null    |
| 1| 4     |     1 |  null    |
| 1| 5     |     1 |  null    |

gameplays
|id|game_id|score|created_at|user_id|
| 1| 1     | 100 |24.07.2020| 1     |
| 1| 1     | 700 |24.07.2020| 1     |
| 1| 1     | 400 |24.07.2020| 2     |
| 1| 1     | 300 |24.07.2020| 3     |
| 1| 1     | 200 |24.07.2020| 1     |
| 1| 1     | 400 |24.07.2020| 2     |
| 1| 1     | 500 |24.07.2020| 3     |
| 1| 1     | 100 |24.07.2020| 2     |
| 1| 1     | 200 |24.07.2020| 3     |
| 1| 1     | 100 |24.07.2020| 1     |
| 1| 1     | 900 |24.07.2020| 2     |
| 1| 1     | 870 |24.07.2020| 3     |

【问题讨论】:

  • 如果能在帖子中添加各种表格就好了?
  • 还有你的mysql版本。
  • @Aashishgaba 用表格更新了帖子
  • @P.Salmon mysql:5.7
  • 如果是我,我会从原始 sql 开始。考虑到这一点,请参阅meta.stackoverflow.com/questions/333952/…

标签: php mysql database laravel eloquent


【解决方案1】:

用户模型

public function playerData(){
   return hasOne(App\PlayerData::class, 'user_id', 'id');
}

游戏模式

public function game(){
   return belongsTo(App\Game::class, 'game_id', 'id');
}

public function user(){
   return belongsTo(App\User::class, 'user_id', 'id');
}

使用给定的game_id 获取游戏的所有游戏玩法,并且满足created_at 条件(您在帖子中提到)。

$gamePlays = GamePlay::with(['game', 'user.playerData'])
   ->where('game_id', $game_id)
   ->where('created_at', '>', Carbon::now()->subDays($type))
   ->get();

现在我们将使用集合中可用的方法。 (orderByDescgroupBy)。

https://laravel.com/docs/7.x/collections#available-methods

将包含所需的所有用户详细信息以及他们的最高分。

$users = collect([]);

foreach($gamePlays->groupBy('user_id') as $user_id => $gp_c){

    // $gp_c are the gameplays of that user from the above list that we obtained.

    // From those gameplays get the one with the highest score.

    $gp = $gp_c->sortByDesc('score')->first();
    
    // add the necessary details.
    $users.push([
         'id' => $user_id,
         'name' => $gp->user->name,
         'score' => $gp->score,
         'duration' => $gp->duration,
         'created_at' => $gp->created_at,
         'appearance' => $gp->user->playerData->appearance,
    ]);
}

选择得分最高的前 5 名(如果用户数

$top_users = $users->sortByDesc('score')->take( (sizeof($users) > 5) ? 5 : sizeof($users)));

【讨论】:

  • 感谢您的回答,我认为这可能会很好。我没有那样做,因为我认为这会比原始 sql 效率低,但也许有一天我会尝试一下并检查我将如何处理真实数据
  • 是的,我同意你的观点,原始 sql 会比这更有效。但是 Eloquent ORM 有它自己的美 :) 即使在我只有一个 DB 查询的解决方案中,其余的都是收集方法。谢谢:)
【解决方案2】:

您可以在subquery 中使用group by 根据总分对用户进行分组。然后将其加入playerdata 以从该表中获取信息。这是一个示例(您可以根据需要添加where 条件):

一、子查询位:

$scores = DB::table('gameplays')
            ->select(DB::raw('MAX(score) as max_score'), //edited after your comment
                   DB::raw('gameplays.user_id',  //edited aft. your comment
                   DB::raw('users.name as user_name')))
          ->join('users', 'users.id', 'gameplays.user_id')
          ->groupBy('gameplays.user_id')
          ->orderBy('max_score', 'desc') //edited aft. comment
          ->limit(5); //limit to 5 players

现在获取玩家数据信息并使用joinSub加入子查询($scores):

$playerData = DB::table('playerdatas')
              ->joinSub($scores, 'max_scores', function($join){ //edited to max_scores
                   $join->on('max_scores.user_id', 'playerdatas.user_id');
              })
              ->orderBy('max_score', 'desc') //edited aft comment
              ->get();

原始查询中的相同是:

select * from `playerdatas` 
inner join 
 (select MAX(score) as max_score, 
 gameplays.user_id from `gameplays` 
 inner join `users` on `users`.`id` = `gameplays`.`user_id` 
 group by `gameplays`.`user_id` 
 order by `max_score` desc   
 limit 5) as `max_scores` on `max_scores`.`user_id` = `playerdatas`.`user_id` 
order by `max_score` desc

【讨论】:

  • 感谢您的回答,它实际上并没有做我想做的事,但那已经很接近了。我不想有分数的总和,只是每个玩家的 MAX 分数并在此基础上创建排名。为了使最终查询正常工作,我需要将 mysql strict_mode 设置为 false,因为 sql_mode=only_full_group_by
  • 哦,我误解了你的逻辑。但是,我认为相同的查询应该可以在不改变完整组设置的情况下工作;您只需将sum 替换为max..
  • 哦,等等,我的选择是users.id as user_id,,它应该是gamplays.user_id,你也不需要别名,我的错。如果您将其更改为gamplays.user_id,则无需更改完整的分组设置。
【解决方案3】:

写答案让你知道我最终做了什么来解决这个问题。

我在 laravel 中使用 GROUP BY 创建了原始 SQL,但这还不是全部。问题是您现在使用GROUP BY(MySQL 的最新版本),您必须将所有选定的列添加到GROUP BY 或通过函数之一聚合它们(MINMAXSUM 等.)。如果你不这样做,你会得到错误:

this is incompatible with sql_mode=only_full_group_by

来源:How can I solve incompatible with sql_mode=only_full_group_by in laravel eloquent?

所以为了绕过这个错误并让GROUP BY 在这种情况下工作,我不得不将 laravel database.php mysql 模式切换到非严格模式

 'mysql' => [
            'strict' => false,

这不是理想的解决方案,目前只是暂时的,但它可以按预期工作。

【讨论】:

    猜你喜欢
    • 2022-10-24
    • 1970-01-01
    • 2023-04-05
    • 2016-08-12
    • 1970-01-01
    • 2016-01-17
    • 1970-01-01
    • 2017-10-10
    • 2023-03-30
    相关资源
    最近更新 更多