sqlalchemy之sqlite3之ON CONFLICT DO UPDATE(insert if exists else update或upsert)

最近开发过程当中接到的一个需求,将一堆数据插入到已有数据表中,若是存在则更新,不存在则新增

接到需求想到的第一个想法是去判断,判断其中某个惟一字段是否已经存在在表里了.存在了就使用更新语法,不然使用插入语法.
伪代码
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

在查找的过程当中还看到一个merge的方法.后面再继续研究,虽然本次的方法比较繁琐,不过在寻找解决方案的过程当中也是学到了不少.还需继续努力,越学以为本身越菜,操做愈来愈下饭.加油加油.

相关文章
相关标签/搜索