利用 sqlalchemy 实现关系表查询功能


下面的例子将完成一个通过关系表进行查询的功能,示例中的数据表均在MySQL中建立,建立过程可以使用 SQL 命令或编写 Python 适配器完成。

示例中用到的表主要有3张,一张personInfo个人信息表,一张account_store账号信息表,以及一张person_account_rel的个人信息与账号关系表。

示例中将会通过已知的人物年龄和id通过个人信息表查出个人姓名(仅为参考示例,请忽略怪异的查找逻辑 :) ),随后根据关系表得到的人物名字所对应的账号id,再根据给定的账号信息筛选出所需的账号密码结果。

完整代码如下

 from sqlalchemy import create_engine, exc, orm
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.schema import Table, ForeignKey, Column
from sqlalchemy.sql.sqltypes import Integer, VARCHAR
from sqlalchemy.dialects.mysql.base import TINYINT
from sqlalchemy.orm import relationship # declarative_base function will return a class which using active record pattern
# It will combine object opeartion and data operation automatically
Base = declarative_base() # This is rel table
t_PersonAccount_Rel = Table('personaccount_rel',
Base.metadata,
Column('name', VARCHAR(8), ForeignKey('person_info.name')),
Column('account_id', Integer, ForeignKey('account_store.account_id'))) # Create table based on Base obj
class PersonInfo(Base):
__table__ = Table('person_info',
Base.metadata,
Column('id', TINYINT(4)),
Column('age', Integer),
Column('name', VARCHAR(8), primary_key=True)) # Need to search via person --> account
# So build up a relationship between person and account
# relationship(class_name, class, class_name)
AccountStore = relationship('AccountStore',
secondary=t_PersonAccount_Rel,
backref='PersonInfo') class AccountStore(Base):
__table__ = Table('account_store',
Base.metadata,
Column('account_id', Integer, primary_key=True),
Column('items', VARCHAR(20)),
Column('account', VARCHAR(50)),
Column('password', VARCHAR(50))) def __repr__(self):
return 'Items: %s\nAccount: %s\nPassword: %s' % (self.items, self.account, self.password) class SqlalchemyActor():
def __init__(self, dsn):
try:
engine = create_engine(dsn, echo=False, max_overflow=5, encoding='utf-8')
except ImportError:
raise RuntimeError
engine.connect() # sessionmaker is a factory obj, generate a Session instance, reload __call__ function
# __call__ function will return a session class each time
Session = orm.sessionmaker(bind=engine)
# use Session() to create a class, and assign it to an attribute
self.session = Session()
# Assign costom table and engine to attribute
self.account = AccountStore.__table__
self.engine = engine
# Bind engine and table
# Method one: assign manually one by one
self.account.metadata.bind = engine
# Method two: use reflect to map all/partly Table schema
#Base.metadata.reflect(engine) class PersonInfoCriteria():
"""
This is the criteria for PersonInfo
Replace None with input value
"""
def __init__(self, **kwargs):
self.id = None
self.age = None
self.name = None
self.result = None for field, argument in kwargs.items():
if str(field) == 'id':
self.id = argument
if str(field) == 'age':
self.age = argument
if str(field) == 'name':
self.name = argument class PersonInfoService():
"""
This is the service for PersonInfo
Generate condition and filter out expression for filter(SQL) according to criteria value
""" # This function to build criteria(expression/clause) for filter(SQL)
# Note: PersonInfo is based on declarative_base,
# so PersonInfo.attr == value is an condition expression(clause) for sqlalchemy function
# also PersonInfo.attr.like(value) too, like function equal to "%" in SQL
# finally return the list of clauses
@staticmethod
def _criteria_builder(person_info_criteria):
clauses = []
if person_info_criteria.id:
clauses.append(PersonInfo.id == person_info_criteria.id)
if person_info_criteria.age:
clauses.append(PersonInfo.age == person_info_criteria.age)
if person_info_criteria.name:
if '%' in person_info_criteria.name:
clauses.append(PersonInfo.name.like(person_info_criteria.name))
else:
clauses.append(PersonInfo.name == person_info_criteria.name)
return clauses @staticmethod
def find(person_info_criteria, session):
# Build clauses for session filter
clauses = PersonInfoService._criteria_builder(person_info_criteria)
# Query PersonInfo and filter according to clauses, use all() function to return as list
person_info_criteria.result = session.query(PersonInfo).filter(*clauses).all()
return person_info_criteria.result class AccountStoreCriteria():
def __init__(self, **kwargs):
self.items = None
self.account = None
self.password = None
self.account_id = None
self.person_info = None
self.result = None for field, argument in kwargs.items():
if field == 'items':
self.items = argument
if field == 'account':
self.account = argument
if field == 'password':
self.password = argument
if field == 'account_id':
self.account_id = argument
if field == 'person_info':
self.person_info = argument class AccountStoreService(): @staticmethod
def _criteria_builder(account_store_criteria):
clauses = []
if account_store_criteria.items:
clauses.append(AccountStore.items == account_store_criteria.items)
if account_store_criteria.account:
if '%' in account_store_criteria.account:
clauses.append(AccountStore.account.like(account_store_criteria.account))
else:
clauses.append(AccountStore.account == account_store_criteria.account)
if account_store_criteria.password:
clauses.append(AccountStore.password == account_store_criteria.password)
if account_store_criteria.account_id:
clauses.append(AccountStore.accout_id == account_store_criteria.account_id) # person_info from PersonInfoService filter
# Note: pnif.AccountStore is an instrumentedList type obj
# sqlalchemy use instrumentedList to simulate one-to-many and many-to-many relationships
# sqlalchemy does not support in_ many to many relationships yet
# in_() function to filter out account id in range
# SQL: SELECT * FROM account_store WHERE account_store.account_id in (...)
if account_store_criteria.person_info:
account_ids = []
for pnif in account_store_criteria.person_info:
for acid in pnif.AccountStore:
account_ids.append(acid.account_id)
clauses.append(AccountStore.account_id.in_(account_ids)) return clauses @staticmethod
def find(account_store_criteria, session):
clauses = AccountStoreService._criteria_builder(account_store_criteria)
account_store_criteria.result = session.query(AccountStore).filter(*clauses).all()
return account_store_criteria.result if __name__ == '__main__':
#dsn = 'mssql+pyodbc://ItpReadOnly:@reaedonlyENC@encitp.cn.ao.ericsson.se\itp:0/ITP'
dsn = 'mysql+mysqldb://root:root@localhost/test_db'
ses = SqlalchemyActor(dsn)
session = ses.session # Filter out the person information according to id and age
id, age = 2, 7
clauses = PersonInfoCriteria(id=id, age=age)
# re is an obj list of PersonInfo, use obj.attr to fetch value
person_info = PersonInfoService.find(clauses, session)
name = person_info[0].name
print('Filter out user: %s' % name) # Filter out the account id according to name via relation table
items = ['WeChat', 'Qq']
for it in items:
clauses = AccountStoreCriteria(items=it, person_info=person_info)
account_info = AccountStoreService.find(clauses, session)
for ac in account_info:
print(30*'-'+'\n%s' % name)
print(ac)

