MySQL 抓取数据组中的最后一行,但特定于列值

huangapple go评论62阅读模式
英文:

MySQL grab last row of a group of data but specific to a column value

问题

我们正在使用Laravel开发一个系统,这意味着我们正在使用Laravel ORM。我们有一个类似于这样的表格:

table: approvals
+----+-------------------------+--------+----------+----------+------+----------+
| id |        app_model        | app_id | group_id | revision | year |  status  |
+----+-------------------------+--------+----------+----------+------+----------+
|  1 | App\Models\AApplication |      4 |      500 | (NULL)   | 2021 | APPROVED |
|  2 | App\Models\AApplication |     95 |      501 | (NULL)   | 2022 | APPROVED |
|  3 | App.Models\BApplication |     22 |       90 | 1        | 2019 | APPROVED |
|  4 | App.Models\BApplication |     37 |       90 | 2        | 2020 | APPROVED |
|  5 | App\Models\AApplication |    125 |      501 | 1        | 2023 | APPROVED |
|  6 | App.Models\BApplication |     89 |       90 | 3        | 2024 | DRAFT    |
+----+-------------------------+--------+----------+----------+------+----------+

我们正在使用Laravel的morphTo()关系来获取关联表格数据:

public function app() {
    return $this->morphTo(__FUNCTION__, 'app_model', 'app_id');
}

我尝试呈现一些数据,以便提供有关此表格中数据维度的一些上下文。我们在这个approvals表中保留了不同应用表的批准。一个机构可以根据每个申请获取修订批准多次。批准可以最初是'草稿',后来变成'批准'。

👉 我们需要获取组(group_id)中状态为'批准'的最后一次修订行。

批准UI表格
+-------+----------+--------+
| GROUP | REVISION | ACTION |
+-------+----------+--------+
|   500 |        - | [查看] |
|   501 |        1 | [查看] |
|    90 |     👁 2 | [查看] |
+-------+----------+--------+

我们已经在Laravel ORM中制定了以下MySQL查询来实现这一目标:

// $userId = 获取当前用户的ID;

$query = DB::table('approvals')
    ->with(['app'])
    ->whereNotIn('id', function ($query) {
        $query->from('approvals as t1')
            ->crossJoin('approvals as t2')
            ->whereColumn('t1.id', '<', 't2.id')
            ->whereColumn('t1.group_id', '=', 't2.group_id')
            ->whereColumn('t1.app_model', '=', 't2.app_model')
            ->select('t1.id');
    });

// 仅显示属于该特定用户的批准。
$query = $query->whereRelation('app', 'created_by', '=', $userId);

$query = $query->orderBy('approvals.id', 'DESC');

$query = $query->where('approvals.status', 'APPROVED'); // &lt;-------- 🚩 此处存在问题

return $query;

如果我们从查询中注释掉'APPROVED'条件,我们可以获取每个组的最后一行。问题是,组ID 90 的最后一行不是'批准',但它会出现在表格中,这不是我们想要的。

👉 我们需要组的最后一行,但如果已批准,则应显示前一个最后一行。

在MySQL中,我们如何实现这一点呢?

英文:

We're developing a system using Laravel, which means we are using Laravel ORM. We have a table similar to this:

table: approvals
+----+-------------------------+--------+----------+----------+------+----------+
| id |        app_model        | app_id | group_id | revision | year |  status  |
+----+-------------------------+--------+----------+----------+------+----------+
|  1 | App\Models\AApplication |      4 |      500 | (NULL)   | 2021 | APPROVED |
|  2 | App\Models\AApplication |     95 |      501 | (NULL)   | 2022 | APPROVED |
|  3 | App\Models\BApplication |     22 |       90 | 1        | 2019 | APPROVED |
|  4 | App\Models\BApplication |     37 |       90 | 2        | 2020 | APPROVED |
|  5 | App\Models\AApplication |    125 |      501 | 1        | 2023 | APPROVED |
|  6 | App\Models\BApplication |     89 |       90 | 3        | 2024 | DRAFT    |
+----+-------------------------+--------+----------+----------+------+----------+

We're grabbing the relational table data using Laravel's morphTo() relation:

