如何手动实现Try Insert和Insert Or Update

在日常开发中,我们有时会需要对数据的插入操作进行定制。比如,如果表里已有某某记录就不写入新纪录,或者表里没该记录就插入,否则就更新。前者我们称为TryInsert,后者为InsertOrUpdate(也叫做upsert)。一般来说,很多orm框架都会附带这样的函数,但是如果你要批量插入数据,orm自带的函数就不太够用了。下面我们从手动拼SQL的角度来实现TryInsertInsertOrUpdate

考虑到现在流行的两大开源RDBMS对SQL标准支持比较落后,而早期的标准并没有这方面的标准语法,所以我们分成MySQL篇和Postgres篇来分别使用它们各自的方言解决上面提到的两个问题。

MySQL篇

原理解析

insert ignore into

插入如果报错(主键或者Unique键重复),会把错误转成警告,此时返回的影响行数为0,可以用来实现TryInsert()

replace into

replaceinsert语法基本一致,是Mysql的扩展语法,官方的InsertOrUpdatereplace语句的基本逻辑如下:

ok:=Insert()
if !ok {
  if duplicate-key {  // key重复就删掉重新插入
    Delete()
    Insert()
  }
}

从这里我们可以看出replace语句的影响行数,如果是插入,影响行数为1;如果是更新,删除再插入,影响行数为2。

Insert into ... on duplicate key update

也是MySQL扩展语法。... on duplicate key update的逻辑与replace差不多,唯一的区别就是如果插入的新值与旧值一样,默认返回的影响行数为0,所以这里的逻辑是如果新值和旧值相同就不作处理。

代码示例

下面是以golang为例,给出示例:

type User struct {
  UserID   int64  `gorm:"user_id"`
  Username string   `gorm:"username"`
  Password string   `gorm:"password"`
  Address  string   `gorm:"address"`
}

func BulkTryInsert(data []*User) error{
  str:=make([]string, 0, len(data))
  param:=make([]interface{},0,len(data)*4)  // 4个属性
  for _,d:=range data {
    str=append(str,"(?,?,?,?)")
    param=append(d.UserID)
    param=append(d.Username)
    param=append(d.Password)
    param=append(d.Address)
  }
  stmt:=fmt.Sprintf("INSERT IGNORE INTO table_name(user_id,username,password,address) VALUES %s",strings.Join(str,",") )
  return DB.Exec(stmt, param...).Error
}

func BulkUpsert(data []*User) error{
  str:=make([]string, 0, len(data))
  param:=make([]interface{},0,len(data)*4)  // 4个属性
  for _,d:=range data {
    str=append(str,"(?,?,?,?)")
    param=append(d.UserID)
    param=append(d.Username)
    param=append(d.Password)
    param=append(d.Address)
  }
  stmt:=fmt.Sprintf("REPLACE INTO table_name(user_id,username,password,address) VALUES %s",strings.Join(str,",") )    // 与上面的区别仅在这行的SQL
  return DB.Exec(stmt, param...).Error
}

Postgres篇

原理解析

Insert into ... on conflict (...) do nothing

on conflict后面需要带上冲突的键,比如主键或者Unique约束。这条SQL的意思就如字面所示,当某某键存在重复冲突的时候,什么也不做,即TryInsert

Insert into ... on conflict (...) do update set (...)

这条SQL就比较复杂了,Postgres这个语法表面上看比MySQL自由度更高,实际上非常繁琐笨重,不如MySQL务实。set的意思是,冲突时需要指定更新哪些属性,这是强制的,必须具体地说明每个字段,真是不友好啊。大概是要写成这样,其中EXCLUDED指代要插入的那条记录:

INSERT INTO ... on conflict (user_id, address) do update set password=EXCLUDED.password and username=EXCLUDED.username

代码示例

这次我们设想一种实用的场景,python经常被用作科学计算,pandas是大家偏爱的计算包,pandasio部分提供了傻瓜式的读写文件和数据库里数据的函数,比如写数据库的to_sql,但是这个函数有局限性,它只能做到TryInsert和清空表数据再插入,对于upsert则无能为力。目前来说,我们只能手动实现它。

按照上面的解析,我们需要给每张表设置好UniqueConstraint才能使用这个语法。下面给出一个例子:

# 使用的是sqlalchemy
Base = declarative_base()

# 将一个list分割成m个大小为n的list
def chunks(a, n):
    return [a[i:i + n] for i in range(0, len(a), n)]

class DBUser(Base):
  __tablename__ = 'user' # UniqueConstraint和PrimaryKey至少要有一个
  __table_args__ = (UniqueConstraint('user_id', 'address'),
                   {'schema': 'db'})
  user_id = Column(BigInteger)
  username = Column(String(200))
  password = Column(String(200))
  address = Column(String(200))

  def dtype(self): # pandas需要的dtype
    d = {c.name: c.type for c in self.__table__.c}
    if 'id' in d:
        el d['id']   # 一般id都是自动生成的,提供给pandas的dtype应该剔除id
    return d

  def fullname(self):
    return self.__table_args__[-1]['schema'] + '.' + self.__tablename__

  # 只要DBUser再提供一个Unique Constraint的属性列表,下面这两个函数就可以写成通用的函数
  # 这里只是给出例子,点到为止
  def bulk_try_insert(self, engine, data):
    col = self.dtype().keys()
    col_str = ','.join(col)
    col_str = '(' + col_str + ')'
    update_col = []
    for c in col:
      update_str = '{0}=EXCLUDED.{1}'.format(c, c)
      update_col.append(update_str)
    value_str = []
    value_args = []
    for d in data:
      tmp_str = '(' + col.__len__() * '%s,'
      tmp_str = tmp_str[:-1] + ')'
      value_str.append(tmp_str)
      for k in col:
        value_args.append(d[k])

    stmt= 'insert into ' + self.fullname() + col_str + 'values ' + ','.join(
      value_str) + 'on conflict (user_id, address) do update set ' + ",".join(update_col)
    engine.execute(stmt, value_args)

  def bulk_insert_chunk(self, engine, data, n=1000):
    d_list = chunks(data, n)
    for a in d_list:
      self.bulk_insert(engine, a)