下面将分段进行解释
首先对所需的模块进行相应的导入

from sqlalchemy import create_engine, exc, orm
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.schema import Table, ForeignKey, Column
from sqlalchemy.sql.sqltypes import Integer, VARCHAR
from sqlalchemy.dialects.mysql.base import TINYINT
from sqlalchemy.orm import relationship

随后利用声明层函数产生一个声明层类,这个声明层类使用了Active Record设计模式,能够自动将对象的修改变为表格的修改

# declarative_base function will return a class which using active record pattern
# It will combine object opeartion and data operation automatically
Base = declarative_base()

接下来将进行表格建立的工作,首先建立一张关系表类对应数据库中的关系表格,此处的关系表格采用显示的ORM方式,即不基于声明层的表格类,同时定义外键信息。

# This is rel table
t_PersonAccount_Rel = Table('personaccount_rel',
Base.metadata,
Column('name', VARCHAR(8), ForeignKey('person_info.name')),
Column('account_id', Integer, ForeignKey('account_store.account_id')))

随后利用声明层建立起两张信息表,由于需要通过个人信息表与关系表两张表查找到账号的信息,因此在定义PersonInfo表类的时候,需要同时定义一个关系对象,利用relationship函数建立起表中的关系对象。

# Create table based on Base obj
class PersonInfo(Base):
__table__ = Table('person_info',
Base.metadata,
Column('id', TINYINT(4)),
Column('age', Integer),
Column('name', VARCHAR(8), primary_key=True)) # Need to search via person --> account
# So build up a relationship between person and account
# relationship(class_name, class, class_name)
AccountStore = relationship('AccountStore',
secondary=t_PersonAccount_Rel,
backref='PersonInfo') class AccountStore(Base):
__table__ = Table('account_store',
Base.metadata,
Column('account_id', Integer, primary_key=True),
Column('items', VARCHAR(20)),
Column('account', VARCHAR(50)),
Column('password', VARCHAR(50))) def __repr__(self):
return 'Items: %s\nAccount: %s\nPassword: %s' % (self.items, self.account, self.password)

