Выберите все, где последнее отношение верно?


У меня есть система, в которой можно утверждать многие элементы, и историю утверждений можно увидеть по каждому элементу. Примером может служить контрольная точка пользователя, которая может быть одобрена или отклонена. Таблицы в моей книге базы данных выглядят следующим образом:

+-----------------+------------------+
| 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, который проверяет, есть ли у milestones одобрение, которое принято. Однако это также позволяет получить результаты, которые были приняты, а затем отклонены:

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

Можно ли охватить все этапы, на которых принято последнее утверждение? Или это должно быть сделано на уровне приложения вместо этого?

Author: M Khalid Junaid, 2017-09-20

3 answers

Это должно выполнить свою работу

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

 1
Author: M Khalid Junaid, 2017-09-20 14:52:36

Поскольку вы пометили это с помощью Laravel и настроили изменяемые отношения, даже если вы пишете простой SQL, я собираюсь показать вам способ Laravel (Красноречивый).

Если я правильно вас понимаю, используя Полиморфные отношения Ларавеля, я думаю, что это должно сработать:

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

Milestone.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();

Вот как выглядит структура БД (должна быть такой же, как у вас):

milestones enter image description here

 1
Author: tptcat, 2017-09-20 14:53:42

Как указано в tptcat, этот вопрос помечен как вопрос Laravel. Поэтому, используя Ответ М Халида Джунаида , я преобразовал его в Красноречивый синтаксис (или, насколько мог) и получил следующее:

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);
}
 0
Author: Pet1330, 2017-09-21 21:14:37