常用方法

Insert/Replace/Save

这几个链式操作方法用于数据的写入,并且支持自动的单条或者批量的数据写入,区别如下:

  1. Insert

    使用INSERT INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,返回失败,否则写入一条新数据。

  2. Replace

    使用REPLACE INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,会删除原有的记录,必定会写入一条新记录。

  3. Save

    使用INSERT INTO语句进行数据库写入,如果写入的数据中存在主键或者唯一索引时,更新原有数据,否则写入一条新数据。对于部分数据库,例如 PgSQL, SQL server, Oracle 可以使用 OnConflict 方法指定冲突键。

    db.Model(table).Data(g.Map{
    	"id":          1,
    	"passport":    "p1",
    	"password":    "pw1",
    }).OnConflict("id").Save()

在部分数据库类型中,并不支持Replace/Save方法,具体请参考链式操作介绍章节。

这几个方法需要结合Data方法使用,该方法用于传递数据参数,用于数据写入/更新等写操作。

InsertIgnore

用于写入数据时如果写入的数据中存在主键或者唯一索引时,忽略错误继续执行写入。该方法定义如下:

func (m *Model) InsertIgnore(data ...interface{}) (result sql.Result, err error)

InsertAndGetId

用于写入数据时并直接返回自增字段的ID。该方法定义如下:

func (m *Model) InsertAndGetId(data ...interface{}) (lastInsertId int64, err error)

OnDuplicate/OnDuplicateEx

OnDuplicate/OnDuplicateEx方法需要结合Save方法一起使用,用于指定Save方法的更新/不更新字段,参数为字符串、字符串数组、Map。例如:

OnDuplicate("nickname, age")
OnDuplicate("nickname", "age")
OnDuplicate(g.Map{
    "nickname": gdb.Raw("CONCAT('name_', VALUES(`nickname`))"),
})
OnDuplicate(g.Map{
    "nickname": "passport",
})

其中OnDuplicateEx用于排除指定忽略更新的字段,排除的字段需要在写入的数据集合中。

使用示例

示例1,基本使用

数据写入/保存方法需要结合Data方法使用,方法的参数类型可以为Map/Struct/Slice

// INSERT INTO `user`(`name`) VALUES('john')
g.Model("user").Data(g.Map{"name": "john"}).Insert()

// INSERT IGNORE INTO `user`(`uid`,`name`) VALUES(10000,'john')
g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).InsertIgnore()

// REPLACE INTO `user`(`uid`,`name`) VALUES(10000,'john')
g.Model("user").Data(g.Map{"uid": 10000, "name": "john"}).Replace()

// INSERT INTO `user`(`uid`,`name`) VALUES(10001,'john') ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`)
g.Model("user").Data(g.Map{"uid": 10001, "name": "john"}).Save()

也可以不使用Data方法,而给写入/保存方法直接传递数据参数:

g.Model("user").Insert(g.Map{"name": "john"})
g.Model("user").Replace(g.Map{"uid": 10000, "name": "john"})
g.Model("user").Save(g.Map{"uid": 10001, "name": "john"})

数据参数也常用 struct 类型,例如当表字段为 uid/name/site 时:

type User struct {
    Uid  int    `orm:"uid"`
    Name string `orm:"name"`
    Site string `orm:"site"`
}
user := &User{
    Uid:  1,
    Name: "john",
    Site: "https://goframe.org",
}
// INSERT INTO `user`(`uid`,`name`,`site`) VALUES(1,'john','https://goframe.org')
g.Model("user").Data(user).Insert()

示例2,数据批量写入

通过给Data方法输入Slice数组类型的参数,用以实现批量写入。数组元素需要为Map或者Struct类型,以便于数据库组件自动获取字段信息并生成批量操作SQL

// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2'),('john_3')
g.Model("user").Data(g.List{
    {"name": "john_1"},
    {"name": "john_2"},
    {"name": "john_3"},
}).Insert()

可以通过Batch方法指定批量操作中分批写入条数数量(默认是10),以下示例将会被拆分为两条写入请求:

// INSERT INTO `user`(`name`) VALUES('john_1'),('john_2')
// INSERT INTO `user`(`name`) VALUES('john_3')
g.Model("user").Data(g.List{
    {"name": "john_1"},
    {"name": "john_2"},
    {"name": "john_3"},
}).Batch(2).Insert()