public function app() {
    return $this-&gt;morphTo(__FUNCTION__, &#39;app_model&#39;, &#39;app_id&#39;);
}

I tried to present some data so that I can provide some context about the dimensions of the data in this table. We're keeping approvals of different application tables in this approvals table. A single institute can get revised approval multiple times based on each of the application. The approval can be 'DRAFT' initially, later be 'APPROVED'.

👉 We need to grab the last revised row of the group (group_id) where the status is 'APPROVED'.

Approvals UI Grid
+-------+----------+--------+
| GROUP | REVISION | ACTION |
+-------+----------+--------+
|   500 |        - | [view] |
|   501 |        1 | [view] |
|    90 |     &#128073; 2 | [view] |
+-------+----------+--------+

We've made the following MySQL query in Laravel ORM to achieve that:

// $userId = get current user id;

$query = DB::table(&#39;approvals&#39;)
    -&gt;with([&#39;app&#39;])
    -&gt;whereNotIn(&#39;id&#39;, function ($query) {
        $query-&gt;from(&#39;approvals as t1&#39;)
            -&gt;crossJoin(&#39;approvals as t2&#39;)
            -&gt;whereColumn(&#39;t1.id&#39;, &#39;&lt;&#39;, &#39;t2.id&#39;)
            -&gt;whereColumn(&#39;t1.group_id&#39;, &#39;=&#39;, &#39;t2.group_id&#39;)
            -&gt;whereColumn(&#39;t1.app_model&#39;, &#39;=&#39;, &#39;t2.app_model&#39;)
            -&gt;select(&#39;t1.id&#39;);
    });

// Only show approvals of _that_ particular user.
$query = $query-&gt;whereRelation(&#39;app&#39;, &#39;created_by&#39;, &#39;=&#39;, $userId);

$query = $query-&gt;orderBy(&#39;approvals.id&#39;, &#39;DESC&#39;);

$query = $query-&gt;where(&#39;approvals.status&#39;, &#39;APPROVED&#39;); // &lt;-------- &#128997; ISSUE IS HERE

return $query;

If we comment out the 'APPROVED' condition from the query, we can get the last row of each group. Issue is that, the last row of group_id 90 is not 'APPROVED', but that comes into the grid which is not what we want.

👉 We need the last row of the group, but if approved, otherwise the previous-last row should come.

How can we achive this in MySQL?

答案1

得分: 1

要获取每个group_id的最新的APPROVED行。对于每个group_id,您要找到具有最大id且状态为APPROVED的行。

以下是我翻译好的代码部分:

$query = DB::table('approvals')
    ->with(['app'])
    ->whereNotIn('id', function ($query) {
        $query->from('approvals as t1')
            ->join('approvals as t2', function ($join) {
                $join->on('t1.group_id', '=', 't2.group_id')
                     ->on('t1.app_model', '=', 't2.app_model')
                     ->on('t1.id', '<', 't2.id')
                     ->where('t2.status', 'APPROVED');
            })
            ->select('t1.id');
    });

// 只显示特定用户的批准。
$query = $query->whereRelation('app', 'created_by', '=', $userId);

$query = $query->orderBy('approvals.id', 'DESC');

$query = $query->where('approvals.status', 'APPROVED');

return $query;

希望这有助于您的需求。

英文:

You want to get the latest APPROVED row per group_id. For each group_id, you want to find the row with the maximum id where the status is APPROVED.

If that is the case, heres what i would do:

$query = DB::table(&#39;approvals&#39;)
    -&gt;with([&#39;app&#39;])
    -&gt;whereNotIn(&#39;id&#39;, function ($query) {
        $query-&gt;from(&#39;approvals as t1&#39;)
            -&gt;join(&#39;approvals as t2&#39;, function ($join) {
                $join-&gt;on(&#39;t1.group_id&#39;, &#39;=&#39;, &#39;t2.group_id&#39;)
                     -&gt;on(&#39;t1.app_model&#39;, &#39;=&#39;, &#39;t2.app_model&#39;)
                     -&gt;on(&#39;t1.id&#39;, &#39;&lt;&#39;, &#39;t2.id&#39;)
                     -&gt;where(&#39;t2.status&#39;, &#39;APPROVED&#39;);
            })
            -&gt;select(&#39;t1.id&#39;);
    });

// Only show approvals of _that_ particular user.
$query = $query-&gt;whereRelation(&#39;app&#39;, &#39;created_by&#39;, &#39;=&#39;, $userId);

$query = $query-&gt;orderBy(&#39;approvals.id&#39;, &#39;DESC&#39;);

$query = $query-&gt;where(&#39;approvals.status&#39;, &#39;APPROVED&#39;);

return $query;

Edit: Explanation

The subquery in general returns all id where there's a row with the same group_id and app_model, as well as a greater id and finally the status of Approved. This essentially eliminates all but the latest APPROVED row for each group. The outer query then selects these latest APPROVED rows.

huangapple
  • 本文由 发表于 2023年7月31日 20:49:52
  • 转载请务必保留本文链接:https://go.coder-hub.com/76803803.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定