最后定义一个sqlalchemy的执行器,利用基本建立步骤建立起各项对应关系

Note: 由于使用的是声明层,因此此处的metadata的绑定可以省略,这部分可参考声明层表对象的两种建立方式。

class SqlalchemyActor():
def __init__(self, dsn):
try:
engine = create_engine(dsn, echo=False, max_overflow=5, encoding='utf-8')
except ImportError:
raise RuntimeError
engine.connect() # sessionmaker is a factory obj, generate a Session instance, reload __call__ function
# __call__ function will return a session class each time
Session = orm.sessionmaker(bind=engine)
# use Session() to create a class, and assign it to an attribute
self.session = Session()
# Assign costom table and engine to attribute
self.account = AccountStore.__table__
self.engine = engine
# Bind engine and table
# Method one: assign manually one by one
self.account.metadata.bind = engine
# Method two: use reflect to map all/partly Table schema
#Base.metadata.reflect(engine)

接着,将进行信息标准类及信息筛选类的建立,利用这两个类来完成类似于SQL中的条件信息筛选。

首先是信息标准类,用于存储所需要进行处理的条件信息,根据传入的参数对筛选信息类的属性进行赋值。

class PersonInfoCriteria():
"""
This is the criteria for PersonInfo
Replace None with input value
"""
def __init__(self, **kwargs):
self.id = None
self.age = None
self.name = None
self.result = None for field, argument in kwargs.items():
if str(field) == 'id':
self.id = argument
if str(field) == 'age':
self.age = argument
if str(field) == 'name':
self.name = argument

接着建立起一个个人信息的处理服务类 PersonInfoService,在这个类中将建立起两个静态方法,首先是_criteria_builder静态方法(确切来说应该算是clauses_builder),该静态方法会根据属性的存在,来建立起一个clauses条件语句的列表,随后再定义一个查找函数,利用query获取表格信息,再利用filter函数及前面的clause条件语句进行筛选得到最终的结果。

Note: 此处值得注意的是,由于此处的PersonInfo是一个声明层表格类,因此其==号两边的返回结果将不是bool值的True或False,而是一个等价于SQL条件的语句,一般用于filter函数中实现条件筛选。参考filter()函数的传入参数形式。

class PersonInfoService():
"""
This is the service for PersonInfo
Generate condition and filter out expression for filter(SQL) according to criteria value
""" # This function to build criteria(expression/clause) for filter(SQL)
# Note: PersonInfo is based on declarative_base,
# so PersonInfo.attr == value is an condition expression(clause) for sqlalchemy function
# also PersonInfo.attr.like(value) too, like function equal to "%" in SQL
# finally return the list of clauses
@staticmethod
def _criteria_builder(person_info_criteria):
clauses = []
if person_info_criteria.id:
clauses.append(PersonInfo.id == person_info_criteria.id)
if person_info_criteria.age:
clauses.append(PersonInfo.age == person_info_criteria.age)
if person_info_criteria.name:
if '%' in person_info_criteria.name:
clauses.append(PersonInfo.name.like(person_info_criteria.name))
else:
clauses.append(PersonInfo.name == person_info_criteria.name)
return clauses @staticmethod
def find(person_info_criteria, session):
# Build clauses for session filter
clauses = PersonInfoService._criteria_builder(person_info_criteria)
# Query PersonInfo and filter according to clauses, use all() function to return as list
person_info_criteria.result = session.query(PersonInfo).filter(*clauses).all()
return person_info_criteria.result

与前面类似,此处针对account_store类建立其标准类及服务类

Note: 此处应当注意的是,由于需要通过关系表的查询,因此需要在这个筛选标准类中多增加一项筛选标准,即传入的PersonInfo筛选结果,若传入了person_info项目,则说明需要对个人信息进行关系筛选。

class AccountStoreCriteria():
def __init__(self, **kwargs):
self.items = None
self.account = None
self.password = None
self.account_id = None
self.person_info = None
self.result = None for field, argument in kwargs.items():
if field == 'items':
self.items = argument
if field == 'account':
self.account = argument
if field == 'password':
self.password = argument
if field == 'account_id':
self.account_id = argument
if field == 'person_info':
self.person_info = argument

Note: 此处的表格服务类值得注意的是,在创建条件子句时,对于中间表的处理。由于在sqlalchemy的in_()函数尚且不支持多对多筛选,此处sqlalchemy利用instrumentedList来处理一对多或多对多的情况,在之前建立的Account_Store关系对象中,AccountStore即是instrumentList类型,可以利用instrumentList.in_(list)建立条件语句。此处利用for循环首先获取所有需要的account_id信息,生成一个列表,随后利用id列表建立等价于SQL的IN条件语句,添加到clause中。关于instrumentedList,参考stackoverflow的答案

class AccountStoreService():

    @staticmethod
def _criteria_builder(account_store_criteria):
clauses = []
if account_store_criteria.items:
clauses.append(AccountStore.items == account_store_criteria.items)
if account_store_criteria.account:
if '%' in account_store_criteria.account:
clauses.append(AccountStore.account.like(account_store_criteria.account))
else:
clauses.append(AccountStore.account == account_store_criteria.account)
if account_store_criteria.password:
clauses.append(AccountStore.password == account_store_criteria.password)
if account_store_criteria.account_id:
clauses.append(AccountStore.accout_id == account_store_criteria.account_id) # person_info from PersonInfoService filter
# Note: pnif.AccountStore is an instrumentedList type obj
# sqlalchemy use instrumentedList to simulate one-to-many and many-to-many relationships
# sqlalchemy does not support in_ many to many relationships yet
# in_() function to filter out account id in range
# SQL: SELECT * FROM account_store WHERE account_store.account_id in (...)
if account_store_criteria.person_info:
account_ids = []
for pnif in account_store_criteria.person_info:
for acid in pnif.AccountStore:
account_ids.append(acid.account_id)
clauses.append(AccountStore.account_id.in_(account_ids)) return clauses @staticmethod
def find(account_store_criteria, session):
clauses = AccountStoreService._criteria_builder(account_store_criteria)
account_store_criteria.result = session.query(AccountStore).filter(*clauses).all()
return account_store_criteria.result

最后是执行的主程序,连接本地数据库,通过id和age筛选出name信息,随后利用关系表通过name与account_id的对应,以及所需账户类型,找到账户信息,最终显示。

if __name__ == '__main__':
dsn = 'mysql+mysqldb://root:root@localhost/test_db'
ses = SqlalchemyActor(dsn)
session = ses.session # Filter out the person information according to id and age
id, age = 2, 7
clauses = PersonInfoCriteria(id=id, age=age)
# re is an obj list of PersonInfo, use obj.attr to fetch value
person_info = PersonInfoService.find(clauses, session)
name = person_info[0].name
print('Filter out user: %s' % name) # Filter out the account id according to name via relation table
items = ['WeChat', 'Qq']
for it in items:
clauses = AccountStoreCriteria(items=it, person_info=person_info)
account_info = AccountStoreService.find(clauses, session)
for ac in account_info:
print(30*'-'+'\n%s' % name)
print(ac)

运行代码得到结果

Filter out user: LIKE
------------------------------
LIKE
Items: WeChat
Account: hereisac
Password: 12345
------------------------------
LIKE
Items: Qq
Account: re32isac
Password: 123435

从最终显示的结果可以看到,通过一系列筛选过程,得到了最终所需的账号密码信息

相关阅读


1. ORM 与 sqlalchemy 模块

2. sqlalchemy 的基本使用

3. 建立声明层表对象的两种方式

4. 声明层 ORM 访问方式

