在日常开发中,我们有时会需要对数据的插入操作进行定制。比如,如果表里已有某某记录就不写入新纪录,或者表里没该记录就插入,否则就更新。前者我们称为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. Python OpenCV4趣味应用系列(四)---颜色物体实时检测

    今天,我们来实现一个视频实时检测颜色物体的小实例,视频中主要有三个颜色物体,我们只检测红色和绿色的球状物体,如下图所示: 第一步需要打开视频(或者摄像头): cap = cv2.VideoCaptur ...

  2. [软件使用][matlab]最近经常用到的一些函数的意思,和用法

    ① cat(dim,A,B)按指定的维度,将A和B串联,dim是维度,比如1,2.1指列,2指行: ②numel(A),返回数组中,元素的个数 ③gpuArray(A),在gpu中产生一个数组A,一般 ...

  3. (C#)WPF:关于INotifyPropertyChanged接口的介绍

    注意:INotifyPropertyChanged接口位于System.CompenentModel名称空间中,想使用INotifyPropertyChanged接口时,头文件需添加“using Sy ...

  4. mpvue开发微信小程序,分享按钮报错:`Cannot read property 'apply' of null`

    用mpvue开发微信小程序,分享按钮报错:Cannot read property 'apply' of null onShareAppMessage 是于微信小程序Pages的生命周期钩子,顾这个方 ...

  5. Arduino 将 String 转化为 int

    Arduino 将 String 转化为 int 函数:toInt() 实例: String my_str = "; int my_int = my_str.toInt();

  6. lqb 基础练习 回文数

    基础练习 回文数 时间限制:1.0s   内存限制:512.0MB     问题描述 1221是一个非常特殊的数,它从左边读和从右边读是一样的,编程求所有这样的四位十进制数. 输出格式 按从小到大的顺 ...

  7. requests模块发送带headers的Get请求和带参数的请求

    1.在PyCharm开发工具中新建try_params.py文件: 2.try_params.py文件中编写代码: import requests#设置请求Headers头部header = {&qu ...

  8. 大公司 vs 小公司,你会选哪个?

    找工作跟找对象差不多,在确立关系领证前,彼此要多些了解.在了解的基础上,你再确认是否真心喜欢对方,彼此身上有没有相互吸引的特质,两个人的性格是否互补.三观是否匹配.契合度越高,往后才能相互扶持.彼此成 ...

  9. 使用图数据库 Nebula Graph 数据导入快速体验知识图谱 OwnThink

    前言 本文由 Nebula Graph 实习生@王杰贡献. 最近 @Yener 开源了史上最大规模的中文知识图谱--OwnThink(链接:https://github.com/ownthink/Kn ...

  10. 论文阅读:Face Recognition: From Traditional to Deep Learning Methods 《人脸识别综述:从传统方法到深度学习》

     论文阅读:Face Recognition: From Traditional to Deep Learning Methods  <人脸识别综述:从传统方法到深度学习>     一.引 ...