Pages

Wednesday, October 16, 2019

Filtering Queries by a Relationship with SQLAlchemy (and potatoes)

I have a database with tables about plots of land, and the farmers who tend to them.
"Using SQLAlchemy, I want to find out how many potatoes are on plots of land where the tending farmer actually likes potatoes."
In SQL this query could look something like this:
SELECT plot.potato_count
FROM plot
JOIN farmer ON plot.farmer_id=farmer.id
WHERE farmer.likes_potatoes
That's right, if you don't like potatoes, we don't want to know about your plot's potatoes. ðŸ¥”

So, how do we do this in SQLAlchemy?
In paradise, we can type this:
result = session.query(Plot).filter(Plot.farmer.likes_potatoes == True)
However, this is planet Earth, not paradise1, so we must work with what we have. To that end, here are 3 ways to filter a query by an attribute present in a relationship:
  1. The cleanest way
    result = session.query(Plot).filter(Plot.farmer.has(likes_potatoes=True)
    This uses the has() operator which conveniently abstracts away all of the arduous "join" code. This is one reason why it's nice to have an ORM.
  2. The join and filter way
    result = session.query(Plot).join(Plot.farmer).\
             filter(Plot.farmer.property.mapper.class_.likes_potatoes == True)
    This uses the join functionality and directly accesses the likes_potatoes attribute of the farmer linked to the plot.
  3. The join and filter_by way
    result = session.query(Plot).filter(Plot.farmer.likes_potatoes == True)
    This is very similar to method 2, but it uses filter_by to get a cleaner looking filter statement.

The above SQLAlchemy lines all spat out the same thing when I ran print(result):
SELECT plot.id AS plot_id, plot.farmer_id AS plot_farmer_id
FROM plot
JOIN farmer ON farmer.id = plot.farmer_id 
WHERE farmer.likes_potatoes


Inspiration:

This article Multi-table filters in SQLAlchemy
Key Take-aways:
You can enable SQLAlchemy query logging thusly:
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

This SO question which references the has() documentation.

1: A wise man once said (at the bottom of a comment thread) "SQLAlchemy [is] doing its best to bridge two very different languages [SQL and python]"