原文地址:https://www.cnblogs.com/ripley/p/12045098.html

时间: 2024-10-14 11:15:01

如何手动实现Try Insert和Insert Or Update的相关文章

Oracle多表插入语句Insert All/Insert First

关于INSERT ALL和INSERT FIRST 一.无条件 INSERT ALL 二.条件 INSERT ALL 三.条件 INSERT FIRST Insert-Select 使用Insert Select实现同时向多个表插入记录 一.无条件 INSERT ALL --------------------------------------------------------------------------------------------- INSERT ALL insert_in

INSERT ... ON DUPLICATE KEY UPDATE Syntax

If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that would cause a duplicate value in a UNIQUE index or PRIMARY KEY, MySQL performs an UPDATE of the old row. For example, if column a is declared as UNIQUE and contains the value 1, the f

MYSQL之REPLACE INTO和INSERT … ON DUPLICATE KEY UPDATE用法

REPLACE INTO的用法与INSERT很相似,最终在表中的目的是插入一行新的数据.不同的是,当插入时出现主键或者唯一索引冲突的时候,会删除原有记录,重新插入新的记录.因此,除非表具有主键或者唯一索引,否则使用REPLACE INTO无任何意义. 以下新建了一个表来进行测试,并添加触发检视REPLACE INTO是如何工作的: CREATE TABLE `replace_into` ( `id` int(11) NOT NULL AUTO_INCREMENT, `uid` int(11) N

Oracle 增删改(INSERT、DELETE、UPDATE)语句

?  简介 本文介绍 Oracle 中的增删改语句,即 INSERT.DELETE.UPDATE 语句的使用.是时候展现真正的技术了,快上车: 1.   插入数据(INSERT) 2.   修改数据(UPDATE) 3.   删除数据(DELETE) 4.   注意事项 1.   插入数据(INSERT) u  语法: INSERT INTO TABLE_NAME [(column1[, column2-]] VALUES(value1[, value2-]); 说明: 1)   INSERT

MySQL INSERT ON DUPLICATE KEY UPDATE

来源:https://www.mysqltutorial.org/mysql-insert-or-update-on-duplicate-key-update/ Introduction to the MySQL INSERT ON DUPLICATE KEY UPDATE statement The INSERT ON DUPLICATE KEY UPDATE is a MySQL's extension to the SQL standard's INSERT statement. When

玩转JPA(一)---异常:Repeated column in mapping for entity/should be mapped with insert="false" update="fal

近期用JPA遇到这样一个问题:Repeated column in mapping for entity: com.ketayao.security.entity.main.User column: org_id (should be mapped with insert="false" update="false") 这个错误是由实体类引起的.我一開始是这样写的: @Column private long orgId; @ManyToOne @JoinColumn

mysql之select,insert,delete,update

写在前面 上篇文章学习了创建数据库和数据表,这篇文章将学习对数据表的增删改查操作. 系列文章 mysql之创建数据库,创建数据表 一个例子 上篇文章中,创建了数据库和数据表,数据表中还没有数据,这里我们为三张表中添加数据进行测试. 注意:为了避免字段名或者表明与系统的某些关键字重复,可以使用``包裹字符串,与sql server中的[]类似.``在键盘上方数字键最左边的那个键(英文输入法) 1.添加四个班级信息 use school; -- 添加班级信息 insert into tb_class

Linux命令:MySQL系列之七--INSERT、DELET、UPDATE语句相关练习

一.INSERT插入语句 1.批量插入字段数据 INSERT INTO tb_name (col1,col2,...) VALUES (val1,val2,...),(val1,val2,...): Usage:INSERT INTO class (Name,Age,Gender) VALUES (stu1,Age1,Gender1),(stu2,Age2,Gender2); 在插入数据时,需注意以下几点: 字符型:需用单引号括起 数值型:不需要引号 日期时间型:也不需要引号 空值:必须写成NU

PLSQL_性能优化系列08_Oracle Insert / Direct Insert性能优化

2014-09-25 BaoXinjian 一.Insert 性能影响 应用设计不合理导致的session之间的互锁(enqueue)是影响程序可扩展性最常见的原因.此外,一些共享资源的争用,也会导致性能下降. 本篇介绍两个由并发insert操作导致的等待事件(wait event),以及如何通过优化物理设计来进行改善. 普通Insert操作本身产生的是行锁,因此进程相互之间不会锁住(enqueue),但当很多进程insert同一张表时,会有资源上冲突. 以下是两个例子: 1. Buffer b