SQLAlchemy rocks!

    Сегодня занимался модельками своего блога и решил приделать к Entry (это запись в блоге) счётчик комментариев. Представление данных в приложении у меня реализовано с помощью SQLAlchemy, напомню, что это реализация Data Mapper, в отличие от модных сейчас (в основном благодаря Django и Rails) Active Record реализаций слоя источника данных.

    Для подсчёта комментариев у меня нет никакого аккумулятора в таблице entries_table, таким образом, мне надо каждый раз считать count на комментариях. Но как это сделать правильно?



    Загрузка по требованию



    Первое что приходит в голову — сделать аттрибут Entry.comments_count загружаемым по требования (lazy-loading attribute) и повесить на него запрос::

    SELECT COUNT(*) FROM comments_table WHERE comments_table.entry_id == <ENTRY_ID>


    Всё просто, обращаемся к аттрибуту — происходит запрос, не обращаемся — не происходит. Но что, если обращение происходит слишком часто, например как теперь это делается у меня на главной странице в блоге (указано количество комментариев к каждой записи). На это потребуется n+1 запрос, где n — это количество записей на страницу. Не очень хорошо…

    Объединяем таблицы



    А теперь самое интересное: SQLAlchemy умеет мапить классы не только на отдельные таблицы, но и на JOIN'ы и даже на SELECT'ы. То есть всё что нам нужно это составить SQL запрос::

    SELECT <поля из entries_table>, COUNT(comments_table.id)
    FROM entries_table
    LEFT OUTER JOIN comments_table
    ON entries_table.id == comments_table.entry_id
    GROUP BY entries_table.id


    Вот как он выглядит используя описание метаданных (Metadata mapping) SQLAlchemy::

    entries_with_comments = select(
    [
    entries_table,
    func.count(comments_table.c.id).label("comments_count")
    ],
    from_obj=[entries_table.outerjoin(comments_table)],
    group_by=[c for c in entries_table.c]
    ).alias("entries_with_comments")


    Всё довольно просто. Теперь мапим классик Entry на этот SELECT::

    mapper(Entry, schema.entries_with_comments,
    primary_key=[schema.entries_with_comments.c.id],
    )


    Всё, готово! Теперь при получении записей Entry, мы будем иметь у каждого экземпляра аттрибут comments_count. И всё это за один запрос.

    Django.orm и Rails со своим ActiveRecord нервно курят в сторонке ;).
    Поделиться публикацией

    Комментарии 8

      +1
      Когда-то в пытался разобраться с SqlAlchemy, но ниасилил, эта штука показалась мне слишком монстуозной. Может посоветуете, где и что по SqlAlchemy лучше прочитать?
        +1
        Она действительно очень громоздкая, но к сожаления альтернатив практически нету (наверное кроме Dejavu Роберта Брюера). А читать про SQLAlchemy лучше официальную документацию.
      • НЛО прилетело и опубликовало эту надпись здесь
        • НЛО прилетело и опубликовало эту надпись здесь
          • НЛО прилетело и опубликовало эту надпись здесь
              0
              Ну с аггрегацией то понятно :) а вот замапить классик на селект или джоин джанго не умеет
              • НЛО прилетело и опубликовало эту надпись здесь
              0
              Джоинить таблицы из за показа количества комментариев? Не жалко ресурсов?

              Я лично всегда делаю в таблице новостей поле comments_num и просто обновляю это поле при добавлении/удалении комментария.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое