选择最新关系为真的所有地方?

时间:2017-09-20 14:05:16

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

我有一个系统,可以批准许多项目,并且可以在每个项目上看到批准的历史记录。一个例子是用户的里程碑,可以批准或拒绝。我的数据库中的表格如下:

+-----------------+------------------+
| 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

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

3 个答案:

答案 0 :(得分:1)

由于您使用Laravel对此进行了标记,并且您已经设置了可变形关系,即使您正在编写纯SQL,我也会向您展示Laravel(Eloquent)方式。 < / p>

如果我正确理解你,使用Laravel's Polymorphic Relationships我认为这应该有效:

<强> Approval.php

BinaryFormatter

<强> Milestone.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

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);
    }
}

这是DB结构的样子(应该与你的相同):

milestones enter image description here

答案 1 :(得分: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 :(得分:0)

正如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);
}