298

I've looked through the docs and I cant 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(????)
JiminyCricket
  • 7,050
  • 7
  • 42
  • 59

6 Answers6

516

From the tutorial:

from sqlalchemy import or_
filter(or_(User.name == 'ed', User.name == 'wendy'))
Bastien Léonard
  • 60,478
  • 20
  • 78
  • 95
  • 97
    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 Aug 26 '15 at 20:21
  • 107
    @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']))` – intgr Sep 30 '16 at 09:37
  • 8
    Ah thanks i was not aware sqlalchemy had that filter – robru Oct 02 '16 at 16:49
  • 13
    @intgr The example showed by robru is still efficient, if you want to use another operator instead of in_, for example the LIKE operator. – Lhassan Baazzi Jul 12 '17 at 01:41
  • 5
    @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. – g.a Sep 05 '18 at 21:13
  • @g.a Using `OR` when you have a list of values to compare against a single column (i.e., exactly what `IN` does) might be fairly premature optimization, let the query analyzer optimize it for you. Also you can get very different results with other RDBMs. Even for Oracle I doubt that the statement is true in general, there are for sure scenarios when using `IN` is faster/more efficient. – jso Mar 28 '19 at 11:22
  • @robru Thanks a lot! It solve the problem: ```query.filter_by(**somedict)``` when the type of somedict's value is ```list```.The code is:```query.filter(getattr(Classname,somedict'key)==v for v in somedict'value)``` – DachuanZhao Apr 03 '19 at 07:43
462

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'))
EliadL
  • 6,230
  • 2
  • 26
  • 43
ThiefMaster
  • 310,957
  • 84
  • 592
  • 636
  • 12
    +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? – Chase Sandmann Aug 14 '13 at 20:34
  • 29
    @ChaseSandmann: Yes you could. But would it be more readable? No. – ThiefMaster Aug 14 '13 at 21:34
  • 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')` – Ledorub Mar 14 '23 at 17:36
56

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))
SuperShoot
  • 9,880
  • 2
  • 38
  • 55
Valar
  • 1,833
  • 1
  • 16
  • 18
  • 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_()`. – theberzi Aug 26 '20 at 13:19
  • 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. – Matthias Urlichs Oct 04 '20 at 16:35
28

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)
Willemoes
  • 5,752
  • 3
  • 30
  • 27
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)
snakecharmerb
  • 47,570
  • 11
  • 100
  • 153
delpozov
  • 49
  • 1
3

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())
Luka H
  • 29
  • 3