327

I've looked through the docs and I can't seem to find out how to do an OR query in SQLAlchemy. I just want to do this query.

SELECT address FROM addressbook
WHERE city='boston' AND (lastname='bulger' OR firstname='whitey')

Should be something like

addr = session.query(AddressBook).filter(City == "boston").filter(????)

7 Answers 7

559

From the tutorial:

from sqlalchemy import or_
filter(or_(User.name == 'ed', User.name == 'wendy'))
Sign up to request clarification or add additional context in comments.

7 Comments

Note that this approach supports using generators, so if you have a long list of things to OR, you can do filter(or_(User.name == v for v in ('Alice', 'Bob', 'Carl')))
@Robru's advice is unnecessarily inefficient. If you already have a collection then you should use the in_ operator like this: filter(User.name.in_(['Alice', 'Bob', 'Carl']))
Ah thanks i was not aware sqlalchemy had that filter
@intgr The example showed by robru is still efficient, if you want to use another operator instead of in_, for example the LIKE operator.
@intgr My experience with Oracle shows that a sequence of "OR"s is way faster than using "IN". Also "IN" is limited to a set of ~1000 entries, while "OR" is not.
|
489

SQLAlchemy overloads the bitwise operators &, | and ~ so instead of the ugly and hard-to-read prefix syntax with or_() and and_() (like in Bastien's answer) you can use these operators:

.filter((AddressBook.lastname == 'bulger') | (AddressBook.firstname == 'whitey'))

Note that the parentheses are not optional due to the precedence of the bitwise operators.

So your whole query could look like this:

addr = session.query(AddressBook) \
    .filter(AddressBook.city == "boston") \
    .filter((AddressBook.lastname == 'bulger') | (AddressBook.firstname == 'whitey'))

3 Comments

+1, but could you instead wrap the last two filter arguments in more parentheses and use an & between them and the first (rather than using a second filter call) for the same effect?
@ChaseSandmann: Yes you could. But would it be more readable? No.
For some reason this doesn't work if one operand is a collection, but and_() does. E.g. select(Author).outerjoin(Book, Author.books & (Book.pub_year == '2020')
61

or_() function can be useful in case of unknown number of OR query components.

For example, let's assume that we are creating a REST service with few optional filters, that should return record if any of filters return true. On the other side, if parameter was not defined in a request, our query shouldn't change. Without or_() function we must do something like this:

query = Book.query
if filter.title and filter.author:
    query = query.filter((Book.title.ilike(filter.title))|(Book.author.ilike(filter.author)))
else if filter.title:
    query = query.filter(Book.title.ilike(filter.title))
else if filter.author:
    query = query.filter(Book.author.ilike(filter.author))

With or_() function it can be rewritten to:

query = Book.query
not_null_filters = []
if filter.title:
    not_null_filters.append(Book.title.ilike(filter.title))
if filter.author:
    not_null_filters.append(Book.author.ilike(filter.author))

if len(not_null_filters) > 0:
    query = query.filter(or_(*not_null_filters))

2 Comments

Can this be used with the == operator on a column? I see like, ilike etc implemented but no "equals", apart from the __eq__ override which apparently just returns a bool, not a filter that i can put into or_().
Umm, no you don't need to write such convoluted logic. You can always collect your conjunctions in a temporary variable. I.e. instead of collecting them in an array you set a variable to False (initially) and |= your additional conditions to it.
38

For SQLAlchemy ORM 2.0 both | and or_ are accepted.

Documentation

from sqlalchemy.future import select
from sqlalchemy.sql import or_


query = select(User).where(or_(User.name == 'ed', User.name == 'wendy'))
print(query)

# also possible:

query = select(User).where((User.name == 'ed') | (User.name == 'wendy'))
print(query)

Comments

4

In case you need to apply an or condition if a condition is met filters can be stored in variable and chained them with a pipe. Here is an example

 class Student(db.Model):
     __tablename__ = "student"
     id = Column(Integer, primary_key=True)
     name = Column(String, nullable=False)

   def get_by_name(self, name):
      condition = # Code Here for condition
      filters = (Student.name == "Stack") | (Student.name == "Overflow") if 
condition else (Student.name == "Stack")
      query = Student.query.filter(filters).order_by(Student.id.asc())

Comments

4

This has been really helpful. Here is my implementation for any given table:

def sql_replace(self, tableobject, dictargs):

    #missing check of table object is valid
    primarykeys = [key.name for key in inspect(tableobject).primary_key]

    filterargs = []
    for primkeys in primarykeys:
        if dictargs[primkeys] is not None:
            filterargs.append(getattr(db.RT_eqmtvsdata, primkeys) == dictargs[primkeys])
        else:
            return

    query = select([tableobject]).where(and_(*filterargs))

    if self.r_ExecuteAndErrorChk2(query)[primarykeys[0]] is not None:
        # update
        filter = and_(*filterargs)
        query = tableobject.__table__.update().values(dictargs).where(filter)
        return self.w_ExecuteAndErrorChk2(query)

    else:
        query = tableobject.__table__.insert().values(dictargs)
        return self.w_ExecuteAndErrorChk2(query)

# example usage
inrow = {'eqmtvs_id': eqmtvsid, 'datetime': dtime, 'param_id': paramid}

self.sql_replace(tableobject=db.RT_eqmtvsdata, dictargs=inrow)

Comments

0

For filtering on values in JSON columns, the syntax would be like this:

query = query.filter(or_(func.json_contains(models.table.column, '"' + c + '"') for c in (list_of_values)))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.