示例3,数据批量保存

批量保存操作与单条保存操作原理是一样的,当写入的数据中存在主键或者唯一索引时将会更新原有记录值,否则新写入一条记录。

oracle, dm, mssql 不支持批量保存。

// INSERT INTO `user`(`uid`,`name`) VALUES(10000,'john_1'),(10001,'john_2'),(10002,'john_3')
// ON DUPLICATE KEY UPDATE `uid`=VALUES(`uid`),`name`=VALUES(`name`)
g.Model("user").Data(g.List{
    {"uid":10000, "name": "john_1"},
    {"uid":10001, "name": "john_2"},
    {"uid":10002, "name": "john_3"},
}).Save()

RawSQL语句嵌入

gdb.Raw是字符串类型,该类型的参数将会直接作为SQL片段嵌入到提交到底层的SQL语句中,不会被自动转换为字符串参数类型、也不会被当做预处理参数。更详细的介绍请参考章节:ORM高级特性-RawSQL。例如:

// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES('id+2','john','123456','now()')
g.Model("user").Data(g.Map{
	"id":          "id+2",
	"passport":    "john",
	"password":    "123456",
	"nickname":    "JohnGuo",
	"create_time": "now()",
}).Insert()
// 执行报错:Error Code: 1136. Column count doesn't match value count at row 1

使用gdb.Raw改造后:

// INSERT INTO `user`(`id`,`passport`,`password`,`nickname`,`create_time`) VALUES(id+2,'john','123456',now())
g.Model("user").Data(g.Map{
	"id":          gdb.Raw("id+2"),
	"passport":    "john",
	"password":    "123456",
	"nickname":    "JohnGuo",
	"create_time": gdb.Raw("now()"),
}).Insert()






Content Menu

  • No labels

