在 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 中按日期删除重复的数据库记录的主要内容,如果未能解决你的问题,请参考以下文章