sqlalchemy是python下一个著名的数据库orm库,可以方便地进行数据表创建、数据增删改查等操作

最详细的教程,见官方:https://docs.sqlalchemy.org

这里列举一些常用操作:

一、创建数据表

代码以及相关的注释:

import datetime
import sqlalchemy as db
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base # 为了使用MySQL支持的类型,如unsigned bigint
import sqlalchemy.dialects.mysql as mysql # 创建对象的基类:
Base = declarative_base()
# 定义User对象, 尽量展示一些创建选项:
class User(Base):
# 表的名字:
__tablename__ = 't_user'
# 创建表的扩展设置,这里设置表的字符集为utf8
__table_args__ = (
# 联合唯一索引
db.UniqueConstraint('id', 'name', name='uix_id_name'), # 联合索引
db.Index('sex_createtime', 'sex', 'createtime'), # 设置引擎和字符集,注意:这个map只能放到元组的最后,否则会报错
{
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8',
}
) # 表的结构字段定义
# id字段,类型为unsigned bigint,定义为主键,自增,db.BigInteger不支持unsigned,所以使用mysql特别支持的类型
# id = db.Column(db.BigInteger(), primary_key=True, autoincrement=True)
id = db.Column(mysql.BIGINT(unsigned=True), primary_key=True, autoincrement=True)
# name字段,定义为varchar(20),默认允许空,带注释
name = db.Column(db.String(20), comment='姓名')
# phone字段,定义为varchar(11),不允许空,有唯一限制,带注释
phone = db.Column(db.String(11), nullable=False, unique=True, comment='电话')
# score, float类型,带索引, 允许空
score = db.Column(db.Float, index=True, comment='成绩')
# sex,性别, int类型, 默认值为1
sex = db.Column(db.Integer, default=1, comment='性别,1-男;2-女')
# createtime, datetime类型, 不允许空
createtime = db.Column(db.DateTime, nullable=False, comment='创建时间')
# modifytime, datetime类型, 带索引, 默认使用datetime.now()生成当前时间,注意,不带()
modifytime = db.Column(db.DateTime, default=datetime.datetime.now, comment='修改时间') # 只是用于打印对象
def __str__(self):
return "(" + ', '.join(['%s:%s' % item for item in self.__dict__.items()]) + ")" # 初始化数据库连接:
# 连接字符串模式:数据库类型+连接库+用户名+密码+主机,字符编码,是否打印建表细节
# 其中,连接库是当前用于操作数据库的库,对于python2.7,一般使用MysqlDb,对于Python3,一般使用pymysql
# 连接的例子如:create_engine("mysql+pymysql://cai:123@localhost/test?charset=utf8", echo=True)
engine = db.create_engine('mysql+pymysql://user:pass@localhost:3306/test') # 删除现有的表,谨慎决定是否需要这样操作
Base.metadata.drop_all(engine) # 创建表
Base.metadata.create_all(engine)

在mysql中生成的表结构如下:

