在日常开发中,我们有时会需要对数据的插入操作进行定制。比如,如果表里已有某某记录就不写入新纪录,或者表里没该记录就插入,否则就更新。前者我们称为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)

如何手动实现TryInsert和InsertOrUpdate的更多相关文章

  1. 手动添加kdump

    背景:     Linux嵌入式设备内核挂死后,无法自动重启,需要手动重启.而且如果当时没有连串口的话,就无法记录内核挂死时的堆栈,所以需要添加一种方式来记录内核挂死信息方便以后调试使用.设备中增加k ...

  2. SSH免手动输入密码和设置代理

    通过使用sshpass将密码写入命令里,直接执行,免去手动密码输入的步骤命令如下: sshpass -p password_abc ssh user_abc@ssh_host -p ssh_port ...

  3. Ubuntu手动设置DSL连接

    在安装完Ubuntu之后,发现图形界面的DSL连接不管用了,郁闷了好几天,想想移动每个月120个小时的流量岂不是白白浪费了.正当我想重返Windows系统的时候,却发现了手动设置连接DSL的好方法,感 ...

  4. 【Win 10应用开发】手动调用WCF服务

    调用服务最简单的方法就是,直接在VS里面添加服务引用,输入服务的地址即可,无论是普通Web服务,还是WCF服务均可.VS会根据获取到的元数据,自动生成客户端代码. 如果服务的调用量很大,应用广泛,可以 ...

  5. 记录一次bug解决过程:resultType和手动开启事务

    一.总结 二.BUG描述:MyBatis中resultType使用 MyBatis中的resultType类似于入参:parameterType.先看IDCM项目中的实际使用案例代码,如下: // L ...

  6. 手动配置三台虚拟机pacemaker+corosync并添加httpd服务

    创建三台虚拟机,实验环境:centos7.1,选择基础设施服务安装. 每台虚拟机两块网卡,第一块为pxe,第二块连通外网,手动为两块网卡配置IP.网关,使它们都能ping通外网并可以互相通过hostn ...

  7. Azure AD Connect 手动同步

    我们目前采用工具Azure AD Connect 目录同步工具将本地域控制器的用户信息同步至office365和Azure 在之前目录同步工具中使用Windows 任务计划程序或单独的 Windows ...

  8. Oracle XE手动建立数据库实例

    参考资料为: [Oracle XE系列之三]使用OMF方式手工创建Oracle XE数据库 - 王立夫 - 博客园http://www.cnblogs.com/opfo/p/5056122.html ...

  9. centos手动编译安装apache、php、mysql

    64位centos 5.5手动安装lamp,要求curl.json.pdo_mysql.gd,记录如下. centos 5.4.5.5.5.6的内核都是2.6.18,都可以安装php 5.3. 卸载旧 ...

随机推荐

  1. 详细讲解 Redis 的两种安装部署方式

    Redis 是一款比较常用的 NoSQL 数据库,我们通常使用 Redis 来做缓存,这是一篇关于 Redis 安装的文章,所以不会涉及到 Redis 的高级特性和使用场景,Redis 能够兼容绝大部 ...

  2. OC语言自学基础知识总结

    一.成员变量的作用域 二.点语法 三.构造方法 四.分类 五.类的本质 六.自动生成getter和setter方法 七.description方法 八.id类型 九.SEL 一.成员变量的作用域 @p ...

  3. SqlServer2005 查询 第七讲 order by

    今天我们来讲sql命令中的参数order by的用法 order by order by:可以理解成[以某个字段排序] order by a,b // a和b都按升序 order by a,b des ...

  4. [error] hadoop:ls: `.': No such file or directory

    问题: 解决: https://stackoverflow.com/questions/28241251/hadoop-fs-ls-results-in-no-such-file-or-directo ...

  5. Mybatis 关联对象不能输出的解决办法

    Mybatis 关联对象不能输出的解决办法 1.如图所示,现在进行查询的时候并没有得到来自另一张表address项 2.我们进行如下配置: (1).在mybatis-config.xml 文件中配置, ...

  6. 关于 Python 对象拷贝的那点事?

    概述 在本篇文章中,会先介绍 Python 中对象的基础概念,之后会提到对象的深浅拷贝以及区别.在阅读后,应该掌握如下的内容: 理解变量.引用和对象的关系 理解 Python 对象中 identity ...

  7. Vue_声明周期

    Vue生命周期 在vue2.0的时候,声明钩子发生了改变,具体有八个 <!-- HTML部分 --> <div id="app"> <div>{ ...

  8. PostGIS 使用Mysql_fdw同步ArcGIS填坑记录

    ##实现Mysql_fdw数据同步过程中,出现过很多坑,开此贴记录一下 1.触发器记录 这里insert的时候,采用过insert into f_pressureline select new.*,出 ...

  9. PostGIS 安装教程(Linux)(一)

    ##本文分两部分,第一部分讲linux下postgresql的安装,第二部分讲postgis的安装 ##感谢作者:https://www.linuxidc.com/Linux/2017-10/1475 ...

  10. Requests小技巧