Laravel 查询构建器 - 从另一个表中选择前 1 行

Posted

技术标签:

【中文标题】Laravel 查询构建器 - 从另一个表中选择前 1 行【英文标题】:Laravel Query builder - select top 1 row from another table 【发布时间】:2020-01-19 02:38:19 【问题描述】:

我有这个用于 mysql 的 SQL 查询,它运行良好。但是我需要使用查询生成器重写它,并且需要完全避免DB::raw(),因为开发数据库与生产数据库不同。我知道远非理想,但不幸的是它就是这样。

SELECT athletes.*, 
     (
         SELECT performance
         FROM performances
         WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
         ORDER BY performance DESC
         LIMIT 0,1
     ) AS personal_best
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100

我正在努力如何重写personal_best 部分。我有运动员的成绩表,我只需要选择每个运动员的最佳成绩作为他的个人最好成绩。

我试图搜索答案,但我找到的所有答案都包括原始添加原始 SQL。

任何想法或提示将不胜感激。

提前谢谢你!

所以我接受了我可能不得不为此使用 Eloquent,但仍然无法取得进展。这是我的代码:

class Athlete extends Model

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'athletes';

    /**
     * The primary key associated with the table.
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;

    /**
     * Get the performances for the Athelete post.
     *
     * @return HasMany
     */
    public function performances()
    
        return $this->hasMany('App\EloquentModels\Performance', 'athlete_id');
    


class Performance extends Model

    /**
     * The table associated with the model.
     *
     * @var string
     */
    protected $table = 'performances';

    /**
     * The primary key associated with the table.
     *
     * @var string
     */
    protected $primaryKey = 'id';

    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;

【问题讨论】:

【参考方案1】:

如果您使用的是原始 SQL,只需使用 GROUP BY 为每个运动员执行 MAX 即可。

SELECT athletes.*, MAX(performance) AS personal_best
FROM athletes
INNER JOIN performances ON athletes.id = performances.athlete_id AND performances.event_id = 1
GROUP BY athletes.id
ORDER BY personal_best DESC
LIMIT 0, 100

Laravel 查询构建器:

DB::table('athletes')
    ->join('performances', 'athletes.id', '=', 'performances.athlete_id')
    ->where('performances.event_id', '=', 1)
    ->groupBy('athletes.id')
    ->orderBy('personal_best', 'desc')
    ->select('athletes.*',DB::raw('MAX(performance) AS personal_best')
    ->limit(100);

Doc 说我们可以使用max(personal_best),但不确定如何将它与 group by 一起使用。

恐怕您无法避免在查询生成器中使用DB::raw,但您可以使用eloquent model,正如Shaielndra Gupta 所回答的那样。

为此,您可以创建模型和关系。

1. Create Model:
php artisan make:model Athelete
php artisan make:model Performance

2. Create relationship between Athelete and Perforamnce.

Update Athelete.php 

    /**
     * Get the performances for the Athelete post.
     */
    public function performances()
    
        return $this->hasMany('App\Performance');
    

3. Get data(didn't verify by myself)
$data = Athelete::with('performances',function ($query) use ($eventId)
    $query->max('performance')
    $query->where('event_id',$eventId)
    $query->orderBy('performance');
)->get();

参考:

Laravel Model Laravel Relationship

【讨论】:

我知道如何用 sql 解决它,但我需要将 sql 重写为 laravel 查询生成器。如果我没有提供足够的描述,我很抱歉。 所以我转换为 eloquent 但仍然无法获得结果现在我收到错误 mb_strpos() expects parameter 1 to be string, object given 任何想法这可能是小问题 对不起,伙计,现在在办公室,稍后可以查看。 @MarekBarta,能否请您出示您的代码。运动员模特。【参考方案2】:

你可以像下面这样使用。

$sql1 = "(
         SELECT performance
         FROM performances
         WHERE athletes.id = performances.athlete_id AND performances.event_id = 1
         ORDER BY performance DESC
         LIMIT 0,1
     ) AS personal_best";

$sql2 = "SELECT athletes.*,$sql1          
FROM athletes
ORDER BY personal_best DESC
Limit 0, 100";

$result = DB::select($sql2);

【讨论】:

这个对我有用的解决方案让我知道它是否适合你。 我需要避免使用 Raw sql,因为本地数据库与生产数据库不同。这就是为什么我想使用 laravel 查询构建,所以我不必两次编写相同的查询。一次本地,一次生产 如果本地数据库不同,那么您必须通过模型访问表。因此,您可以将表名定义为常量并在 RAW SQL 中使用。【参考方案3】:

database.php 创建一个新连接,例如 mysql_dev 用于开发参数。

DB::connection('mysql_dev')->table('athletes')
    ->leftJoin('performances','athletes.id','performances.athlete_id')
    ->where('performances.event_id',1)
    ->groupBy('athletes.id')
    ->orderByDesc('personal_best')
    ->select('athletes.*',DB::raw('MAX(performances.performance) AS personal_best')
    ->paginate(100);

尝试这样不生,

DB::connection('mysql_dev')->table('athletes')
    ->leftJoin('performances','athletes.id','performances.athlete_id')
    ->where('performances.event_id',1)
    ->groupBy('athletes.id')
    ->orderByDesc('performances.performance')
    ->select('athletes.*','performances.performance'
    ->paginate(100);

【讨论】:

@Marek Barta,如果您需要查询生成器,可以这样做。也许你会因为数据库结构而出错,但你可以很容易地修复它。 是的,这就是我正在寻找的更多内容,但是有没有办法避免 DB::raw 部分?我现在处于一个愚蠢的境地。数据库服务器可能会随着时间而改变,所以如果发生这种情况,我想避免重写所有的 quieres 我现在得到 0 个结果。当然这可能是我的错,并试图确保我没有因为数据库结构而犯错,但应该没问题 如果你说表演是错误的,我可以说建造者是错误的。但是 0 结果是相当出乎意料的。看起来 event_id 1 没有性能,但您说原始查询确实有效。您可以从 ->paginate(100) 更改为 ->toSql() 以检查构建器执行真正的 SQL 吗? 好吧,这是有道理的。但即使这样行​​得通,它也无济于事,因为如果 event_id 1 没有性能,我仍然需要该行,但实际性能将是空的,而不是实际性能。这是可能的还是变得越来越复杂?【参考方案4】:

你可以像这样使用 Eloquent ORM

$data = Athelete::with('performances',function ($query) use ($eventId)
    $query->max('performance')
    $query->where('event_id',$eventId)
    $query->orderBy('performance');
)->get()

【讨论】:

请问我应该如何制作“Athelete”,如果这是一件微不足道的事情,我很抱歉 Athlete 是你的模型代表运动员,在运动员内部,模型定义了与性能表的关系更多你可以看到laravel.com/docs/6.x/eloquent 好的,我知道了。但是你知道如何在不使用 Eloquent ORM 的情况下仅使用 laravel 查询构建器来做到这一点吗? 兄弟,我是个会说话的人?

以上是关于Laravel 查询构建器 - 从另一个表中选择前 1 行的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 查询构建器与 where 左外连接

从另一个表上的 Where 表中选择计数

Laravel - 使用 Eloquent 查询构建器在选择中添加自定义列

如何使用 Laravel 的流畅查询构建器选择计数?

Laravel 的查询构建器 JSON 选择器 `field->key` 导致语法错误

从另一个查询插入记录