最近开发过程当中接到的一个需求,将一堆数据插入到已有数据表中,若是存在则更新,不存在则新增
接到需求想到的第一个想法是去判断,判断其中某个惟一字段是否已经存在在表里了.存在了就使用更新语法,不然使用插入语法.
伪代码
html
if db.query(table).filter_by(name=input_name).first(): do update else: do insert
写着写着就发现不对了,数据要是很是多,那这个效率可就不好了.果断借助搜索引擎汲取知识.python
先作一个表,再造一点数据
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from sqlalchemy import Column, Integer, String # 这个在后面作了修改.使用了os模块 sqlite_engine = 'sqlite:///./test_data.db' engine = create_engine(sqlite_engine, connect_args={ "check_same_thread": False}, echo=True) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() class Users(Base): __tablename__ = 'users' # sqlite好像默认主键为integer的时候是自增的,在我填写表的时候,不传值也是能够的. id = Column(Integer, primary_key=True) # 主键以外须要有一个惟一字段,来做为判断是否重复的依据. name = Column(String, unique=True, index=True) niki_name = Column(String) password = Column(String) Base.metadata.create_all(bind=engine) sess = SessionLocal()
造好的数据mysql
id name nikiname password 1 张三 nkiname1 12312312 2 李四 1232asdksdk asdhzcbjkgxkhjjga 3 王五 123awsdhjkzxhckjag 12379asdghkjashdkj 4 赵柳 123ews99p 123askdlhklsdah 5 田七 123skizdyhasd- zsdfkhzzjfhashl 6 网吧 23907sadhyash 234907adshashdkl
肯定最终方案
主要在考虑的是使用REPLACE INTO 仍是使用INSERT INTO OR UPDATE
二者的区别:
1,replace into 若是主键或者惟一索引上存在相同的字段,会将当前数据删除,而后再insert一条新数据进去,这个时候若是没有指定id,那么id就会自增进行改变了,不符合需求
2, 使用insert into or update 若是存在就使用update语法,不然使用insert能够保证id不会被修改.最终选择这个比较适合场景.
sql
由于使用的是sqlite 在官网上找到了对应的upsert的操做方法
https://www.sqlite.org/lang_UPSERT.html数据库
出现的问题一:在Navicat可视化操做上操做,显示语法出错(后来发现是个人版本过低了)
按照官网提供的写法在navicat上没法执行,原本用的12版本,后来我换15版本了 发现又能够了,不排除我以前的语句写错了?那就尴尬了express
INSERT INTO users ( name, niki_name, password ) VALUES ( '川川川川普', '123123aweq', '123qseasd' ),('张三','昵称','他的密码')ON CONFLICT ( NAME ) DO UPDATE --这个exclude能够将它理解为这些values存在的一张临时表名字叫excluded 当name冲突的时候 SET niki_name = excluded.niki_name, password = excluded.password;
百思不得其解.官网都说能够了,为何会出问题,去看了一下版本,很明显版本没问题windows
#sqlite3 test_db.db SQLite version 3.24.0 2018-06-04 19:24:41 Enter ".help" for usage hints. sqlite>
突发奇想在命令行将代码试了一下,巧了成功了,已经能够在navicat运行了,这个记录一下当初的想法bash
sqlite> INSERT INTO users ( name, niki_name, password ) ...> VALUES ...> ( '川川川川普', '123123aweq', '123qseasd' ),('张三','昵称','他的密码')ON CONFLICT ( NAME ) DO ...> UPDATE ...> --这个exclude能够将它理解为这些values存在的一张临时表名字叫excluded 当name冲突的时候 ...> SET niki_name = excluded.niki_name, ...> password = excluded.password; sqlite> select * from users ...> ; 1|张三|昵称|他的密码 2|李四|1232asdksdk|asdhzcbjkgxkhjjga 3|王五|123awsdhjkzxhckjag|12379asdghkjashdkj 4|赵柳|123ews99p|123askdlhklsdah 5|田七|123skizdyhasd-|zsdfkhzzjfhashl 6|网吧|23907sadhyash|234907adshashdkl 7|川川川川普|123123aweq|123qseasd
肯定了sql以后就是该考虑怎么将它改写成python代码了
既然sql已经肯定下来了,最容易想到的天然就是直接执行sql语句了session
方法一:将数据拼接,而后直接执行sql
我放弃了这个方法,由于不知道values后面的值怎么格式化进去.再加上学习一下sqlalchemy的内容,这个就没去考虑了.
下面是伪代码,也没试试看,晚点再试试.
app
sql_com = text(''' INSERT INTO users ( name, niki_name, password ) VALUES :values ON CONFLICT ( NAME ) DO UPDATE SET niki_name = excluded.niki_name, password = excluded.password; ''') sess.excute(sql_com,{'values':values})
方法二:使用sqlalchemy提供的compiler将它自带的方法语句给添加进去on conflict内容
固然,一开始也走了不少弯路,好比说在使用PostgreSQL的时候他是有对应的方法能够直接调用,我也开心的去尝试了,对于sqlite3 不行,或许是我还不够了解.
代码以下 原文连接https://stackoverflow.com/questions/7165998/how-to-do-an-upsert-with-sqlalchemy
from sqlalchemy.dialects.postgresql import insert stmt = insert(my_table).values(user_email='a@b.com', data='inserted data') stmt = stmt.on_conflict_do_update( index_elements=[my_table.c.user_email], index_where=my_table.c.user_email.like('%@gmail.com'), set_=dict(data=stmt.excluded.data) ) conn.execute(stmt)
一样的对于mysql也是有他对应的方法 好像是叫作on_duplicate_key的样子.这个不适配与sqlite
既然没有现成的办法,那就改写它原有的办法,顺着这个思路去搜索并看了官方文档.找到了compiler的写法
# 代码的意思很简单 就是将sqlalchemy表达式的Insert语法翻译成sql语句的时候在后面加入一个append_string from sqlalchemy.ext.compiler import compiles from sqlalchemy.sql.expression import Insert @compiles(Insert) def append_string(insert, compiler, **kw): s = compiler.visit_insert(insert, **kw) if 'append_string' in insert.kwargs: return s + " " + insert.kwargs['append_string'] return s
再而后就是使用咱们的新的insert方法去插入数据试试看
def upsert_test(): # 这个写法主要是为了防止后面作出修改后 多个地方须要进行修改,这样作能够减小重复工做量 coldefs = Users.__table__.c values = [ {coldefs.name.name: "张三", coldefs.niki_name.name: "nkiname1", coldefs.password.name: "12312312"}, {coldefs.name.name: "李四", coldefs.niki_name.name: "1232asdksdk", coldefs.password.name: "asdhzcbjkgxkhjjga"}, ] sess.execute( Users.__table__.insert(append_string='ON CONFLICT(name) do UPDATE' 'SET niki_name = excluded.niki_name,password = excluded.password;'), values ) # 值得一提的是这个commit.主要是由于sessionmaker(autocommit=False, # autoflush=False, bind=engine) # 将这个autocommit指定为True就会自动提交了 sess.commit() if __name__ == "__main__": upsert_test()
提交后的sqlalchemy输出日志,哦,要想输出 echo=True就好
engine = create_engine(sqlite_engine, connect_args={"check_same_thread": False}, echo=True)
日志内容:
2020-08-08 10:46:04,032 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 2020-08-08 10:46:04,032 INFO sqlalchemy.engine.base.Engine () 2020-08-08 10:46:04,033 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 2020-08-08 10:46:04,033 INFO sqlalchemy.engine.base.Engine () 2020-08-08 10:46:04,034 INFO sqlalchemy.engine.base.Engine PRAGMA main.table_info("users") 2020-08-08 10:46:04,044 INFO sqlalchemy.engine.base.Engine () D:\SoftWare\Anaconda3\envs\blog\lib\site-packages\sqlalchemy\sql\base.py:302: SAWarning: Can't validate argument 'append_string'; can't locate any SQLAlchemy dialect named 'append' % (k, dialect_name) 2020-08-08 10:46:04,118 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, niki_name, password) VALUES (?, ?, ?) ON CONFLICT(name) do UPDATE SET niki_name = excluded.niki_name,password = excluded.password; 2020-08-08 10:46:04,118 INFO sqlalchemy.engine.base.Engine (('张三', 'nkiname1', '12312312'), ('李四', '1232asdksdk', 'asdhzcbjkgxkhjjga')) 2020-08-08 10:46:04,119 INFO sqlalchemy.engine.base.Engine COMMIT
查看数据库结果:
我一看发现没有改变??惊了!!!开发经常使用语之我昨天试了还好好的,怎么如今就这样了呢?
出问题了总得排错…
日志说明已经提交,代码没问题,那就是数据库问题了
将数据库删掉从新创建.
问题出现了,他创建的不是在当前文件夹下???跑到了windows用户文件夹下创建了数据库.但是昨天在另外一台也是windows的电脑上是正常的创建,这就很让人费解了
选择妥协,改写成使用os模块的绝对路径方式
import os db_file = os.path.join( os.path.dirname(__file__), 'test_data.db' ) sqlite_engine = f'sqlite:///{db_file}'
成功修改
1 张三 nkiname1 12312312 2 李四 1232asdksdk asdhzcbjkgxkhjjga 3 王五 123awsdhjkzxhckjag 12379asdghkjashdkj 4 赵柳 123ews99p 123askdlhklsdah 5 田七 123skizdyhasd- zsdfkhzzjfhashl 6 网吧 23907sadhyash 234907adshashdkl 7 川川川川普 123123aweq 123qseasd