SQLAlchemy, как отношение group_by? - PullRequest
0 голосов
/ 08 февраля 2019

Вот модели:

class User(Base):
    __tablename__ = 'users'

    id = Column(CHAR, primary_key=True)
    first_name = Column(CHAR)
    last_name = Column(CHAR)
    email = Column(CHAR)
    receive_reports = Column(Boolean)


class MailPiece(Base):
    __tablename__ = 'mail_pieces'

    id = Column(CHAR, primary_key=True)
    created_at = Column(DateTime)
    template_id = Column(CHAR, ForeignKey('templates.id'))


class Template(Base):
    __tablename__ = 'templates'

    id = Column(CHAR, primary_key=True)
    name = Column(CHAR)
    created_by_id = Column(CHAR, ForeignKey('users.id'))
    user = relationship(User, backref='templates')

Я хочу отправлять пользователям отчеты с: с какими шаблонами они отправлены и сколько почтовых отправлений было отправлено для каждого шаблона.

Я написалкод:

        stmt = self.session.query(MailPiece.template_id, func.count('*')
                               .label('mail_pieces_count')).filter(
            MailPiece.created_at > day_ago,
            MailPiece.created_at < now,
            ).group_by(MailPiece.template_id).subquery()

        query = self.session.query(Template, stmt.c.mail_pieces_count).\
            filter(Template.user.has(receive_reports=True)).\
            join(stmt, Template.id == stmt.c.template_id)

Но это немного не то, что я хотел.Я получил результат, сгруппированный по шаблонам, но мне нужен список, сгруппированный по пользователям, где у каждого пользователя будут шаблоны.Таким образом, я могу перебирать список пользователей и отправлять каждому пользователю отчет со сводкой.

Текущий результат:

[<Template(id='123', name='Test', mail_pieces_count='123', user=<User(id=1212, first_name='Some name', last_name='Some lastname')>)>, <Template(id='456', name='Test2', mail_pieces_count='456', user=<User(id=1212, first_name='Some name', last_name='Some lastname')>)>]

Ожидаемый результат:

[<User(id=1212, first_name='Some name', last_name='Some lastname, templates=[<Template(id='123', name='Test', mail_pieces_count='123')>, <Template(id='456', name='Test2', mail_pieces_count='456')>,])>]

Другими словами, теперь его можно представить так: Текущий:

templates = [
    {
        "id": 123,
        "name": "Test",
        "mail_pieces_count": 123,
        "user": {
            "id": 1212,
            "first_name": "Some name",
            "last_name": "Some lastname"
        }
    },
    {
        "id": 456,
        "name": "Test2",
        "mail_pieces_count": 456,
        "user": {
            "id": 1212,
            "first_name": "Some name",
            "last_name": "Some lastname"
        }
    }
]

Ожидаемый:

users = [
    {
        "id": 1212,
        "first_name": "Some name",
        "last_name": "Some lastname",
        "templates": [
        {
            "id": 123,
            "name": "Test",
            "mail_pieces_count": 123
        },
        {
            "id": 456,
            "name": "Test2",
            "mail_pieces_count": 456
        },]
    },
]

1 Ответ

0 голосов
/ 11 февраля 2019

Причина текущего вывода:

Вы строите запрос, используя Класс шаблона :

self.session.query(Template, stmt.c.mail_pieces_count)

Это основная причина, по которой вы получили Template экземпляроввместо User.Ниже приведен только пример того, как получить ожидаемый результат.

Примечание! Я просто пытаюсь объяснить, как это работает.Мой ответ не связан с оптимизацией, производительностью и т. Д.

# I skipped filters...
stmt = (session.query(MailPiece.template_id, func.count('*')
        .label('mail_pieces_count'))
        .group_by(MailPiece.template_id).subquery())
# to tie templates with upper subquery
stmt2 = (session.query(Template.created_by_id, stmt.c.mail_pieces_count)
         .join(stmt, Template.id == stmt.c.template_id)
         .subquery())
# User query - to tie template id with user id
query = session.query(User, stmt2.c.mail_pieces_count).join(
    stmt2,
    # you can add additional conditions into JOIN ON...
    and_(User.id == stmt2.c.created_by_id, User.receive_reports.is_(True))
)

for result in query:
    print("count: %s" % result.mail_pieces_count)
    print("user: %s" % result.User)
    print("templates: %s" % result.User.templates)

JFYI. Если мы посмотрим на консоль, вы обнаружите, что алхимия будет выполнять 1 запрос за итерацию:

### one more select when you use result.User.templates
2019-02-11 13:02:24,702 INFO sqlalchemy.engine.base.Engine SELECT templates.id AS templates_id, templates.name AS templates_name, templates.created_by_id AS templates_created_by_id 
FROM templates 
WHERE %(param_1)s = templates.created_by_id

Вы можете добавить Template в запрос, чтобы избежать этого, но в этом случае вы получите количество записей, равное количеству шаблонов (не пользователей ):

stmt2 = (session.query(Template, stmt.c.mail_pieces_count)
         .join(stmt, Template.id == stmt.c.template_id)
         .subquery())
# select User and Template 
query = session.query(User, Template, stmt2.c.mail_pieces_count).join(
    stmt2,
    and_(User.id == stmt2.c.created_by_id)
)

for result in query.all():
    print("user: %s" % result.User)
    print("template: %s" % result.Template)

Итак, подытожим, вам нужно использовать определенный класс , если вам нужно получить экземпляров класса в результате:

session.query(ExpectedClass).other_methods()...

Надеюсь, это поможет.

...