filtering Sqlalchemy relational condition

Question:

I would like to query a user based on their code and mobile number.

Of note, I am using an async session when connecting to the DB.

This is my models and query:

class model_user(Base):
    __tablename__ = "users"
    id = Column(UUID)
    code = Column(Integer)
    user_name = Column(String(11), unique=True)
    first_name = Column(String(255), index=True)
    last_name = Column(String(255), index=True)
    contact = relationship("model_contact", back_populates="user")

class model_contact(Base):
    __tablename__ = "contacts"
    id = Column(Integer)
    mobile = Column(String(320))
    user_id = Column(
        UUID(as_uuid=True), ForeignKey("users.id"), nullable=False, index=True
    )
    user = relationship("model_user", back_populates="contact")


# Here is my query:
query = await db.execute(
    select(user_model)
    .filter_by(code==5)
    .options(
        joinedload(model_user.contact)
    )
    .filter(model_contact.mobile == mobile)
Asked By: Masy

||

Answers:

This really has nothing to do with whether or not you are using the async extension of SQLAlchemy. Queries are constructed the same way. Only the session setup and interaction is obviously different.

Side notes:

  1. You should use PascalCase to name your classes and including the term "model" in the name is typically not good style, i.e. User and Contact.
  2. Since you have a one-to-many relationship between your user model and your contact model (i.e. one user can have multiple sets of contact info), you should name the relationship attribute on the user model with plural, i.e. contacts.

The simplest way to do what you want that I can think of is using the Select.where method. You can then construct an SQL EXISTS subquery using the any method of the relationship. The statement would look like this:

statement = select(User).where(
    User.code == 123,
    User.contacts.any(Contact.mobile == "555")
)

Here is a full working example using aiosqlite just to demonstrate that this works with async tools:

from asyncio import run

from sqlalchemy import Column, ForeignKey, Integer, String, select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker


Base = declarative_base()


class User(Base):
    __tablename__ = "users"
    id = Column(Integer, primary_key=True)
    code = Column(Integer)
    contacts = relationship("Contact", back_populates="user")


class Contact(Base):
    __tablename__ = "contacts"
    id = Column(Integer, primary_key=True)
    mobile = Column(String(320))
    user_id = Column(
        Integer(), ForeignKey("users.id"), nullable=False, index=True
    )
    user = relationship(User, back_populates="contacts")


async def main():
    engine = create_async_engine("sqlite+aiosqlite://", echo=True)
    async with engine.begin() as conn:
        await conn.run_sync(Base.metadata.create_all)

    async_session = sessionmaker(
        engine, expire_on_commit=False, class_=AsyncSession
    )
    async with async_session() as session:
        statement = select(User).where(
            User.code == 123,
            User.contacts.any(Contact.mobile == "555")
        )
        await session.execute(statement)


if __name__ == "__main__":
    run(main())

Running this script produces the following SQL output for the select query:

SELECT users.id, users.code 
FROM users 
WHERE users.code = ? AND (EXISTS (SELECT 1 
FROM contacts 
WHERE users.id = contacts.user_id AND contacts.mobile = ?))
...
(123, '555')

This approach should give you the results you want.

Answered By: Daniil Fajnberg
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.