在使用 SQLAlchemy 进行查询时,有时我们需要构建较为复杂的过滤条件,以便对查询结果进行更精细的控制。例如,我们需要查询满足多个条件的记录,或者查询满足某个条件或另一个条件的记录。此时,我们可以使用 SQLAlchemy 中的 "or_" 和 "and_" 方法来构建复杂的过滤条件。
2、解决方案
方法一:使用 in_() 方法
如果要查询一个字段的值包含在某个列表中的记录,可以使用 in_() 方法。例如:
terms = ['term1', 'term2', 'term3']
query.filter(Cls.field.in_(terms))
方法二:使用 or_() 和 and_() 方法
如果要构建更复杂的过滤条件,可以使用 or_() 和 and_() 方法。这两个方法都接收 ClauseElement 对象作为参数。ClauseElement 对象是 SQLAlchemy 中用于表示 SQL AST 的对象。通常情况下,可以通过对 Column 或 InstrumentedAttribute 对象调用比较运算符来创建 ClauseElement 对象。例如:
# 创建 ClauseElement 对象
clause = (users_table.columns['name'] == "something")
# 也可以使用简写 users_table.c.name
# ClauseElement 是一个二元表达式 ...
print(type(clause))
# <class 'sqlalchemy.sql.expression._BinaryExpression'>
# ... 它将列与绑定值进行比较。
print(type(clause.left), clause.operator, type(clause.right))
# <class 'sqlalchemy.schema.Column'>, <built-in function eq>,
# <class 'sqlalchemy.sql.expression._BindParamClause'>
# str() 将其编译成 SQL
print(str(clause))
# users.name = ?
# 也可以使用 ORM 属性
clause = (User.name == "something")
print(str(clause))
# users.name = ?
你可以将表示条件的 ClauseElement 对象像任何 Python 对象一样进行处理,可以将它们放入列表中,组合成其他 ClauseElement 对象等。因此,你可以这样做:
# 将单独的条件收集到一个列表中
conditions = []
for term in terms:
conditions.append(User.name == term)
# 将它们与 or 组合成 BooleanClauseList
condition = or_(*conditions)
# 现在可以使用该 ClauseElement 作为查询中的谓词
query = query.filter(condition)
# 或查看 SQL 片段
print(str(condition))
# users.name = ? OR users.name = ? OR users.name = ?
方法三:使用参数化 SQL 查询
为了避免 SQL 注入攻击,应该使用参数化 SQL 查询。 SQLAlchemy 提供了多种方法来构建参数化 SQL 查询,例如使用 bindparam() 方法或使用 ORM 模型对象。例如:
from sqlalchemy.sql import select, or_, and_
terms = [users.c.name == 'spam', users.c.email == 'spam@eggs.com']
query = select([users], and_(*terms))
for row in conn.execute(query):
# 在这里做任何你想做的事情
在上面的示例中,users.c.name == 'spam' 将创建一个 sqlalchemy.sql.expression._BinaryExpression 对象,该对象记录了 users 表的 name 列与包含 spam 的字符串文字之间的二元相等关系。当将此对象转换为字符串时,你将获得一个类似于 users.name = :1 的 SQL 片段,其中 :1 是参数的占位符。_BinaryExpression 对象还记录了 :1 与 'spam' 的绑定,但它不会在执行 SQL 查询之前插入它。当它被插入时,数据库引擎将确保它被正确转义。
代码例子
from sqlalchemy import create_engine, Table, Column, String, Integer, or_, and_
# 创建一个数据库引擎
engine = create_engine('sqlite:///mydb.db')
# 创建一个表
metadata = MetaData()
users = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('email', String),
Column('active', Integer)
)
metadata.create_all(engine)
# 插入一些数据
engine.execute(users.insert(), [
{'name': 'John', 'email': 'john@example.com', 'active': 1},
{'name': 'Mary', 'email': 'mary@example.com', 'active': 1},
{'name': 'Bob', 'email': 'bob@example.com', 'active': 0},
])
# 查询所有活跃用户
query = users.select().where(users.c.active == 1)
for row in engine.execute(query):
print(row)
# 查询所有名为 John 或 Mary 的用户
terms = [users.c.name == 'John', users.c.name == 'Mary']
query = users.select().where(or_(*terms))
for row in engine.execute(query):
print(row)
# 查询所有名为 John 且活跃的用户
terms = [users.c.name == 'John', users.c.active == 1]
query = users.select().where(and_(*terms))
for row in engine.execute(query):
print(row)