Как выполнить JOIN-запрос в SQLAlchemy?

Ответ

В SQLAlchemy JOIN-запросы можно выполнять двумя основными способами: через ORM (объектно-реляционное отображение) и через Core (прямое построение SQL-выражений).

1. ORM-подход

Это наиболее распространенный способ при работе с моделями данных. SQLAlchemy автоматически определяет условие соединения, если между моделями настроены отношения (relationship).

Предположим, есть модели User и Address со связью User.addresses.

INNER JOIN

Используется метод .join(). Он соединяет таблицы по настроенному отношению.

from sqlalchemy.orm import sessionmaker

# Найти всех пользователей, у которых есть адрес в Калифорнии
query = session.query(User).join(Address).filter(Address.state == 'CA')
california_users = query.all()

LEFT OUTER JOIN

Используется метод .outerjoin() или join(..., isouter=True). Полезен, когда нужно получить все записи из левой таблицы, даже если для них нет совпадений в правой.

# Найти всех пользователей и их адреса (включая пользователей без адресов)
query = session.query(User, Address).outerjoin(Address)
all_users_with_addresses = query.all()

Жадная загрузка (Eager Loading)

Чтобы избежать проблемы N+1 запросов при доступе к связанным объектам, используйте joinedload.

from sqlalchemy.orm import joinedload

# Загрузить всех пользователей и сразу же их адреса одним запросом
users = session.query(User).options(joinedload(User.addresses)).all()

# Доступ к user.addresses не вызовет дополнительных запросов к БД
for user in users:
    for address in user.addresses:
        print(user.name, address.email_address)

2. Core-подход

Этот подход ближе к написанию чистого SQL и используется для более сложных или оптимизированных запросов, работая напрямую с объектами Table.

from sqlalchemy import select, text

# users и addresses - это объекты Table
stmt = select(users.c.name, addresses.c.email_address)
    .select_from(users.join(addresses, users.c.id == addresses.c.user_id))
    .where(users.c.name.like('j%'))

with engine.connect() as connection:
    result = connection.execute(stmt)
    for row in result:
        print(row)

Ответ 18+ 🔞

А, SQLAlchemy, блядь! Ну, слушай, тут всё просто, как три копейки, если не заебываться. JOIN-ы, сука, можно делать двумя путями: через их эту, блядь, ORM-магию, где всё само летает, и через Core, где ты сам, как пацан, собираешь SQL из кубиков.

1. ORM-подход (для ленивых и умных)

Это когда ты настраиваешь модели, они там друг за друга держатся, а потом просто говоришь «дай» — и всё прилетает. Представь, есть User и Address, и они уже, блядь, знакомы через relationship.

INNER JOIN (обычный, чтоб не выёбываться)

Используешь .join(). Он сам догадается, за какую ручку их соединить, если ты отношения настроил.

from sqlalchemy.orm import sessionmaker

# Найти всех юзеров, которые живут в Калифорнии (ну, или хотя бы адрес там зарегили)
query = session.query(User).join(Address).filter(Address.state == 'CA')
california_users = query.all()

LEFT OUTER JOIN (чтоб всех подряд, даже лузеров без адреса)

Тут .outerjoin() или join(..., isouter=True). Получишь всех пользователей, даже если они живут в бомжатнике под мостом.

# Всех юзеров и их адреса, включая тех, у кого адрес — это «спроси у мамы»
query = session.query(User, Address).outerjoin(Address)
all_users_with_addresses = query.all()

Жадная загрузка (Eager Loading, чтоб не ебать базу лишними запросами)

А то бывает: достал юзеров, потом полез за адресами — и пошло-поехало, N+1 запрос, сервер плачет. Чтоб такого не было, юзай joinedload.

from sqlalchemy.orm import joinedload

# Одним махом грузим всех юзеров и сразу все их адреса, как мешки с картошкой
users = session.query(User).options(joinedload(User.addresses)).all()

# Теперь можно спокойно итерировать — базу больше не дергаем, она уже выдохлась
for user in users:
    for address in user.addresses:
        print(user.name, address.email_address)

2. Core-подход (для настоящих пацанов, которые в SQL как рыба в воде)

Тут уже ближе к голому металлу, работаешь прямо с таблицами (Table), без всяких модельных нежностей.

from sqlalchemy import select, text

# users и addresses — это уже не модели, а голые Table-объекты
stmt = select(users.c.name, addresses.c.email_address)
    .select_from(users.join(addresses, users.c.id == addresses.c.user_id))
    .where(users.c.name.like('j%'))

with engine.connect() as connection:
    result = connection.execute(stmt)
    for row in result:
        print(row)

Вот и вся магия, ёпта. Выбирай, что тебе ближе: ORM — чтобы быстро и с комфортом, или Core — когда надо всё по-взрослому, с контролем над каждым байтом. Главное — не перепутай, а то получишь хуй с горы вместо результата.