Pull to refresh

SQLAlchemy rocks!

Reading time2 min
Views2.1K
Сегодня занимался модельками своего блога и решил приделать к 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 нервно курят в сторонке ;).
Tags:
Hubs:
Total votes 13: ↑12 and ↓1+11
Comments8

Articles