Python操作MySQL之SQLAlchemy
一 概述
SQLAlchemy是Python编程语言下的一款ORM框架,该框架建立在数据库API之上,使用关系对象映射进行数据库操作,简单来说即是:将对象转换成SQL,然后使用数据API执行SQL并获取执行结果。
类似于如下关系:
- Python中的类 == 数据库中的表
- Python中类的属性 == 表中的字段
- python中类的实例 == 表中的行
ORM英文全称Object Relational Mapping,翻译过来即为对象映射关系。简单来说,对于Python这种面向对象的编程语言来说一切皆对象,但我们使用的数据库却是关系型的,为了保证使用的一致性,通过ORM将编程语言的对象模型和数据库的关系模型建立映射关系,这样我们在使用编程语言对数据库进行操作的时候,就可以直接使用编程语言的对象模型进行操作,而不用直接使用SQL语言。 除了本文学习到的 SQLAlchemy外,Python中常见的ORM库还有SQLObject、Storm以及Django中的ORM等。
1.1 安装
pip3 install sqlalchemy
1.2 架构与流程
SQLAlchemy主要有两个组件:SQLAlchemy ORM 和 SQLAlchemy Core 。
流程说明:
1、使用者通过ORM对象提交命令
2、将命令交给SQLAlchemy Core(Schema/Types SQL Expression Language)转换成SQL
3、使用 Engine/ConnectionPooling/Dialect 进行数据库操作
- 3.1 匹配使用者事先配置好的egine
- 3.2 egine从连接池中取出一个链接
- 3.3 基于该链接通过Dialect调用DB API,将SQL转交给它去执行
上述流程分析,可以大致分为两个阶段:
第一个阶段(流程1-2):将SQLAlchemy的对象换成可执行的sql语句
第二个阶段(流程3):将sql语句交给数据库执行
如果我们不依赖于SQLAlchemy的转换,而是自己写好sql语句,那是不是意味着可以直接从第二个阶段开始执行了,事实上正是如此,我们完全可以只用SQLAlchemy执行纯sql语句,实例参照2.2。
1.3 DB API
SQLAlchemy本身无法操作数据库,其必须用pymysql/mysqldb等第三方库,Dialect用于和DBAPI进行交流,根据配置文件的不同调用不同的数据库API,从而实现对数据库的操作,如:
# 1、MySQL-Python mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname> # 2、pymysql mysql+pymysql://<username>:<password>@<host>/<dbname>[?<options>] # 3、MySQL-Connector mysql+mysqlconnector://<user>:<password>@<host>[:<port>]/<dbname> # 4、cx_Oracle oracle+cx_oracle://user:pass@host:port/dbname[?key=value&key=value...]
更多可参考:http://docs.sqlalchemy.org/en/latest/dialects/index.html
二 执行原生SQL
在1.2中我们说到,我们完全可以只用SQLAlchemy执行纯sql语句SQLAlchemy,它内部使用 Engine/ConnectionPooling/Dialect 进行数据库操作,Engine使用ConnectionPooling连接数据库,然后再通过Dialect执行SQL语句。
2.1 创建连接
所有的数据库操作,我们都需,先创建一个连接,告诉我们的代码,我们需要连接的数据库的路径,具体代码如下:
from sqlalchemy import create_engine db_conn_str = "mysql+pymysql://username:password@hostname/tb" engine = create_engine(db_conn_str,max_overflow=5,echo=True) print(engine) # Engine(mysql+pymysql://username:***@hostname/tb)
create_engine方法返回一个Engine实例,只有触发数据库事件发生时Engine实例才真正去连接数据库。
create_engine的可选参数很多, 下面举例说明几个常用的:
max_overflow : 允许连接到数据库的最大连接数; echo: 将其设置为True的时候,会打印所有的状态变化,包括转换的SQL语句。 一般情况下,将其设置为Flase的; encoding:设置字符编码
2.2 一个实例
#!/usr/bin/env python # -*- coding:utf-8 -*- from sqlalchemy import create_engine # 1. 准备 # 需要事先安装好pymysql # 需要事先创建好数据库:create database sqlatest charset utf8; # 2. 创建引擎 engine = create_engine('mysql+mysqlconnector://root@127.0.0.1/sqlatest?charset=utf8') # 3. 执行sql # engine.execute( # 'create table if not EXISTS userinfo(' # 'id int(11) not null auto_increment primary key,' # 'username varchar(32) NOT NULL,' # 'password varchar(32) NOT NULL' # ')engine = innodb default charset utf8;') # cur = engine.execute( # "INSERT INTO userinfo (username, password) VALUES ('joe1','123')" # ) # 按关键字传值 # cur = engine.execute( # 'INSERT INTO userinfo (username, password) VALUES(%(username)s,%(password)s);',username='joe2',password='123' # ) # 按位置传值 # cur = engine.execute( # "INSERT INTO userinfo (username, password) VALUES(%s, %s);",[('joe3','123'),('joe4','123'),] # ) # 4. 新插入行的自增id # print(cur.lastrowid) # 5. 查询 # cur = engine.execute('select * from userinfo') # # res1 = cur.fetchone() # 获取一行 # res2 = cur.fetchmany(2) # 获取多行 # res3 = cur.fetchall() # 获取所有行 # print(res1) # print(res2) # print(res3)
注意:执行时会警告1366,如下:
..\pymysql\cursors.py:166: Warning: (1366, "Incorrect string value: '\\xB9\\xA4\\xD7\\xF7\\xC8\\xED...' for column 'VARIABLE_VALUE' at row 7") result = self._query(query)
造成该警告的应该是MySQL驱动的问题,解决方法如下:
第一步:
安装模块:pip3 install mysql-connector-python,本人亲测不建议加上参数--allow-external 直接pip3安装。以下为原文:
由于MySQL服务器以独立的进程运行,并通过网络对外服务,所以,需要支持Python的MySQL驱动来连接到MySQL服务器。MySQL官方提供了mysql-connector-python驱动,但是安装的时候需要给pip命令加上参数--allow-external:
pip install mysql-connector-python--allow-external mysql-connector-python
如果上面的命令安装失败,可以试试另一个驱动:
pip install mysql-connector
第二步:
将连接引擎的:engine = create_engine("mysql+pymysql//user:password@host/{data_base}")
修改为:engine = create_engine("mysql+mysqlconnector//user:password@host/{data_base}",encoding='utf-8')
就是将pymysql连接数据库换成了官方的连接引擎!
三 ORM功能使用
3.1 创建Base
declarative_base类维持了一个从类到表的关系,通常一个应用使用一个base实例,所有实体类都应该继承此类对象。
简单来说,就是调用这个方法, 可以产生一个基类。这个基类和它的子类,可以通过接受到的数据, 映射成一张表。
from sqlalchemy.ext.declarative import declarative_base Base = declarative_base()
3.2 创建表
我们以工厂的一个情况出发,具备以下几种东东:车间,设备,维修组,工程师,利用ORM创建它们,并建立好它们直接的关系,具体如下:
#!/usr/bin/env python # -*- coding:utf-8 -*- from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index # 创建连接,等同于pymysql中的pymysql.connection() db_conn_str = "mysql+mysqlconnector://root:@localhost/sqlatest" engine = create_engine(db_conn_str, max_overflow=5) # 生成一个SQLORM基类 Base = declarative_base() class Workshop(Base): __tablename__ = "workshop" id = Column(Integer, primary_key=True, autoincrement=True) wname = Column(String(32), nullable=False, unique=True, index=True) # 一对一:一个组管理一个车间,一个车间只被一个组管理 class Group(Base): __tablename__ = 'group' id = Column(Integer, primary_key=True, autoincrement=True) gname = Column(String(64), nullable=False, unique=True, index=True) workshop_id = Column(Integer, ForeignKey('workshop.id'), unique=True) # 一对多:一个车间包含多台设备,一台设备只属于某一车间 class Equipment(Base): __tablename__ = 'equipment' id = Column(Integer, primary_key=True, autoincrement=True) ename = Column(String(64), nullable=False, index=True) workshop_id = Column(Integer,ForeignKey('workshop.id')) # ForeignKey建在多的一方 # 多对多:多个工程师可以是同一个组,多个组可以包含同一个工程师 class Engineer(Base): __tablename__ = "engineer" id = Column(Integer, primary_key=True, autoincrement=True) ename = Column(String(32), nullable=False, unique=True, index=True) age = Column(Integer, nullable=True) class Engineer2Group(Base): __tablename__ = "workers2group" id = Column(Integer, primary_key=True, autoincrement=True) eid = Column(Integer, ForeignKey('engineer.id')) # ForeignKey建在多的一方 gid = Column(Integer, ForeignKey('group.id')) # ForeignKey建在多的一方 __table_args__ = ( UniqueConstraint(eid, gid, name='uix_eid_gid'), ) def init_db(): """ 根据类创建数据库表 :return: """ Base.metadata.create_all(engine) def drop_db(): """ 根据类删除数据库表 :return: """ Base.metadata.drop_all(engine) if __name__ == '__main__': init_db()
注:设置外键的另一种方式 ForeignKeyConstraint(['other_id'], ['othertable.other_id'])
元数据(Metadata),又称中介数据、中继数据,为描述数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置、历史数据、资源查找、文件记录等功能。 比如,我们在上面创建表时,需要告诉计算机,我们要创建的表的名字,在哪个数据库,具体有哪些列,怎样进行连接等,这些都为元数据。
注意点:
- 每个类一定要有__tablename__,也就是一定要有表名;
- 至少有一列是主键;
- 类中定义的__repr()方法可选。
对于主键,Oracle没有自增主键,解决方法如下:
from sqlalchemy import Sequence
Column(Integer,Sequence('user_idseq'),prmary_key=True)
Column中常有的参数如下:
primary_key:True 设置此字段为主键; autoincrement:True 表示这个字段是自增的; index:True 设置这个字段为索引; nullable:True 允许次字段为空; unique: True 设置此字段唯一。此字段相同的数据,第一条数据插入后,之后的就不允许再插入了,直接pass掉。
3.3 创建session
from sqlalchemy.orm import sessionmaker Session = sessionmaker(bind=engine) session = Session()
从字面意思来看,sessionmaker为会话制造工厂,是一个可以不断产生新会话的类,Session是真正与数据库通信的handler,Session类将创建新的Session对象绑定到我们的数据库。
当你需要与数据库连接时,实例化一个Session对象即可。
类似于pymysql,我们先要与数据库建立连接(create_engine),然后通过会话来(与pymysql中的游标(cursor)作用类似)建立自己代码与数据库之间的通信。
通信过程剖析
# 建立会话 Session = sessionmaker(bind=engine) session = Session() # 添加数据 session.add(data) # 触发语句 session.commit() # 关闭会话 session.close()
3.4 操作表
#!/usr/bin/env python # -*- coding:utf-8 -*- from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, String, ForeignKey, UniqueConstraint, Index from sqlalchemy.orm import sessionmaker # 创建连接,等同于pymysql中的pymysql.connection() db_conn_str = "mysql+mysqlconnector://root:@localhost/sqlatest" engine = create_engine(db_conn_str, max_overflow=5) # 生成一个SQLORM基类 Base = declarative_base() class Workshop(Base): __tablename__ = "workshop" id = Column(Integer, primary_key=True, autoincrement=True) wname = Column(String(32), nullable=False, unique=True, index=True) # 一对一:一个组管理一个车间,一个车间只被一个组管理 class Group(Base): __tablename__ = 'group' id = Column(Integer, primary_key=True, autoincrement=True) gname = Column(String(64), nullable=False, unique=True, index=True) workshop_id = Column(Integer, ForeignKey('workshop.id'), unique=True) # 一对多:一个车间包含多台设备,一台设备只属于某一车间 class Equipment(Base): __tablename__ = 'equipment' id = Column(Integer, primary_key=True, autoincrement=True) ename = Column(String(64), nullable=False, index=True) workshop_id = Column(Integer,ForeignKey('workshop.id')) # ForeignKey建在多的一方 # 多对多:多个工程师可以是同一个组,多个组可以包含同一个工程师 class Engineer(Base): __tablename__ = "engineer" id = Column(Integer, primary_key=True, autoincrement=True) ename = Column(String(32), nullable=False, unique=True, index=True) age = Column(Integer, nullable=True) class Engineer2Group(Base): __tablename__ = "workers2group" id = Column(Integer, primary_key=True, autoincrement=True) eid = Column(Integer, ForeignKey('engineer.id')) # ForeignKey建在多的一方 gid = Column(Integer, ForeignKey('group.id')) # ForeignKey建在多的一方 __table_args__ = ( UniqueConstraint(eid, gid, name='uix_eid_gid'), ) def init_db(): """ 根据类创建数据库表 :return: """ Base.metadata.create_all(engine) def drop_db(): """ 根据类删除数据库表 :return: """ Base.metadata.drop_all(engine) # 创建会话,相当于pymysql中的游标 Session = sessionmaker(bind=engine) session = Session()
增
# 添加单行数据 # add_data = Engineer(ename="技师1", age=20) # add_data实则为Engineer的一个对象,即是数据表中的一行数据 # session.add(add_data) # 添加多行数据 add_datas = [ Engineer(ename="技师2", age=24), Engineer(ename="技师3", age=24), Engineer(ename="技师4", age=24), ] session.add_all(add_datas) session.commit() session.close()
删
session.query(Engineer).filter(Engineer.age == 24).delete()
session.commit()
session.close()
改
session.query(Engineer).filter(Engineer.id == 2).update({"age" : 30})
session.query(Engineer).filter(Engineer.id > 2).update({Engineer.ename: Engineer.ename + "号"}, synchronize_session=False)
session.query(Engineer).filter(Engineer.age == 20).update({"age": Engineer.age + 3}, synchronize_session="evaluate")
session.commit()
session.close()
synchronize_session用于query在进行delete or update操作时,对session的同步策略: •False -- 不对session进行同步,直接进行delete or update操作; •'fetch' -- 在delete or update操作之前,先发一条sql到数据库获取符合条件的记录;在delete or update操作之后,将session的identity_map与前一步获取到的记录进行match,符合条件的就从session中删掉或更新。 •'evaluate' -- 在delete or update操作之前,用query中的条件直接对session的identity_map中的objects进行eval操作,将符合条件的记录下来; 在delete or update操作之后,将符合条件的记录删除或更新。
synchronize_session更多可参考:https://www.cnblogs.com/rednada/p/5520896.html
查
前面已经用到了,在Session上使用query()方法,创建了一个Query对象。此函数接受数目可变的参数,可以是任意组合的类和类表的描述符。
- query.all():返回列表
- query.first():返回第一个元素
- query.one():有且只有一个元素时才正确返回。
# 查询Engineer的数据
ress = session.query(Engineer).all()
for res in ress:
print(res.id,res.ename,res.age)
filter_by()与filter()
filter_by()接收的参数形式是关键字参数,而filter接收的参数是更加灵活的SQL表达式结构。
# ress = session.query(Engineer).filter_by(age=23).all()
ress = session.query(Engineer).filter(Engineer.age==23).all()
for res in ress:
print(res.id,res.ename,res.age)
3.5 其他查询相关
3.5.1 准备表和数据
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column,Integer,String,ForeignKey from sqlalchemy.orm import sessionmaker egine = create_engine('mysql+mysqlconnector://root@127.0.0.1:3306/sqlatest?charset=utf8', max_overflow=5) Base = declarative_base() # 一对多:一个部门可以有多个员工,员工不能属于多个部门 class Department(Base): __tablename__ = 'department' id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) class Staff(Base): __tablename__ = 'staff' id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id')) def init_db(): Base.metadata.create_all(egine) def drop_db(): Base.metadata.drop_all(egine) drop_db() init_db() Session = sessionmaker(bind=egine) session = Session() # 准备数据 session.add_all([ Department(dname='设备'), Department(dname='销售'), Department(dname='技术'), Department(dname='人事'), ]) session.add_all([ Staff(sname='Joe1', dep_id=1), Staff(sname='Joe2', dep_id=2), Staff(sname='Joe3', dep_id=3), Staff(sname='Joe4', dep_id=4), Staff(sname='Jack', dep_id=1), Staff(sname='Tracy', dep_id=1), Staff(sname='Kobe', dep_id=3), Staff(sname='James', dep_id=4), ]) session.commit() session.close()
3.5.2 条件、通配符、limit、排序、分组、连表、组合
# 1. 条件 # sql = session.query(Staff).filter_by(sname='Joe1') # filter_by只能传参数:什么等于什么 # res = sql.all() # sql语句的执行结果 # and/or # res = session.query(Staff).filter(Staff.id>0, Staff.sname == 'Joe1').all() # filter内传的是表达式,逗号分隔,默认为and # res = session.query(Staff).filter(Staff.id.between(1,3), Staff.sname == 'Joe1').all() # in/not in # res = session.query(Staff).filter(Staff.id.in_([1,3,5,7]), Staff.sname == 'Joe1').all() # res = session.query(Staff).filter(~Staff.id.in_([1,3,5,7]), Staff.sname == 'Joe1').all() # ~代表取反,转换成sql就是关键字not # and_/or_ # from sqlalchemy import and_,or_ # res = session.query(Staff).filter(and_(Staff.id > 0, Staff.sname=='Joe1')).all() # res = session.query(Staff).filter(or_(Staff.id < 2, Staff.sname=='Joe1')).all() # res = session.query(Staff).filter( # or_( # Staff.dep_id == 3, # and_(Staff.id < 1, Staff.sname != 'Joe1'), # Staff.sname == 'James' # ) # ).all() # null/not null # res = session.query(Staff).filter(Staff.sname == None).all() # res = session.query(Staff).filter(Staff.sname != None).all() # 2. 通配符like # res = session.query(Staff).filter(Staff.sname.like('%Joe%')).all() # res = session.query(Staff).filter(~Staff.sname.like('%Joe%')).all() # 3. 限制limit # res = session.query(Staff)[0:5:2] # 4. 排序order_by # res = session.query(Staff).order_by(Staff.dep_id.desc()).all() # res = session.query(Staff).order_by(Staff.dep_id.desc(), Staff.id.asc()).all() # 5. 分组group_by from sqlalchemy.sql import func # res = session.query(Staff.dep_id).group_by(Staff.dep_id).all() # res = session.query( # func.max(Staff.dep_id), # func.min(Staff.dep_id), # func.sum(Staff.dep_id), # func.avg(Staff.dep_id), # func.count(Staff.dep_id), # ).group_by(Staff.dep_id).all() # res = session.query( # Staff.dep_id, # func.count(), # ).group_by(Staff.dep_id).having(func.count() > 1).all() # 6. 连表 # 笛卡尔积模式,调取结果是需要加下标 # res = session.query(Staff, Department).all() # select * from staff,department; # where条件 # res = session.query(Staff, Department).filter(Staff.dep_id == Department.id).all() # for row in res: # Staff_tb = row[0] # Department_tb = row[1] # print(Staff_tb.id, Staff_tb.sname, Department_tb.id, Department_tb.dname) # 内连接 # res = session.query(Staff).join(Department) # join默认为内连接,SQLAlchemy会自动帮我们通过foreign key字段去找关联关系 # 但是上述查询的结果均为Staff表的字段,这样链表还有毛线意义,于是我们修改为 # res = session.query(Staff.id, Staff.sname, Staff.dep_id, Department.dname).join(Department).all() # 左连接:isouter=True;右连接同左连接,只是把两个表的位置换一下 # res = session.query(Staff.id, Staff.sname, Staff.dep_id, Department.dname).join(Department,isouter=True).all() # 7.组合 q1 = session.query(Staff.id, Staff.sname).filter(Staff.id > 0,Staff.id < 3) q2 = session.query(Staff.id, Staff.sname).filter( or_( Staff.sname.like('%Joe%'), Staff.sname.like('%T%'), ) ) res1 = q1.union(q2) # 组合+去重 res2 = q1.union_all(q2) # 组合,不去重 print([i.sname for i in q1.all()]) # ['Joe1', 'Joe2', 'Joe3', 'Joe4'] print([i.sname for i in q2.all()]) # ['Joe1', 'Joe2', 'Joe3', 'Joe4', 'Tracy'] print([i.sname for i in res1.all()]) # ['Joe1', 'Joe2', 'Joe3', 'Joe4', 'Tracy'] print([i.sname for i in res2.all()]) # ['Joe1', 'Joe2', 'Joe3', 'Joe4', 'Joe1', 'Joe2', 'Joe3', 'Joe4', 'Tracy']
3.5.3 子查询
有三种形式的子查询,注意:子查询的sql必须用括号包起来,尤其在形式三中需要注意这一点
# 示例:查出id大于2的员工,当做子查询的表使用
# 原生SQL:
# select * from (select * from staff where id > 2) as A;
# ORM:
res = session.query(
session.query(Staff).filter(Staff.id > 2).subquery()
).all()
# 示例:查出销设备部门的员工姓名
# 原生SQL:
# select sname from staff where dep_id in (select id from department where dname='设备');
# ORM:
res = session.query(Staff.sname).filter(Staff.dep_id.in_(
# session.query(Department.id).filter_by(dname = '设备') # 传的是参数
session.query(Department.id).filter(Department.dname == '设备') # 传的是表达式
)).all()
# 示例:查询所有的员工姓名与部门名 # 原生SQL: # select sname as 员工姓名,(select dname from department where id = staff.dep_id) as 部门名 from staff; # ORM: sub_sql = session.query(Department.dname).filter(Department.id == Staff.dep_id) # SELECT department.dname FROM department, staff WHERE department.id = staff.dep_id; sub_sql.as_scalar() # as_scalar的功能就是把上面的sub_sql加上了括号 res = session.query(Staff.sname, sub_sql.as_scalar()).all()
四 relationship
如新建表格一样,SQLAlchemy中的映射关系有四种,分别是多对一,一对多,一对一,多对多。使用relationship与生成表结构无关,仅用于查询方便
4.1 多对一与一对多
因为外键(ForeignKey)始终定义在多的一方。如果relationship定义在多的一方,那就是多对一,一对多与多对一的区别在于其关联(relationship)的属性在多的一方还是一的一方,如果relationship定义在一的一方那就是一对多。
class Department(Base): __tablename__ = "department" id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) class Staff(Base): __tablename__ = 'staff' # ForeignKey建在多的一方 id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id')) # relationship建在多的一方 dep = relationship("Department")
class Department(Base): __tablename__ = "department" id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) # relationship建在一的一方 staf = relationship("Staff") class Staff(Base): __tablename__ = 'staff' # ForeignKey建在多的一方 id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id'))
为了建立双向关系,可以在relationship()中设置backref(使用方法:backref=“指定名称”),比如在多对一的代码中,插入backref,department对象就有staff属性。
department = relationship("Department", backref = "staf")
同时,如果设置 cascade= ‘all’,可以级联删除。如果不设置cascade,删除department时,其关联的staff不会删除,只会把staff关联的staff.dep_id设置为空,设置cascade后就可以级联删除staff。
dep = relationship("Department", cascade="all", backref = "staf")
4.2 一对一
一对一就是多对一和一对多的一个特例,只需在relationship加上一个参数uselist=False替换多的一端就是一对一。
class Department(Base): __tablename__ = "department" id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) class Staff(Base): # ForeignKey建在多的一方 __tablename__ = 'staff' id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id')) # relationship建在多的一方 dep = relationship("Department", backref ="staff", uselist=False)
class Department(Base): __tablename__ = "department" id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) # relationship建在一的一方 staf = relationship("Staff", uselist=False, backref = "department") class Staff(Base): __tablename__ = 'staff' # ForeignKey建在多的一方 id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id'))
4.3 对多对
多对多需要一个中间关联表,通过参数secondary指定中间关联表来建立。
class Staff(Base): __tablename__ = 'staff' id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id')) ser_id = Column(Integer, ForeignKey("province.id")) class Department(Base): __tablename__ = "department" id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) pro = relationship("Server", secondary=Staff, backref="department") class Province(Base): __tablename__ = 'province' id = Column(Integer, primary_key=True, autoincrement=True) pname = Column(String(64), unique=True, nullable=False)
五 正查、反查
5.1 表修改
from sqlalchemy import create_engine from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column,Integer,String,ForeignKey from sqlalchemy.orm import sessionmaker,relationship egine = create_engine('mysql+mysqlconnector://root@127.0.0.1:3306/test1?charset=utf8', max_overflow=5) Base = declarative_base() # 一对多:一个部门可以有多个员工,员工不能属于多个部门 class Department(Base): __tablename__ = 'department' id = Column(Integer, primary_key=True, autoincrement=True) dname = Column(String(64), nullable=False, index=True) class Staff(Base): __tablename__ = 'staff' id = Column(Integer, primary_key=True, autoincrement=True) sname = Column(String(32), nullable=False, index=True) dep_id = Column(Integer, ForeignKey('department.id')) # 在ForeignKey所在的类内添加relationship的字段,注意: # 1:Department是类名 # 2:depart字段不会再数据库表中生成字段 # 3:depart用于Staff表查询Department表(正向查询),而xxoo用于Department表查询Staff表(反向查询) depart = relationship('Department', backref='xxoo') def init_db(): Base.metadata.create_all(egine) def drop_db(): Base.metadata.drop_all(egine) drop_db() init_db() Session = sessionmaker(bind=egine) session = Session() # 准备数据 session.add_all([ Department(dname='设备'), Department(dname='销售'), Department(dname='技术'), Department(dname='人事'), ]) session.add_all([ Staff(sname='Joe1', dep_id=1), Staff(sname='Joe2', dep_id=2), Staff(sname='Joe3', dep_id=3), Staff(sname='Joe4', dep_id=4), Staff(sname='Jack', dep_id=1), Staff(sname='Tracy', dep_id=1), Staff(sname='Kobe', dep_id=3), Staff(sname='James', dep_id=4), ]) session.commit() session.close()
5.2 标准连表查询
# 示例:查询员工名与其部门名 res = session.query(Staff.sname, Department.dname).join(Department) # 迭代器 for row in res: print(row[0],row[1])
5.3 基于relationship的正查、反查
# SQLAlchemy的relationship在内部帮我们做好表的链接
# 查询员工名与其部门名(正向查)
res = session.query(Staff)
for row in res:
print(row.id, row.sname, row.depart.dname)
# 查询部门名以及该部门下的员工(反向查)
res = session.query(Department)
for row in res:
# print(row.dname, row.xxoo)
print(row.dname,[r.sname for r in row.xxoo])
六 其他补充
由于Session工作在一个事务内,我们可以回滚所做过的更改。
session.rollback()


浙公网安备 33010602011771号