← Назад к вопросам

Как сделать select из 2 таблиц одним запросом?

1.0 Junior🔥 251 комментариев
#Python Core#Базы данных (SQL)

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Select из 2 таблиц одним запросом

Для объединения данных из нескольких таблиц используются различные типы JOIN. Это фундаментальная операция в SQL, которая позволяет комбинировать данные из разных источников на основе общих столбцов.

Основные типы JOIN

1. INNER JOIN (пересечение)

Возвращает строки, которые есть в ОБЕИХ таблицах:

# SQL
SELECT u.id, u.name, o.order_id, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

# Эквивалент:
SELECT u.id, u.name, o.order_id, o.amount
FROM users u
JOIN orders o ON u.id = o.user_id;  # JOIN по умолчанию = INNER JOIN

Пример результата:

id | name   | order_id | amount
1  | Alice  | 101      | 50
1  | Alice  | 102      | 75
2  | Bob    | 103      | 100

2. LEFT JOIN (все из левой таблицы)

Возвращает ВСЕ строки из левой таблицы + совпадающие из правой:

SELECT u.id, u.name, o.order_id, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

Пример результата:

id | name   | order_id | amount
1  | Alice  | 101      | 50
1  | Alice  | 102      | 75
2  | Bob    | 103      | 100
3  | Charlie| NULL     | NULL    <- Charlie не имеет заказов

3. RIGHT JOIN (все из правой таблицы)

Возвращает ВСЕ строки из правой таблицы + совпадающие из левой:

SELECT u.id, u.name, o.order_id, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

4. FULL OUTER JOIN (все из обеих таблиц)

Возвращает все строки из ОБЕИХ таблиц:

SELECT u.id, u.name, o.order_id, o.amount
FROM users u
FULL OUTER JOIN orders o ON u.id = o.user_id;

5. CROSS JOIN (декартово произведение)

Объединяет каждую строку одной таблицы с каждой строкой другой:

SELECT u.id, u.name, o.order_id
FROM users u
CROSS JOIN orders o;
# Если users 3 строки, orders 4 строки -> результат 12 строк

Интеграция с Python (SQLAlchemy)

1. Базовый INNER JOIN

from sqlalchemy import Column, Integer, String, ForeignKey, select
from sqlalchemy.orm import relationship, Session
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(100))
    orders = relationship('Order', back_populates='user')

class Order(Base):
    __tablename__ = 'orders'
    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users.id'))
    amount = Column(Integer)
    user = relationship('User', back_populates='orders')

# Способ 1: Через relationship
session = Session(engine)
users = session.query(User).join(Order).all()

for user in users:
    for order in user.orders:
        print(f"{user.name}: {order.amount}")

# Способ 2: Явный JOIN
result = session.query(User, Order).join(Order).all()

for user, order in result:
    print(f"{user.name}: {order.amount}")

2. LEFT JOIN

from sqlalchemy.orm import joinedload

# Все пользователи, даже без заказов
users = session.query(User).outerjoin(Order).all()

# Более эффективно с joinedload
users = session.query(User).options(
    joinedload(User.orders)
).all()

for user in users:
    orders_count = len(user.orders)
    print(f"{user.name}: {orders_count} заказов")

3. Условный JOIN

# JOIN с дополнительным условием
result = session.query(User, Order).join(
    Order,
    (User.id == Order.user_id) & (Order.amount > 50)
).all()

for user, order in result:
    print(f"{user.name}: {order.amount} (> 50)")

4. Multiple JOINs (несколько таблиц)

class Comment(Base):
    __tablename__ = 'comments'
    id = Column(Integer, primary_key=True)
    order_id = Column(Integer, ForeignKey('orders.id'))
    text = Column(String(500))

# SELECT из 3 таблиц
result = session.query(User, Order, Comment).join(
    Order, User.id == Order.user_id
).join(
    Comment, Order.id == Comment.order_id
).all()

for user, order, comment in result:
    print(f"{user.name} -> Order {order.id} -> {comment.text}")

Raw SQL с ORM

from sqlalchemy import text

# Если нужна гибкость raw SQL
query = text("""
    SELECT u.id, u.name, o.order_id, o.amount
    FROM users u
    INNER JOIN orders o ON u.id = o.user_id
    WHERE o.amount > :min_amount
    ORDER BY o.amount DESC
""")

result = session.execute(query, {"min_amount": 50}).fetchall()

for row in result:
    print(f"User {row.id} ({row.name}): ${row.amount}")

Практические примеры

Пример 1: Пользователи и их последний заказ

from sqlalchemy import func, desc

# Найти последний заказ каждого пользователя
result = session.query(
    User.name,
    Order.id,
    Order.amount,
    Order.created_at
).join(
    Order
).filter(
    Order.created_at == session.query(
        func.max(Order.created_at)
    ).filter(Order.user_id == User.id).correlate(User)
).all()

for name, order_id, amount, created_at in result:
    print(f"{name}: Order {order_id} ({amount}) - {created_at}")

Пример 2: Агрегирование с JOIN

from sqlalchemy import func

# Количество заказов и сумма для каждого пользователя
result = session.query(
    User.name,
    func.count(Order.id).label('order_count'),
    func.sum(Order.amount).label('total_amount')
).outerjoin(Order).group_by(User.id).all()

for name, count, total in result:
    print(f"{name}: {count} заказов на сумму ${total}")

Пример 3: JOIN на одной таблице (self-join)

# Найти мене преподавателей
from sqlalchemy.orm import aliased

Employee = aliased(User)
Manager = aliased(User)

result = session.query(
    Employee.name,
    Manager.name
).join(
    Manager,
    Employee.manager_id == Manager.id
).all()

for emp, mgr in result:
    print(f"{emp} работает на {mgr}")

Производительность

# ПЛОХО: N+1 проблема
users = session.query(User).all()  # 1 запрос
for user in users:
    orders = session.query(Order).filter_by(user_id=user.id).all()  # N запросов
    print(f"{user.name}: {len(orders)} заказов")

# ХОРОШО: одним запросом
result = session.query(User, Order).outerjoin(Order).all()  # 1 запрос!

# ИЛИ: joinedload для отложенной загрузки
users = session.query(User).options(
    joinedload(User.orders)
).all()  # 1 запрос

Типичные ошибки

# ОШИБКА 1: Неправильное условие JOIN
result = session.query(User, Order).filter(
    User.id == Order.user_id
).all()  # Это неявный CROSS JOIN

# ПРАВИЛЬНО: явный JOIN
result = session.query(User, Order).join(
    Order,
    User.id == Order.user_id
).all()

# ОШИБКА 2: Забыть about duplicates
result = session.query(User).join(Order).all()
# Если у пользователя 2 заказа, он будет в результате 2 раза!

# ПРАВИЛЬНО: Использовать distinct
result = session.query(User).join(Order).distinct().all()

Лучшие практики

  • Используй INNER JOIN по умолчанию — это самый частый случай
  • Явно указывай условие JOIN — не полагайся на default
  • Избегай N+1 queries — используй joinedload или eager loading
  • Проверяй EXPLAIN для сложных запросов
  • Индексируй столбцы с внешними ключами
  • Используй aliases для self-joins — более читаемо
  • Документируй сложные JOINs — они могут быть трудны для понимания

Младший выбор между различными типами JOIN зависит от структуры данных и того, какие записи тебе нужны. INNER JOIN используется чаще всего, но LEFT и FULL OUTER JOIN часто необходимы для полноты результатов.