前言
在数据库的CURD过程中,我们偶尔会遇到这样的需求,一条记录发到后端,如果已经存在,就更新这条记录,如果不存在,就插入这条数据。比如我遇见的这个云同步便签的功能,
用户点击保存会有两种情况,一是新建的便签,二是已有的标签经过编辑后保存,所以我们希望在同一个接口兼容这两种操作。
如何实现?
如果正常来说,我第一个想到的是这样
func CreateOrUpdateRecord(db *gorm.DB, record *models.Record) error {
// 尝试在数据库中查找具有相同 record_id 的记录
existingRecord := &models.Record{}
result := db.Where(models.Record{RecordID: record.RecordID}).FirstOrCreate(existingRecord)
if result.Error != nil {
return result.Error
}
// 如果记录已存在,更新它的字段
if !result.RowsAffected == 0 {
existingRecord.Title = record.Title
existingRecord.Content = record.Content
result := db.Save(existingRecord)
if result.Error != nil {
return result.Error
}
}
return nil
}
这不优雅!
所以我去查了一下官方文档中关于这种操作的说明,在这里,官方给出的解释是这样的。
// Update columns to new value on `id` conflict
db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
DoUpdates: clause.AssignmentColumns([]string{"name", "age"}),
}).Create(&users)
// MERGE INTO "users" USING *** WHEN NOT MATCHED THEN INSERT *** WHEN MATCHED THEN UPDATE SET "name"="excluded"."name"; SQL Server
// INSERT INTO "users" *** ON CONFLICT ("id") DO UPDATE SET "name"="excluded"."name", "age"="excluded"."age"; PostgreSQL
// INSERT INTO `users` *** ON DUPLICATE KEY UPDATE `name`=VALUES(name),`age`=VALUES(age); MySQL
可惜的时这部分并没有官方的中文翻译,找了好一会,这里使用Clauses
方法:用于指定查询的选项和条件,之后添加了一个错误处理的配置和解决参数,这里需要一个唯一约束的键作为冲突的判断,当然,并不一定是主键才可以,我们可以通过unique标签来选定任意键唯一约束。
// 便签表
type Record struct {
gorm.Model
RecordID int64 `json:"record_id" gorm:"unique"`
UserID int64 `json:"userID"`
Title string `json:"title"`
Content string `json:"content"`
}
下面的参数是当冲突发生时,给出的解决方案。
- DoNothing:冲突后不处理,参照上面的 Build 实现可以看到,这里只会加入 DO NOTHING;
- DoUpdates: 配置一批需要赋值的 KV,如果没有指定 DoNothing,会根据这一批 Assignment 来写入要更新的列和值;
- UpdateAll: 冲突后更新所有的值(非 default tag字段)。
变得优雅
于是,这个操作就变成了下面的样子,如果没有唯一索引的限制,我们就无法复用这个能力,需要考虑别的解法。
func CreatOrUpdateRecord(record *models.Record) error {
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "record_id"}},
UpdateAll: true,
}).Create(record).Error
}