在 Laravel 中按日期删除重复的数据库记录

Posted

技术标签:

【中文标题】在 Laravel 中按日期删除重复的数据库记录【英文标题】:Deleting duplicate database records by date in 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

【问题讨论】:

【参考方案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://***.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 来选择是要保留最新记录还是最旧记录。

【讨论】:

以上是关于在 Laravel 中按日期删除重复的数据库记录的主要内容,如果未能解决你的问题,请参考以下文章

在 laravel 中按日期获取数据

laravel 如何在 yajra 数据表中按创建日期排序

如何在 Laravel 中按日期排序消息

在 LINQ 中按特定列分组 [重复]

在Python中按日期合并行和求和值[重复]

如何在 Laravel 中按字母顺序对记录进行排序