【问题标题】:Deleting duplicate database records by date in Laravel在 Laravel 中按日期删除重复的数据库记录
【发布时间】:2021-09-18 14:20:49
【问题描述】:

我目前正在开发一个由 PostgreSQL 数据库支持的 Laravel 8 应用程序,其中我正在为各种不同的项目生成一个Cost 模型。我的意图是每天最多记录一个Cost->value,每个item;但是,由于重叠作业的一些问题以及我使用 updateOrCreate() 方法的方式,我最终每天为每个项目创建多个 Cost 记录。

我已经修复了逻辑,因此我不再每天获得多条记录,但我现在想回去清理所有重复的记录。

有没有一种有效的方法可以删除每项的所有重复记录,每天保留最新的记录,即:每项不超过一条记录, 每天?虽然我确信这看起来很简单,但我似乎无法直接在 SQL 中或通过 Laravel 和 PHP 找到正确的逻辑。

可能相关信息:目前,表中有约 50k 条记录。

示例表

// Example database table migration
Schema::create('costs', function (Blueprint $table) {
    $table->id();
    $table->string('item');
    $table->decimal('value');
    $table->date('created_at');
    $table->timestamp('updated_at');
});

粗略示例(之前)

id,item,value,created_at,updated_at
510,item1,12,2021-07-02,2021-07-02 16:45:17 126.5010838402907751
500,item1,13,2021-07-02,2021-07-02 16:45:05 126.5010838402907751
490,item1,13,2021-07-02,2021-07-02 16:45:01 126.5010838402907751
480,item2,12,2021-07-02,2021-07-02 16:44:59 126.5010838402907751
470,item2,14,2021-07-02,2021-07-02 16:44:55 126.5010838402907751
460,item2,12,2021-07-02,2021-07-02 16:44:54 126.5010838402907751
450,item2,11,2021-07-02,2021-07-02 16:44:53 126.5010838402907751

粗略示例(期望的最终状态)

id,item,value,created_at,updated_at
510,item1,12,2021-07-02,2021-07-02 16:45:17 126.5010838402907751
480,item2,12,2021-07-02,2021-07-02 16:44:59 126.5010838402907751

【问题讨论】:

    标签: laravel postgresql eloquent


    【解决方案1】:

    你可以使用EXISTS():


    select * from meuk;
    
    DELETE FROM meuk d
    WHERE EXISTS (
            SELECT * FROM meuk x
            WHERE x.item = d.item                           -- same item
            AND x.updated_at::date = d.updated_at::date     -- same date
            AND x.updated_at > d.updated_at                 -- but: more recent
            );
    
    select * from meuk;
    

    结果:


    DROP TABLE
    CREATE TABLE
    COPY 7
    VACUUM
     id  | item  | value | created_at |     updated_at      
    -----+-------+-------+------------+---------------------
     510 | item1 |    12 | 2021-07-02 | 2021-07-02 16:45:17
     500 | item1 |    13 | 2021-07-02 | 2021-07-02 16:45:05
     490 | item1 |    13 | 2021-07-02 | 2021-07-02 16:45:01
     480 | item2 |    12 | 2021-07-02 | 2021-07-02 16:44:59
     470 | item2 |    14 | 2021-07-02 | 2021-07-02 16:44:55
     460 | item2 |    12 | 2021-07-02 | 2021-07-02 16:44:54
     450 | item2 |    11 | 2021-07-02 | 2021-07-02 16:44:53
    (7 rows)
    
    DELETE 5
     id  | item  | value | created_at |     updated_at      
    -----+-------+-------+------------+---------------------
     510 | item1 |    12 | 2021-07-02 | 2021-07-02 16:45:17
     480 | item2 |    12 | 2021-07-02 | 2021-07-02 16:44:59
    (2 rows)
    

    另一种方法,使用窗口函数。这个想法是向下编号同一 {item,day} 上的所有记录,并仅保留第一个:


    DELETE FROM meuk d
    USING (
            SELECT item,updated_at
            , row_number() OVER (PARTITION BY item,updated_at::date 
                                 ORDER BY item,updated_at DESC
                                 ) rn
            FROM meuk x
            ) xx
    WHERE xx.item = d.item
    AND xx.updated_at = d.updated_at
    AND xx.rn > 1
            ;
    

    请注意,此过程始终涉及自联接:记录的命运取决于同一表中是否存在其他记录

    【讨论】:

    • 第一个选项,使用EXISTS() 正是我所追求的。谢谢!
    • 第二个稍微更好,因为它处理关系的方式不同。
    • 将查询从AND x.updated_at > d.updated_at 更改为AND x.id > d.id 以说明updated_at 列中的关系是否有意义?
    • 仅当id 列具有有意义的顺序时,我不知道。在这种情况下,它可以作为总排序,或作为 updated_at 的决胜局。
    【解决方案2】:

    这里有一个毛茸茸的 SQL 查询 https://stackoverflow.com/a/1313293/1346367 ;更简单的一种是基于在costs1.id < costs2.id 上将表与自身连接起来。 <> 表示您希望保留最旧的值还是最新的值。遗憾的是,没有一个简单的方法(如果我没记错的话,你不能相信 GROUP BY 语句中的 ORDER BY)。

    由于我无法向你详细解释这个查询是如何工作的,所以我给你一个 Laravel/PHP 解决方案,它效率低但易于理解:

    $keepIds = [];
    // Loop the table (without Eloquent for performance benefit).
    foreach(DB::table('costs')->orderBy('id', 'ASC')->get() as $cost) {
        // Keep overwriting the index such that the last overwrite will contain the end result.
        $keepIds[$cost->item] = $cost->id;
    }
    
    // Remove elements that you do not want to keep.
    DB::table('costs')->whereNotIn('id', array_values($keepIds))->delete();
    

    我不确定最后一个查询是否能正常工作,尽管数组很大;它可能会引发 SQL 错误。

    请注意,您可以使用orderBy 来选择是要保留最新记录还是最旧记录。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-14
      • 2021-02-23
      • 2011-05-10
      • 1970-01-01
      相关资源
      最近更新 更多