python 使用sqlalchemy进行数据库操作
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进行数据库操作的更多相关文章
- python使用SQLAlchemy对mysql操作
安装SQLAlchemy pip install sqlalchemy 在MySQL的test数据库中创建的user表,用SQLAlchemy来试试 数据库连接 第一步,导入SQLAlchemy,并初 ...
- Python数据存储 — MySQL数据库操作
本地安装MySQL 调试环境python3.6,调试python操作mysql数据库,首先要在本地或服务器安装mysql数据库. 安装参考:https://mp.csdn.net/postedit/8 ...
- python工具之myql数据库操作
import pymysql import config ''' 1.0 简单封装 1.1 添加了insert_id属性,返回insert时返回的主键 1.2 添加了column属性,返回查询的col ...
- python中的MySQL数据库操作 连接 插入 查询 更新 操作
MySQL数据库 就数据库而言,连接之后就要对其操作.但是,目前那个名字叫做qiwsirtest的数据仅仅是空架子,没有什么可操作的,要操作它,就必须在里面建立“表”,什么是数据库的表呢?下面摘抄自维 ...
- R和python连接SQL sever 数据库操作
在R的使用中,为了方便提取数据, 我们经常要进行数据库进行操作,接下来我们尝试使用R进行连接数据. 这里我们使用R中的RODBC进行操作, 首先,我们需要先配置ODBC资源管理器 通过任务管理器或者w ...
- Python实战之MySQL数据库操作
1. 要想使Python可以操作MySQL数据库,首先需要安装MySQL-python包,在CentOS上可以使用一下命令来安装 $ sudo yum install MySQL-python 2. ...
- python 包之 redis 数据库操作教程
一.安装 redis 是一个 Key-Value 数据库 Value 支持 string(字符串),list(列表),set(集合),zset(有序集合),hash(哈希类型)等类型 pip inst ...
- python - 接口自动化测试 - MysqlUtil - 数据库操作封装
# -*- coding:utf-8 -*- ''' @project: ApiAutoTest @author: Jimmy @file: mysql_util.py @ide: PyCharm C ...
- Python学习--17 访问数据库
实际开发中,我们会经常用到数据库. Python里对数据库的操作API都很统一. SQLite SQLite是一种嵌入式数据库,它的数据库就是一个文件.由于SQLite本身是C写的,而且体积很小,所以 ...
随机推荐
- admin端的专业管理模块功能测试
1.概述 1.1 测试范围 本次所测试的内容是admin端的专业管理模块. 1.2 测试方法 本次测试采用黑盒子方法进行集成测试. 1.3 测试环境 操作系统:Windows 2012 Server ...
- 论文笔记系列-Auto-DeepLab:Hierarchical Neural Architecture Search for Semantic Image Segmentation
Pytorch实现代码:https://github.com/MenghaoGuo/AutoDeeplab 创新点 cell-level and network-level search 以往的NAS ...
- sublime设置代码缩进
打开sublime的首选项(Preferences)下的设置-用户(Setting-User) ,配置如下代码 , "translate_tabs_to_spaces": true ...
- Gradle 知识点
mac 系统中,下载的 Gradle 压缩包解压后存储的文件夹:/Users//.gradle/wrapper/dists 当Gradle运行时,会根据settings.gradle的配置情况,构建一 ...
- Sitemap Error : XML declaration allowed only at the start of the document解决方法
今天ytkah的客户反馈说他的xml网站地图有问题,提示Sitemap Error : XML declaration allowed only at the start of the documen ...
- A* 第k短路
#include <cstdio> #include <algorithm> #include <queue> #include <cstring> # ...
- iOS 逆向工程(工具介绍)- 学习整理(转)
一.class-dump 简介:顾名思义,就是用来导出目标对象的class信息的工具,私有方法声明也能导出来. 原理:利用 Objective-C语言的 runtime 特性,将存 在Mach-O 文 ...
- cc2530的I/O中断
通用I/O的中断 cc2530的CPU有18个中断源,每个中断都可以分别使能和控制. 18个中断源的优先级 18个中断源分为6个组,每一组有3个中断源,中断优先级可以通过配置相应寄存器来实现 中断源的 ...
- Linux安全加固(二)禁止普通用户su到root/设置SSH终端接入白名单/修改history条数
一.禁止普通用户su到root管理员.设置可以su到root的白名单 1.首先看一下正常情况 2.可以看到普通用户使用su root命令,输入密码即可登录到root用户 3.下面开始配置禁止所有普通用 ...
- isopod dsl 框架管理kubernetes 配置
isopod 是一个包含了丰富能力的dsl 框架我们可以不用编写yaml 文件来进行k8s 管理 说明 语法类似python,目前移植内置了一些不错的功能kube 方法 vault 集成,helm 集 ...