Как создать немного более сложный фильтр, используя or_ или and_ в sqlalchemy
Я пытаюсь сделать очень простой поиск из списка терминов
terms = ['term1', 'term2', 'term3']
Как я программно просматриваю список терминов и строю "условия" из списка терминов, чтобы я мог сделать запрос с помощью фильтра и или_ или _and?
e.g. query.filter(or_(#something constructed from terms))
Ответы
Ответ 1
Если у вас есть список терминов и вы хотите найти строки, в которых поле соответствует одному из них, вы можете использовать метод in_():
terms = ['term1', 'term2', 'term3']
query.filter(Cls.field.in_(terms))
Если вы хотите сделать что-то более сложное, тогда or_()
и and_()
возьмите объекты ClauseElement
в качестве параметров. ClauseElement и его подклассы в основном представляют SQL AST вашего запроса. Обычно вы создаете элементы предложения, вызывая оператор сравнения объектов Column или InstrumentedAttribute:
# Create the clause element
clause = (users_table.columns['name'] == "something")
# you can also use the shorthand users_table.c.name
# The clause is a binary expression ...
print(type(clause))
# <class 'sqlalchemy.sql.expression._BinaryExpression'>
# ... that compares a column for equality with a bound value.
print(type(clause.left), clause.operator, type(clause.right))
# <class 'sqlalchemy.schema.Column'>, <built-in function eq>,
# <class 'sqlalchemy.sql.expression._BindParamClause'>
# str() compiles it to SQL
print(str(clause))
# users.name = ?
# You can also do that with ORM attributes
clause = (User.name == "something")
print(str(clause))
# users.name = ?
Вы можете обрабатывать элементы предложения, представляющие ваши условия, такие как любые объекты Python, помещать их в списки, составлять их в другие элементы предложения и т.д. Таким образом, вы можете сделать что-то вроде этого:
# Collect the separate conditions to a list
conditions = []
for term in terms:
conditions.append(User.name == term)
# Combine them with or to a BooleanClauseList
condition = or_(*conditions)
# Can now use the clause element as a predicate in queries
query = query.filter(condition)
# or to view the SQL fragment
print(str(condition))
# users.name = ? OR users.name = ? OR users.name = ?
Ответ 2
Предполагая, что ваша переменная terms
содержит действительные фрагменты операторов SQL, вы можете просто передать terms
, которому предшествует звездочка, до or_
или and_
:
>>> from sqlalchemy.sql import and_, or_
>>> terms = ["name='spam'", "email='[email protected]'"]
>>> print or_(*terms)
name='spam' OR email='[email protected]'
>>> print and_(*terms)
name='spam' AND email='[email protected]'
Обратите внимание, что это предполагает, что terms
содержит только допустимые и правильно экранированные фрагменты SQL, поэтому это потенциально опасно, если злоумышленник может каким-либо образом получить доступ к terms
.
Вместо того, чтобы самостоятельно создавать фрагменты SQL, вы должны позволить SQLAlchemy строить параметризованные SQL-запросы, используя другие методы из sqlalchemy.sql
. Я не знаю, подготовили ли вы теги Table
для ваших таблиц или нет; если да, предположим, что у вас есть переменная с именем users
, которая является экземпляром Table
и описывает вашу таблицу users
в базе данных. Затем вы можете сделать следующее:
from sqlalchemy.sql import select, or_, and_
terms = [users.c.name == 'spam', users.c.email == '[email protected]']
query = select([users], and_(*terms))
for row in conn.execute(query):
# do whatever you want here
Здесь users.c.name == 'spam'
создаст объект sqlalchemy.sql.expression._BinaryExpression
, который записывает, что это двоичное отношение равенства между столбцом name
таблицы users
и строковым литералом, содержащим spam
. Когда вы конвертируете этот объект в строку, вы получите фрагмент SQL, например users.name = :1
, где :1
является заполнителем для параметра. Объект _BinaryExpression
также запоминает привязку :1
к 'spam'
, но он не будет вставлять ее до тех пор, пока не будет выполнен SQL-запрос. Когда он будет вставлен, механизм базы данных будет уверен, что он правильно экранирован. Предлагаемое чтение: Парадигма SQLAlchemy
Если у вас есть только таблица базы данных, но у вас нет переменной users
, которая описывает таблицу, вы можете создать ее самостоятельно:
from sqlalchemy import Table, MetaData, Column, String, Boolean
metadata = MetaData()
users = Table('users', metadata,
Column('id', Integer, primary_key=True),
Column('name', String),
Column('email', String),
Column('active', Integer)
)
В качестве альтернативы вы можете использовать автозагрузку, которая запрашивает механизм базы данных для структуры базы данных и автоматически создает users
; очевидно, что это занимает больше времени:
users = Table('users', metadata, autoload=True)
Ответ 3
Хорошо, у меня была такая же проблема:
SQLAlchemy: эффективный/лучший выбор по первичным ключам?
terms = ['one', 'two', 'three']
clauses = or_( * [Table.field == x for x in terms] )
query = Session.query(Table).filter(clauses)
Вам это нравится?
Ответ 4
Вы можете использовать:
http://docs.sqlalchemy.org/en/rel_0_8/core/tutorial.html#conjunctions
В документации abobe вы найдете способ объединения условий. И, Или и не...!