How to Create or Update a record with GORM?

huangapple go评论90阅读模式
英文:

How to Create or Update a record with GORM?

问题

Gorm有一个FirstOrCreate方法和一个FirstOrInit方法,但是如何在之后检查记录是否实际上被创建了呢?我想在记录不存在时创建一条记录,如果记录已存在,则更新一些字段。

英文:

Gorm has a FirstOrCreate method and a FirstOrInit but how to check afterwards if the record was actually created? I like to create a record if it does not exists and if it exists I want to update some fields.

答案1

得分: 38

更新日期:2020年10月9日

感谢@vaelin

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

// 在`id`冲突时更新列为新值
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

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

// 只更新name为nick
if err := db.Model(&newUser).Where("id = ?", 3333).Update("name", "nick").Error; err != nil {
    // 始终像这样处理错误,因为错误可能发生在连接失败或其他情况下。
    // 记录未找到...
    if gorm.IsRecordNotFoundError(err){
        db.Create(&newUser)  // 从newUser创建新记录
    }
}

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

英文:

update 2020.10.09

Thanks for @vaelin

From 1.20.x on, GORM provides compatible Upsert support for different databases( 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

With gorm 1.9.x or below, it's more effecient to update first, then insert when not existed.

// 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 and FirstOrCreate are different. If there is no match record in database, FirstOrInit will init struct but not create record, FirstOrCreate will create a record and query that record to struct.

答案2

得分: 29

最受赞同的答案对我没有用,但这个有用:

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。

英文:

The most upvoted answer did not work for me, but this did:

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

This works for gorm v1.9.15 and go 1.13

答案3

得分: 2

有一种更好的方法来做这件事:

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"的用户,那么"Age"字段将被更新。相反,如果没有找到用户,则会创建一个新用户。

请注意,我在代码中丢弃了创建的用户,但如果你想保留引用,也可以这样做。
另外,由于某些 GORM 的原因,在 Assign 子句中至少需要提供一个过滤字段,这就是为什么你看到 email 被填充两次的原因。

英文:

There is a better way to do it:

    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
	}

In this example, if a user with email "some@email.com" is found, then the field "Age" will be updated. On the contrary, if no user if found, then it is created.

Note that I am discarding the created user, but you can keep the reference if you want.
Also, for some GORM reasons, it is required to provide at least a filter field in the Assign clause, that's why you see email being populated twice.

答案4

得分: 2

FirstOrInit不会创建新的记录。它只会查找第一个匹配的记录,并在找不到时使用给定的条件进行初始化。

对于FirstOrCreateFirstOrInit,你可以使用RowsAffected。如果返回值为"1",表示记录在数据库中已找到,即已存在,因此没有创建新记录。如果返回值为"0",表示未找到。

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

我不确定你想在哪里进行更新。是在你的map/struct中本地更新,还是在数据库中更新。如果是本地更新,我相信你现在已经可以做到了。如果是在数据库中更新,我建议使用AttrsAssign方法。

英文:

FirstOrInit doesn't create a new record. It only finds the first matched record and initialises it with given conditions if unfound.

For both FirstOrCreate and FirstOrInit, you can use RowsAffected. If return value is "1", the record was found in the DB, i.e. it already exists, and thus wasn't created. If return value is "0", it wasn't found.

> ... if it exists I want to update some fields.

I'm not sure where you want this updation. Locally in your map/struct or in the DB. If local, then I'm confident you can do that now. If in the DB, I would suggest using Attrs or Assign methods.

答案5

得分: 1

这是gorm文档中的一个示例,位于CRUD部分

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

db.NewRecord(user) // => 返回 `true`,因为主键为空

db.Create(&user)

db.NewRecord(user) // => 在 `user` 创建后返回 `false`
英文:

Here's example from gorm documentation CRUD section

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

答案6

得分: 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"

英文:
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 

and if exist then

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

else

INSERT INTO "aggregated_data" ("data","type") VALUES ('[{"a":2}]','1') RETURNING "aggregated_data"."id"  

答案7

得分: 0

请参考这里的属性。它不会准确告诉你记录是否实际创建,但是如果记录实际创建了,它将允许你更新一些字段(这似乎是你最终想要实现的目标)。

英文:

See Attrs here. It won't exactly tell you whether the record was actually created, but will let you update some fields only if record was actually created (which seems to be what you want to achieve in the end).

答案8

得分: 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 {
			//插入
			if err = db.Model(model).Create(update).Error; err != nil {
				return nil, err
			}
		}
	}
	//不更新某个字段
	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
}
英文:
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
}

答案9

得分: 0

似乎你可以直接使用以下代码:

db.Save(&MyModel)

在查看这里的答案后,我浏览了gorm的代码,并意识到在保存时,go文档中提到:

// 如果值没有主键,则将更新值保存到数据库中,否则将插入它。

英文:

Seems like you can just use

db.Save(&MyModel)

After poking through the answers here, I was looking through gorm code and realized the go doc on save states

> // Save update value in database, if the value doesn't have primary key, will insert it

答案10

得分: 0

只需将其放入一个数组中:

db.Save([]User{user}).Error

通过查看源代码,它将来自切片的所有更新设置为冲突时的更新。

最受投票支持的答案仍然是最好的,因为它使一切都明确化。然而,如果你只想简洁并且对你的模型很了解,将其放入一个切片中可以是一种简短的形式。

源代码:

switch reflectValue.Kind() {
  case reflect.Slice, reflect.Array: 
    if _, ok := tx.Statement.Clauses["ON CONFLICT"]; !ok {
        tx = tx.Clauses(clause.OnConflict{UpdateAll: true}) 
    } 
...
英文:

Just put it in an array:

db.Save([]User{user}).Error

By reviewing the source code, it sets all updates from a slice, and on a conflict it updates all.

The most voted answer is still the best because it makes everything explicit. However, if you just want it brief and know your models well, putting it in a slice can be a short form of that.

Source code:

switch reflectValue.Kind() {
  case reflect.Slice, reflect.Array: 
    if _, ok := tx.Statement.Clauses["ON CONFLICT"]; !ok {
        tx = tx.Clauses(clause.OnConflict{UpdateAll: true}) 
    } 
...

huangapple
  • 本文由 发表于 2016年9月5日 22:48:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/39333102.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定