【问题标题】:Select all where latest relation is true?选择所有最新关系为真的地方?
【发布时间】:2017-09-20 14:05:16
【问题描述】:

我有一个系统,其中可以批准许多项目,并且可以查看每个项目的批准历史记录。一个例子是用户的里程碑,它可以被批准或拒绝。我的数据库书中的表是这样的:

+-----------------+------------------+
| Approvals                          |
+-----------------+------------------+
| Field           | Type             |
+-----------------+------------------+
| id              | int(10) unsigned |
| approved        | tinyint(1)       |
| reason          | text             |
| approvable_id   | varchar(191)     |
| approvable_type | varchar(191)     |
| created_at      | timestamp        |
| updated_at      | timestamp        |
+-----------------+------------------+

+----------------------+---------------------------------------+
| Milestones                                                   |
+----------------------+---------------------------------------+
| Field                | Type                                  |
+----------------------+---------------------------------------+
| id                   | int(10) unsigned                      |
| name                 | varchar(191)                          |
| created_at           | timestamp                             |
| updated_at           | timestamp                             |
+----------------------+---------------------------------------+

我想要做的是获取接受最后批准的所有里程碑。例如,一个里程碑可能先前已被接受,但后来被拒绝,在这种情况下,此里程碑不应出现在接受的查询中,因为它已被拒绝。

如何仅获取接受最新批准的里程碑?到目前为止,我尝试过的方法是进行exist 子查询,以检查里程碑是否获得批准,即被接受。但是,这也会获取已被接受但后来被拒绝的结果:

SELECT * FROM `milestones` 
WHERE EXISTS (
        SELECT * FROM `approvals` 
        WHERE `milestones`.`id` = `approvals`.`approvable_id` 
        AND `approvals`.`approvable_type` = 'App\Models\Milestone'
            AND `created_at` = (
                SELECT MAX(created_at) FROM `approvals` AS `sub` 
                WHERE sub.approvable_id = approvals.approvable_id 
                    AND sub.`approvable_type` = `approvals`.`approvable_type` 
                    AND `sub`.`id` IN (
                        SELECT `id` FROM `approvals` 
                        WHERE `approved` = 1 
                    )
            )
    ) AND `milestones`.`deleted_at` IS NULL

是否可以抓取所有接受最新批准的里程碑?或者这是否必须在应用程序级别完成?

【问题讨论】:

    标签: php mysql sql laravel greatest-n-per-group


    【解决方案1】:

    这应该可以完成工作

    select m.*,a.*
    from milestones m
    join approvals a on m.id = a.approvable_id
    join (
        select approvable_id, max(created_at) created_at
        from approvals
        group by approvable_id
    ) aa on a.approvable_id = aa.approvable_id 
            and a.created_at = aa.created_at    
    where a.approved = 1 /* other filters in query */
    

    首先它将使用批准表中的最新记录加入每个里程碑,然后在 where 子句中过滤掉最新批准,批准 = 1

    【讨论】:

    • 太棒了!这就是我要找的!谢谢!
    【解决方案2】:

    既然你用 Laravel 标记了它并且你已经设置了可变形的关系,即使你正在编写简单的 SQL,我也会向你展示 Laravel(Eloquent)的方式。

    如果我对您的理解正确,请使用Laravel's Polymorphic Relationships 我认为这应该可行:

    Approval.php

    <?php
    
    namespace App\Models;
    
    use App\Models\Milestone;
    use Illuminate\Database\Eloquent\Model;
    
    class Approval extends Model
    {
        public function approvable()
        {
            return $this->morphTo();
        }
    }
    

    里程碑.php

    <?php
    
    namespace App\Models;
    
    use App\Models\Approval;
    use Illuminate\Database\Eloquent\Model;
    
    class Milestone extends Model
    {
        /**
         * All approvals
         *
         * @return MorphMany
         */
        public function approvals()
        {
            return $this->morphMany(Approval::class, 'approvable');
        }
    
        /**
         * The most recent "approved" approval
         *
         * @return MorphMany
         */
        public function lastestApproval()
        {
            return $this->morphMany(Approval::class, 'approvable')->where('approved', 1)->latest()->limit(1);
        }
    }
    

    然后获得所有里程碑并获得他们最近的批准:

    $milestones = App\Models\Milestone::with('lastestApproval')->get();
    

    这是数据库结构的样子(应该和你的一样):

    【讨论】:

    • 我正在使用 Laravel(因此是标签),但我将其转换为纯 SQL 以尝试使其正常工作,一旦我有一个可行的解决方案,我会将其转换回 Laravel 的 Eloquent 方式。感谢您花时间回答,但不幸的是,此解决方案不起作用,因为它只会选择已被接受的里程碑,即使它们后来被拒绝。
    • 我明白了。我误解了,以为你只关心那些被批准的。您在这方面还有其他可批准的课程吗?还是只是里程碑?
    • 目前,我们只批准里程碑,但我们已将其制成多态,以便以后可以扩展到其他模型。
    【解决方案3】:

    正如tptcat 指出的那样,这个问题被标记为 Laravel 问题。因此,使用M Khalid Junaid's 答案,我将其转换为 Eloquent 语法(或尽可能多地)并得到以下结果:

    public function scopeApproved($query, $accepted=true)
    {
        return $query->select('milestones.*', 'approvals.*')
            ->join('approvals', 'milestones.id', '=', 'approvals.approvable_id')
            ->join(\DB::raw(
                '(SELECT approvable_id, MAX(created_at) created_at 
                FROM approvals 
                GROUP BY approvable_id) aa' ), function($join)
            { 
                $join->on('approvals.approvable_id', '=', 'aa.approvable_id')
                     ->on('approvals.created_at', '=', 'aa.created_at');
            })->where('approvals.approved', $accepted);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多