37 Comments

  1. Save方法应该需要做一个Data的区分,分为InsertDataSaveData.例如以下场景:记录当日ip访问次数,不存在时写入数据ip,count=1,存在时count=count+1.这种情况下不做数据源区分会直接导致数据覆盖.

    1. 这样的易用性比较低,复杂度也提高了,你见过有其他ORM这么设计么?

      1. SaveData作为可选参数,当SaveData不存在时直接引用InsertData作为填充.的确作为ORM很少实现这样的功能,所以最后都是以手撸sql来实现.

  2. Insert支持model数组,批量插入吗?

  3. 1、User.Insert() 和  db.Table("user").Data(g.Map{"name": "john"}).Insert() 是不是会造成不统一的混乱

    2、insert后能否支持一下,回写主键自增id,现在是通过LastInsertId手动回写的

    感谢

    1. 1、dao.User.Insert(xxx)方式只是为了对简单的操作提供便捷的快捷方式,这种操作比dao.User.Data(xxx).Insert()要方便一些。其实Update操作更能体现便捷,例如dao.User.Data(xxx).Where(xxx).Update()dao.User.Update(xxx, xxx)差别就比较大。

      2、自增ID都是通过LastInsertId获取的,这其实也是标准库推荐的方式,因为这个自增ID获取其实是一条新的SQL操作,由开发者主动选择性调用。

  4. oracle下该如何通过Insert()插入一条自增数据,我使用gdb.Raw(“table_seq.nextval”)来对主键进行自增,然而Raw的嵌入没有生效,打印的sql显示这依然是一个字符串 'table_seq.nextval',本人比较菜,不知道是不是操作有问题哈

    1. 麻烦将代码往github上提个issue

  5. 最新的 v1.15.7 版本,无法写入数据库 ,Insert,Data,InsertAndGetId 方法均不能写入数据库,也不报错 

    db := g.DB("default")

        r, err := db.Table("chat_window").Insert(g.Map{"from_account": "keke"})

    1. 看下database的配置,是不是在空跑模式下呢



      1. 感谢,是我复制官网的配置文件,里面有dryrun =1 只读不写造成的

  6. orm 操作能否返回这个对象的实例呢,例如加个 create 方法成功后返回这个 model 的实例,有些场景需要给前端返回这个 model 的一些字段,看到现在设计基本上是实现了 sql.Result 这个接口返回主键 id 和插入记录的行数

  7. 后续是否会支持INSERT INFO IF EXISTS语法

  8. 代码片段:

    TableId为主键

    type ToolTable struct {
    TableId :0
    TableName :"user"
    }
    r
    esult, err := tx.Model(dao.ToolTable .Table()).Insert(ToolTable )

    生成的sql:INSERT INTO `tool_table`(`table_name`,`table_id`) VALUES('user',0)  

    导致插入的主键被赋值0,没有自增

    1. 中间加上OmitEmptyData()

    2. tx.Model(dao.ToolTable .Table()).OmitEmptyData().Insert(ToolTable )

      1. 存在主键的表在 insert struct 时都需要调用OmitEmptyData这个方法,感觉有点臃肿了

      1. 可以放一个具体的例子吗?

  9. var result []*model.ToolTable 
    err := db.GetScan(ctx,&result, sql)
    for _, table := range result {
    g.DB().Model("user").Insert(table)
    }

    问题在于:g.DB().GetScan为主键赋了值,然后直接调用insert时导致报错,中间得加一层OmitEmptyData方法


    var result []*model.ToolTable 
    err := db.GetScan(ctx,&result, sql)
    for _, table := range result {
    g.DB().Model("user").OmitEmptyData().Insert(table)
    }
  10. 我用偏方做了在postgre里使用save的方法

    testModel.Data(testEntity).OnDuplicateKey("test_id").Save()
    // 生成如下: INSERT INTO test (test_id,text) VALUES(2,'john') ON CONFLICT (test_id) DO UPDATE SET "test_id"=EXCLUDED.test_id "text1"=EXCLUDED.text1
    
    

    详情的看下下面文章

    https://blog.csdn.net/qlynick/article/details/126524195

  11. 目前遇到个需求上的问题,批量插入后想获取这轮批量插入的自增ID数组怎么用呢,

    1. 批处理就没有返回自增这一说.程序里直接生成唯一编号.

      1. 为什么批处理不能有返回自增主键的这一说呢?

        1. 插入数据的自增编号不是必定连续的.last_insert_id(mysql为例)返回的是最后一次的.ORM不论怎么处理也都是基于数据库自身提供的方法上.

  12. 使用pgsql,用的InsertAndGetId
    第一种情况:id是主键和自增键,fielda是唯一键,同一个接口,调用次数多了,RETURNING id 会变成 RETURNING fielda ,导致报错LastInsertId is not supported by primary key type: varchar ,不重启服务,再调用又可以,反复出现;
    第二种情况:id是主键和自增键,没有唯一键,调用次数很多,也不会报错。
    难道RETURNING的是主键和唯一键随机选择的?

    1. 我也有一样的情况,我还奇怪为啥有些表 InsertAndGetId 正常,有些表就会间隔性报错,原来是因为同一张表里面有非主键以外的唯一健 ...

  13. fy

    insert的问题:

    in.Date = gtime.Now()
    fmt.Println(in.Date)
    _, err = dao.test.Ctx(ctx).Data(in).Insert()

    这里打印的in.Date是2023-03-24 23:27:58

    但是数据库里的是2023-03-24 15:27:58,时间差了8个小时,为什么呢?

    在数据库里直接INSERT INTO `test`(`id`,`date`) VALUES(0,'2023-03-24 23:27:58')没有问题。

    1. fy

      刚看到了orm时区处理,明天了试试。

  14. insert的时候怎么识别错误是主键冲突或者唯一键冲突,有没有定义关于这种错误的变量可以使用

  15. 使用的是 2.4.1 版本但是 pgsql 使用 LastInsertId 方法依旧提示 LastInsertId is not supported by this driver"

  16. 能否实现类似于 gorm 中的 FirstOrCreate 方法或者 php-laravel-ormsave()   ?直接传递一个struct,返回的是一个完整的数据对象

    1. 目前没这个功能,每个方法只走了一个事情,目前可以分开操作,有需要可以提issue或者pr哈。

  17. 调用 Insert() 方法时,生成的语句是 

    insert into test (id, uuid) values (0, 'a');

    MySQL 自动忽略为0 的 id,可以正常插入,并插入自增 id
    PostgreSQL 会插入 id 为 0 的记录,再次调用报逐渐冲突

    怎么破?

    1. 我用的sqlite3,我也碰到此问题了

    2. insert 传入的 data 值中不要把 id int 设置为零值(也就是0),MySQL 可以正常插入id = 0 的数据是因为sql_mode设置了NO_AUTO_VALUE_ON_ZERO