Python与数据库[2] -> 关系对象映射/ORM[5] -> 利用 sqlalchemy 实现关系表查询功能的更多相关文章

  1. Python与数据库[2] -> 关系对象映射/ORM[0] -> ORM 与 sqlalchemy 模块

    ORM 与 sqlalchemy 1 关于ORM / About ORM 1.1 ORM定义 / Definition of ORM ORM(Object Relational Mapping),即对 ...

  2. Python与数据库[2] -> 关系对象映射/ORM[1] -> sqlalchemy 的基本使用示例

    sqlalchemy 的基本使用示例 下面的例子中将利用sqlalchemy进行数据库的连接,通过orm方式利用类实例属性操作的方式对数据库进行相应操作,同时应用一些常用的函数. 完整代码如下: fr ...

  3. Python与数据库[2] -> 关系对象映射/ORM[3] -> sqlalchemy 的声明层 ORM 访问方式

    sqlalchemy的声明层ORM访问方式 sqlalchemy中可以利用声明层进行表格类的建立,并利用ORM对象进行数据库的操作及访问,另一种方式为显式的 ORM 访问方式. 主要的建立步骤包括: ...

  4. Python与数据库[2] -> 关系对象映射/ORM[4] -> sqlalchemy 的显式 ORM 访问方式

    sqlalchemy 的显式 ORM 访问方式 对于sqlalchemy,可以利用一种显式的ORM方式进行访问,这种方式无需依赖声明层,而是显式地进行操作.还有一种访问方式为声明层 ORM 访问方式. ...

  5. Python与数据库[2] -> 关系对象映射/ORM[2] -> 建立声明层表对象的两种方式

    建立声明层表对象的两种方式 在对表对象进行建立的时候,通常有两种方式可以完成,以下是两种方式的建立过程对比 首先导入需要的模块,获取一个声明层 from sqlalchemy.sql.schema i ...

  6. 一:ORM关系对象映射(Object Relational Mapping,简称ORM)

    狼来的日子里! 奋发博取 10)django-ORM(创建,字段类型,字段参数) 一:ORM关系对象映射(Object Relational Mapping,简称ORM) ORM分两种: DB fir ...

  7. 关系/对象映射 多对多关系(@ManyToMany 注释)【重新认识】

    old: @ManyToMany 注释:表示此类是多对多关系的一边, mappedBy 属性定义了此类为双向关系的维护端, 注意:mappedBy 属性的值为此关系的另一端的属性名. 例如,在Stud ...

  8. Vc数据库编程基础MySql数据库的表查询功能

    Vc数据库编程基础MySql数据库的表查询功能 一丶简介 不管是任何数据库.都会有查询功能.而且是很重要的功能.上一讲知识简单的讲解了表的查询所有. 那么这次我们需要掌握的则是. 1.使用select ...

  9. MyBitis(iBitis)系列随笔之二:类型别名(typeAliases)与表-对象映射(ORM)

    类型别名(typeAliases):     作用:通过一个简单的别名来表示一个冗长的类型,这样可以降低复杂度.    类型别名标签typeAliases中可以包含多个typeAlias,如下 < ...

随机推荐

  1. 《Cracking the Coding Interview》——第17章:普通题——题目10

    2014-04-28 23:54 题目:XML文件的冗余度很大,主要在于尖括号里的字段名.按照书上给定的方式进行压缩. 解法:这题我居然忘做了,只写了一句话的注解.用python能够相对方便地实现,因 ...

  2. 《Cracking the Coding Interview》——第17章:普通题——题目5

    2014-04-28 22:44 题目:猜数字游戏.四个数字,每个都是0~9之间.你每猜一次,我都告诉你,有多少个位置和数字都对(全对),有多少个位置错数字对(半对).比如“6309”,你猜“3701 ...

  3. Delphi中的关键字与保留字

    Delphi中的关键字与保留字 分类整理 Delphi 中的“关键字”和“保留字”,方便查询 感谢原作者的收集整理! 关键字和保留字的区别在于,关键字不推荐作标示符(编译器已经内置相关函数或者留给保留 ...

  4. 玩转Linux之pwd命令

    玩转Linux之pwd命令 你有没有遇到过需要知道当前所在目录却无从得知?有没有想要复制出当前所在目录层次却不知如何下手?俗话说有困难找警察,想知道目录层次自然要找pwd了.那么问题来了: 什么是pw ...

  5. 使用pip命令报You are using pip version 9.0.3, however version 18.0 is available pip版本过期.解决方案

    使用pip命令安装或卸载第三方库时报You are using pip version 9.0.3, however version 18.0 is available.错误,一般情况下是pip版本过 ...

  6. 决策树之CART算法

    顾名思义,CART算法(classification and regression tree)分类和回归算法,是一种应用广泛的决策树学习方法,既然是一种决策树学习方法,必然也满足决策树的几大步骤,即: ...

  7. jsp页面提示“Multiple annotations found at this line: - The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path”解决方案

    Multiple annotations found at this line: - The superclass "javax.servlet.http.HttpServlet" ...

  8. nyoj 题目16 矩形嵌套

    矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4   描述 有n个矩形,每个矩形可以用a,b来描述,表示长和宽.矩形X(a,b)可以嵌套在矩形Y(c,d)中当且仅当a& ...

  9. jQuery选择器之元素选择器

    元素选择器:根据给定(html)标记名称选择所有的元素. 描述: $('element') 搜索指定元素标签名的所有节点,这是一个合集的操作.同样的也有原生方法getElementsByTagName ...

  10. 【bzoj1899】[Zjoi2004]Lunch 午餐 dp

    题目描述 上午的训练结束了,THU ACM小组集体去吃午餐,他们一行N人来到了著名的十食堂.这里有两个打饭的窗口,每个窗口同一时刻只能给一个人打饭.由于每个人的口味(以及胃口)不同,所以他们要吃的菜各 ...