16

Gorm 有一个FirstOrCreate方法和一个FirstOrInit,但是之后如何检查记录是否实际创建?如果它不存在,我喜欢创建一条记录,如果它存在,我想更新一些字段。

4

8 回答 8

22

更新 2020.10.09

感谢@vaelin

从 1.20.x 开始,GORM 为不同的数据库提供兼容的 Upsert 支持(Upsert-On-Conflict

// Update columns to new value on `id` conflict
DB.Clauses(clause.OnConflict{
  Columns:   []clause.Column{{Name: "id"}}, // key colume
  DoUpdates: clause.AssignmentColumns([]string{"name", "age"}), // column needed to be updated
}).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

使用gorm 1.9.x 或更低版本,先更新,不存在时再插入更有效。

// update only set name=nick
if err := db.Model(&newUser).Where("id = ?", 3333).Update("name", "nick").Error; err != nil {
    // always handle error like this, cause errors maybe happened when connection failed or something. 
    // record not found...
    if gorm.IsRecordNotFoundError(err){
        db.Create(&newUser)  // create new record from newUser
    }
}

FirstOrInit并且FirstOrCreate是不同的。如果数据库中没有匹配记录,FirstOrInit将初始化结构但不创建记录,FirstOrCreate将创建一条记录并将该记录查询到结构。

于 2019-01-18T10:05:32.160 回答
14

最受好评的答案对我不起作用,但确实如此:

user := NewUser(email, password)
if db.Model(&user).Where("email = ?", email).Updates(&user).RowsAffected == 0 {
    db.Create(&user)
}

这适用于 gorm v1.9.15 和 go 1.13

于 2020-07-29T19:20:49.843 回答
1

FirstOrInit不会创建新记录。它只找到第一个匹配的记录,如果找不到,则使用给定的条件对其进行初始化。

对于两者FirstOrCreateFirstOrInit您可以使用RowsAffected. 如果返回值为“1”,则在数据库中找到该记录,即它已经存在,因此没有创建。如果返回值为“0”,则未找到。

...如果它存在,我想更新一些字段。

我不确定你想在哪里更新。本地在您的map/struct或数据库中。如果是本地的,那么我相信您现在可以做到这一点。如果在数据库中,我建议使用AttrsorAssign方法。

于 2021-06-30T22:28:17.463 回答
1
gormDB.Where(entity.AggregatedData{Type: v.Type}).Assign(entity.AggregatedData{Type: v.Type, Data: v.Data}).FirstOrCreate(v)


 SELECT * FROM "aggregated_data"  WHERE ("aggregated_data"."type" = '2') ORDER BY "aggregated_data"."id" ASC LIMIT 1 

如果存在那么

 UPDATE "aggregated_data" SET "data" = '[{"a":2}]', "type" = '2'  WHERE "aggregated_data"."id" = '2' AND (("aggregated_data"."type" = '2'))  

别的

INSERT INTO "aggregated_data" ("data","type") VALUES ('[{"a":2}]','1') RETURNING "aggregated_data"."id"  
于 2019-01-18T08:09:27.920 回答
1

这是来自 gorm 文档CRUD 部分的示例

user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}

db.NewRecord(user) // => returns `true` as primary key is blank

db.Create(&user)

db.NewRecord(user) // => return `false` after `user` created
于 2016-09-05T15:35:59.923 回答
0
func CreateOrUpdate(db *gorm.DB, model interface{}, where interface{}, update interface{}) (interface{}, error) {
    var result interface{}
    err := db.Model(model).Where(where).First(result).Error
    if err != nil {
        if !errors.Is(err, gorm.ErrRecordNotFound) {
            return nil, err
        } else {
            //insert
            if err = db.Model(model).Create(update).Error; err != nil {
                return nil, err
            }
        }
    }
    //not update some field
    reflect.ValueOf(update).Elem().FieldByName("someField").SetInt(0)
    if err = db.Model(model).Where(where).Updates(update).Error; err != nil {
        return nil, err
    }
    return update, nil
}
于 2020-09-12T17:44:29.397 回答
0

有一个更好的方法来做到这一点:

    if err := db.Where(User{Email: "some@email.com"}).
       Assign(User{Email: "some@email.com", Age: 45}).
       FirstOrCreate(&User{}).Error; err != nil {
         c.Next(err)
         return
    }

在此示例中,如果找到电子邮件为“some@email.com”的用户,则将更新“年龄”字段。相反,如果没有找到用户,则创建它。

请注意,我将丢弃创建的用户,但您可以根据需要保留参考。此外,由于某些 GORM 原因,需要在 Assign 子句中至少提供一个过滤器字段,这就是您看到电子邮件被填充两次的原因。

于 2020-09-21T03:23:30.660 回答
0

请参阅此处的属性。它不会准确地告诉您记录是否实际创建,但仅在实际创建记录时才允许您更新某些字段(这似乎是您最终想要实现的目标)。

于 2016-09-07T02:47:35.343 回答