CREATE TABLE `t_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL COMMENT '姓名',
`phone` varchar(11) NOT NULL COMMENT '电话',
`score` float DEFAULT NULL COMMENT '成绩',
`sex` int(11) DEFAULT NULL COMMENT '性别,1-男;2-女',
`createtime` datetime NOT NULL COMMENT '创建时间',
`modifytime` datetime DEFAULT NULL COMMENT '修改时间',
PRIMARY KEY (`id`),
UNIQUE KEY `phone` (`phone`),
UNIQUE KEY `uix_id_name` (`id`,`name`),
KEY `sex_createtime` (`sex`,`createtime`),
KEY `ix_t_user_score` (`score`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

最常用的SQLAlchemy Column类型

类型名 Python类型 说 明
Integer int 普通整数,一般是 32 位
SmallInteger int 取值范围小的整数,一般是 16 位
BigInteger int 或 long 不限制精度的整数
Float float 浮点数
Numeric decimal.Decimal 定点数
String str 变长字符串
Text str 变长字符串,对较长或不限长度的字符串做了优化
Unicode unicode 变长 Unicode 字符串
UnicodeText unicode 变长 Unicode 字符串,对较长或不限长度的字符串做了优化
Boolean bool 布尔值
Date datetime.date 日期
Time datetime.time 时间
DateTime datetime.datetime 日期和时间
Interval datetime.timedelta 时间间隔
Enum str 一组字符串
PickleType 任何 Python 对象 自动使用 Pickle 序列化
LargeBinary str 二进制文件

最常使用的SQLAlchemy列选项

选项名 说 明
primary_key 如果设为 True ,这列就是表的主键
unique 如果设为 True ,这列不允许出现重复的值
index 如果设为 True ,为这列创建索引,提升查询效率
nullable 如果设为 True ,这列允许使用空值;如果设为 False ,这列不允许使用空值
default 为这列定义默认值

mysql的特别定义列类型选项

BIGINT, BINARY, BIT, BLOB, BOOLEAN, CHAR, DATE,
DATETIME, DECIMAL, DECIMAL, DOUBLE, ENUM, FLOAT, INTEGER,
LONGBLOB, LONGTEXT, MEDIUMBLOB, MEDIUMINT, MEDIUMTEXT, NCHAR,
NUMERIC, NVARCHAR, REAL, SET, SMALLINT, TEXT, TIME, TIMESTAMP,
TINYBLOB, TINYINT, TINYTEXT, VARBINARY, VARCHAR, YEAR

二、数据的增删改查

对数据操作之前,需要定义一个会话类型,并在操作数据时,生成一个会话实例进行操作

# 定义一个会话类型,用来进行数据操作(engine是前面步骤生成的引擎对象)
DBSession = sessionmaker(bind=engine)
#实例一个会话
session = DBSession()
# 数据的操作,如session.add(..)
# 这一句不能少,否则不会提交
session.commit()
session.close()

2.1 新增数据

# 插入一条数据
user1 = User(id=1,name="小明", phone="", score=98.2, sex=1, createtime=datetime.datetime.now())
session.add(user1) # 插入多条数据
users = [
User(id=2,name="小芳", phone="", score=83.1, sex=2, createtime=datetime.datetime.now()),
User(id=3,name="小李", phone="", score=100, sex=2, createtime=datetime.datetime.now()),
User(id=4,name="小牛", phone="", score=62.5, sex=1, createtime=datetime.datetime.now()),
] session.add_all(users) # 非orm方式,特点是快,可定制,如支持ON DUPLICATE KEY UPDATE
session.execute(User.__table__.insert(),
[
{"id":5,"name":"小五", "phone":"", "score":72.5, "sex":1, "createtime":datetime.datetime.now()},
]
)

如何实现ON DUPLICATE KEY UPDATE?

sqlalchemy不支持ON DUPLICATE KEY UPDATE, 可以自己实现一个。

基本的实现方式:

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 my_connection.execute(my_table.insert(append_string = 'ON DUPLICATE KEY UPDATE foo=foo'), my_values)

在实际使用中,这种使用方式显得比较粗糙,一般来说,可以枚举对象的字段,并对每个字段设置xxx=VALUES(xxx)进行更新。

2.2 查询、更新和删除

三者的操作直接合并来说了

query = session.query(User)
print query # 显示SQL 语句
print query.statement # 同上
for user in query: # 遍历时查询
print user.name
print query.all() # 返回的是一个类似列表的对象
print query.first().name # 记录不存在时,first() 会返回 None
# print query.one().name # 不存在,或有多行记录时会抛出异常
print query.filter(User.id == 2).first().name
print query.get(2).name # 以主键获取,等效于上句
print query.filter('id = 2').first().name # 支持字符串 query2 = session.query(User.name)
print query2.all() # 每行是个元组
print query2.limit(1).all() # 最多返回 1 条记录
print query2.offset(1).all() # 从第 2 条记录开始返回
print query2.order_by(User.name).all()
print query2.order_by('name').all()
print query2.order_by(User.name.desc()).all()
print query2.order_by('name desc').all()
print session.query(User.id).order_by(User.name.desc(), User.id).all() print query2.filter(User.id == 1).scalar() # 如果有记录,返回第一条记录的第一个元素
print session.query('id').select_from(User).filter('id = 1').scalar()
print query2.filter(User.id > 1, User.name != 'a').scalar() # and
query3 = query2.filter(User.id > 1) # 多次拼接的 filter 也是 and
query3 = query3.filter(User.name != 'a')
print query3.scalar()
print query2.filter(or_(User.id == 1, User.id == 2)).all() # or
print query2.filter(User.id.in_((1, 2))).all() # in query4 = session.query(User.id)
print query4.filter(User.name == None).scalar()
print query4.filter('name is null').scalar()
print query4.filter(not_(User.name == None)).all() # not
print query4.filter(User.name != None).all() print query4.count()
print session.query(func.count('*')).select_from(User).scalar()
print session.query(func.count('')).select_from(User).scalar()
print session.query(func.count(User.id)).scalar()
print session.query(func.count('*')).filter(User.id > 0).scalar() # filter() 中包含 User,因此不需要指定表
print session.query(func.count('*')).filter(User.name == 'a').limit(1).scalar() == 1 # 可以用 limit() 限制 count() 的返回数
print session.query(func.sum(User.id)).scalar()
print session.query(func.now()).scalar() # func 后可以跟任意函数名,只要该数据库支持
print session.query(func.current_timestamp()).scalar()
print session.query(func.md5(User.name)).filter(User.id == 1).scalar() query.filter(User.id == 1).update({User.name: 'c'})
user = query.get(1)
print user.name user.name = 'd'
session.flush() # 写数据库,但并不提交
print query.get(1).name session.delete(user)
session.flush()
print query.get(1) session.rollback()
print query.get(1).name
query.filter(User.id == 1).delete()
session.commit()
print query.get(1)

三、一些进阶操作

如何批量插入大批数据?
可以使用非 ORM 的方式:

session.execute(
User.__table__.insert(),
[{'name': `randint(1, 100)`,'age': randint(1, 100)} for i in xrange(10000)]
)
session.commit()

上面我批量插入了 10000 条记录,半秒内就执行完了;而 ORM 方式会花掉很长时间。

如何让执行的 SQL 语句增加前缀?

使用 query 对象的 prefix_with() 方法:

session.query(User.name).prefix_with('HIGH_PRIORITY').all()
session.execute(User.__table__.insert().prefix_with('IGNORE'), {'id': 1, 'name': ''})

如何替换一个已有主键的记录?
使用 session.merge() 方法替代 session.add(),其实就是 SELECT + UPDATE:

user = User(id=1, name='ooxx')
session.merge(user)
session.commit()

或者使用 MySQL 的 INSERT … ON DUPLICATE KEY UPDATE,需要用到 @compiles 装饰器,有点难懂,自己看吧:《SQLAlchemy ON DUPLICATE KEY UPDATE》 和 sqlalchemy_mysql_ext

如何使用无符号整数?
可以使用 MySQL 的方言:

如何使用无符号整数?
可以使用 MySQL 的方言:

模型的属性名需要和表的字段名不一样怎么办?
开发时遇到过一个奇怪的需求,有个其他系统的表里包含了一个“from”字段,这在 Python 里是关键字,于是只能这样处理了:

from_ = Column('from', CHAR(10))

如何获取字段的长度?
Column 会生成一个很复杂的对象,想获取长度比较麻烦,这里以 User.name 为例:

User.name.property.columns[0].type.length

如何指定使用 InnoDB,以及使用 UTF-8 编码?
最简单的方式就是修改数据库的默认配置。如果非要在代码里指定的话,可以这样:

class User(BaseModel):
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8'
}

MySQL 5.5 开始支持存储 4 字节的 UTF-8 编码的字符了,iOS 里自带的 emoji(如 � 字符)就属于这种。

如果是对表来设置的话,可以把上面代码中的 utf8 改成 utf8mb4,DB_CONNECT_STRING 里的 charset 也这样更改。
如果对库或字段来设置,则还是自己写 SQL 语句比较方便,具体细节可参考《How to support full Unicode in MySQL databases》
不建议全用 utf8mb4 代替 utf8,因为前者更慢,索引会占用更多空间。

如何设置外键约束?

from random import randint
from sqlalchemy import ForeignKey class User(BaseModel):
__tablename__ = 'user' id = Column(Integer, primary_key=True)
age = Column(Integer) class Friendship(BaseModel):
__tablename__ = 'friendship' id = Column(Integer, primary_key=True)
user_id1 = Column(Integer, ForeignKey('user.id'))
user_id2 = Column(Integer, ForeignKey('user.id')) for i in xrange(100):
session.add(User(age=randint(1, 100)))
session.flush() # 或 session.commit(),执行完后,user 对象的 id 属性才可以访问(因为 id 是自增的) for i in xrange(100):
session.add(Friendship(user_id1=randint(1, 100), user_id2=randint(1, 100)))
session.commit() session.query(User).filter(User.age < 50).delete()

执行这段代码时,你应该会遇到一个错误:

sqlalchemy.exc.IntegrityError: (IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`ooxx`.`friendship`, CONSTRAINT `friendship_ibfk_1` FOREIGN KEY (`user_id1`) REFERENCES `user` (`id`))') 'DELETE FROM user WHERE user.age < %s' (50,)

原因是删除 user 表的数据,可能会导致 friendship 的外键不指向一个真实存在的记录。在默认情况下,MySQL 会拒绝这种操作,也就是 RESTRICT。InnoDB 还允许指定 ON DELETE 为 CASCADE 和 SET NULL,前者会删除 friendship 中无效的记录,后者会将这些记录的外键设为 NULL。
除了删除,还有可能更改主键,这也会导致 friendship 的外键失效。于是相应的就有 ON UPDATE 了。其中 CASCADE 变成了更新相应的外键,而不是删除。
而在 SQLAlchemy 中是这样处理的:

class Friendship(BaseModel):
__tablename__ = 'friendship' id = Column(Integer, primary_key=True)
user_id1 = Column(Integer, ForeignKey('user.id', ondelete='CASCADE', onupdate='CASCADE'))
user_id2 = Column(Integer, ForeignKey('user.id', ondelete='CASCADE', onupdate='CASCADE'))

如何连接表?

from sqlalchemy import distinct
from sqlalchemy.orm import aliased Friend = aliased(User, name='Friend') print session.query(User.id).join(Friendship, User.id == Friendship.user_id1).all() # 所有有朋友的用户
print session.query(distinct(User.id)).join(Friendship, User.id == Friendship.user_id1).all() # 所有有朋友的用户(去掉重复的)
print session.query(User.id).join(Friendship, User.id == Friendship.user_id1).distinct().all() # 同上
print session.query(Friendship.user_id2).join(User, User.id == Friendship.user_id1).order_by(Friendship.user_id2).distinct().all() # 所有被别人当成朋友的用户
print session.query(Friendship.user_id2).select_from(User).join(Friendship, User.id == Friendship.user_id1).order_by(Friendship.user_id2).distinct().all() # 同上,join 的方向相反,但因为不是 STRAIGHT_JOIN,所以 MySQL 可以自己选择顺序
print session.query(User.id, Friendship.user_id2).join(Friendship, User.id == Friendship.user_id1).all() # 用户及其朋友
print session.query(User.id, Friendship.user_id2).join(Friendship, User.id == Friendship.user_id1).filter(User.id < 10).all() # id 小于 10 的用户及其朋友
print session.query(User.id, Friend.id).join(Friendship, User.id == Friendship.user_id1).join(Friend, Friend.id == Friendship.user_id2).all() # 两次 join,由于使用到相同的表,因此需要别名
print session.query(User.id, Friendship.user_id2).outerjoin(Friendship, User.id == Friendship.user_id1).all() # 用户及其朋友(无朋友则为 None,使用左连接)

这里我没提到 relationship,虽然它看上去很方便,但需要学习的内容实在太多,还要考虑很多性能上的问题,所以干脆自己 join 吧。

为什么无法删除 in 操作查询出来的记录?

session.query(User).filter(User.id.in_((1, 2, 3))).delete()

抛出这样的异常:

sqlalchemy.exc.InvalidRequestError: Could not evaluate current criteria in Python.  Specify 'fetch' or False for the synchronize_session parameter.

但这样是没问题的:

session.query(User).filter(or_(User.id == 1, User.id == 2, User.id == 3)).delete()

搜了下找到《Sqlalchemy delete subquery》这个问题,提到了 delete 的一个注意点:删除记录时,默认会尝试删除 session 中符合条件的对象,而 in 操作估计还不支持,于是就出错了。解决办法就是删除时不进行同步,然后再让 session 里的所有实体都过期:

session.query(User).filter(User.id.in_((1, 2, 3))).delete(synchronize_session=False)
session.commit() # or session.expire_all()

此外,update 操作也有同样的参数,如果后面立刻提交了,那么加上 synchronize_session=False 参数会更快。

如何对一个字段进行自增操作
最简单的办法就是获取时加上写锁:

user = session.query(User).with_lockmode('update').get(1)
user.age += 1
session.commit()

如果不想多一次读的话,这样写也是可以的:

session.query(User).filter(User.id == 1).update({
User.age: User.age + 1
})
session.commit()
# 其实字段之间也可以做运算:
session.query(User).filter(User.id == 1).update({
User.age: User.age + User.id
})

参考资料:

https://blog.csdn.net/tastelife/article/details/25218895

https://www.cnblogs.com/Oliver.net/p/7345647.html

https://www.cnblogs.com/Orangeorchard/p/8097547.html

https://www.runoob.com/python3/python3-mysql.html

python 使用sqlalchemy进行数据库操作的更多相关文章

  1. python使用SQLAlchemy对mysql操作

    安装SQLAlchemy pip install sqlalchemy 在MySQL的test数据库中创建的user表,用SQLAlchemy来试试 数据库连接 第一步,导入SQLAlchemy,并初 ...

  2. Python数据存储 — MySQL数据库操作

    本地安装MySQL 调试环境python3.6,调试python操作mysql数据库,首先要在本地或服务器安装mysql数据库. 安装参考:https://mp.csdn.net/postedit/8 ...

  3. python工具之myql数据库操作

    import pymysql import config ''' 1.0 简单封装 1.1 添加了insert_id属性,返回insert时返回的主键 1.2 添加了column属性,返回查询的col ...

  4. python中的MySQL数据库操作 连接 插入 查询 更新 操作

    MySQL数据库 就数据库而言,连接之后就要对其操作.但是,目前那个名字叫做qiwsirtest的数据仅仅是空架子,没有什么可操作的,要操作它,就必须在里面建立“表”,什么是数据库的表呢?下面摘抄自维 ...

  5. R和python连接SQL sever 数据库操作

    在R的使用中,为了方便提取数据, 我们经常要进行数据库进行操作,接下来我们尝试使用R进行连接数据. 这里我们使用R中的RODBC进行操作, 首先,我们需要先配置ODBC资源管理器 通过任务管理器或者w ...

  6. Python实战之MySQL数据库操作

    1. 要想使Python可以操作MySQL数据库,首先需要安装MySQL-python包,在CentOS上可以使用一下命令来安装 $ sudo yum install MySQL-python 2. ...

  7. python 包之 redis 数据库操作教程

    一.安装 redis 是一个 Key-Value 数据库 Value 支持 string(字符串),list(列表),set(集合),zset(有序集合),hash(哈希类型)等类型 pip inst ...

  8. python - 接口自动化测试 - MysqlUtil - 数据库操作封装

    # -*- coding:utf-8 -*- ''' @project: ApiAutoTest @author: Jimmy @file: mysql_util.py @ide: PyCharm C ...

  9. Python学习--17 访问数据库

    实际开发中,我们会经常用到数据库. Python里对数据库的操作API都很统一. SQLite SQLite是一种嵌入式数据库,它的数据库就是一个文件.由于SQLite本身是C写的,而且体积很小,所以 ...

随机推荐

  1. OneDrive,在云端

    应用场景 1.一份文档下班后还没编辑好,发送到自己的QQ/微信回家后继续编辑: 2.由于来回拷贝同一份文件,导致版本太多,忘记那个是最新版本了: 3.出门在外,客户突然需要一份重要文档,这份文件放在办 ...

  2. version GLIBCXX3.4.21 not defined in file libstdc++.so.6 with link time reference

    问题:在运行C++程序时,输入 ./a.out 输出:symbol _ZNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEC1Ev, version ...

  3. php怎么识别真实ip

    PHP 里用来获取客户端 IP 的变量有这些: $_SERVER['HTTP_CLIENT_IP'] 这个头是有的,但是很少,不一定服务器都实现了.客户端可以伪造.(推荐学习:PHP编程从入门到精通) ...

  4. Maven的下载以及配置

    Maven的下载以及配置 Maven的两大核心作用: (1)依赖管理:对Jar包的依赖,解决Jar包之间的冲突 (2)项目构建:项目从编译到测试到运行发布 一.Mavenu的下载(现在的eclipse ...

  5. SpringBoot中实现Spring容器中注入类型相同但名不同Bean

    @Bean(autowire = Autowire.BY_NAME,value = "kaptchaProducer") public Producer kaptchaProduc ...

  6. 未能找到 System.Web.Helpers

    This question is a bit old but here's a simple solution - Microsoft seemed to just move this library ...

  7. python 路径拼接

    >>> import os>>> os.path.join('/hello/','good/boy/','doiido')>>>'/hello/g ...

  8. [sdoi 2017]树点涂色

    传送门 Description Bob 有一棵\(n\)个点的有根树,其中\(1\)号点是根节点.Bob 在每个节点上涂了颜色,并且每个点上的颜色不同. 定义一条路径的权值是,这条路径上的点(包括起点 ...

  9. SQL on Hadoop技术综述

    一.系统架构 runtime framework v.s. mpp 在SQL on Hadoop系统中,有两种架构: 1.一种是基于某个运行时框架来构建查询引擎,典型案例是Hive: 2.另一种是仿照 ...

  10. FZU Monthly-201909 获奖名单

    FZU Monthly-201909 获奖名单 冠军: 空缺 一等奖: 张咏真 S031802540 孔铖晗 S031802115 二等奖: 苏锦程 S031802325 林柄灿 S031802117 ...