Используйте column_property (с вычислением), чтобы определить отношение в SQLAlchemy - PullRequest
0 голосов
/ 09 февраля 2020

У меня есть следующая установка:

Набор соревнований, проводимых в указанные даты, глобальные участники с датами рождения, которые могут быть зарегистрированы для некоторых из этих соревнований, возрастные классы, определенные для каждого соревнования и, наконец, регистрация конкретного c участника для конкретного c конкурса. Эти регистрации должны наконец содержать правильный возрастной класс. Я мог бы добиться этого, например, hybrid_property, но тогда я столкнулся бы с проблемой N + 1. Моя цель состоит в том, чтобы загружать регистрации с помощью одного большого объединенного запроса, включая возрастные классы.

Вот что я пробовал до сих пор:

from sqlalchemy import create_engine, Column, ForeignKey, Integer, Date, select, func, cast, and_
from sqlalchemy.ext.declarative import declarative_base, declared_attr
from sqlalchemy.orm import scoped_session, sessionmaker, relationship, column_property, joinedload, Load, foreign, remote

import transaction
import inflection
import datetime

import logging
logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

engine = create_engine('sqlite://')
db = scoped_session(sessionmaker())

db.configure(bind=engine)


DBModel = declarative_base()
DBModel.metadata.bind = engine


class Competition(DBModel):
    __tablename__ = 'competitions'
    id = Column(Integer, primary_key=True)
    date = Column(Date, nullable=False)


class Competitor(DBModel):
    __tablename__ = 'competitors'
    id = Column(Integer, primary_key=True)
    birthdate = Column(Date, nullable=False)


class AgeClass(DBModel):
    __tablename__ = 'age_classes'
    id = Column(Integer, primary_key=True)
    competition_id = Column(ForeignKey(Competition.id), nullable=False)
    min = Column(Integer, nullable=False)

    competition = relationship(Competition)


class Registration(DBModel):
    __tablename__ = 'registrations'
    id = Column(Integer, primary_key=True)
    competition_id = Column(ForeignKey(Competition.id), nullable=False)
    competitor_id = Column(ForeignKey(Competitor.id), nullable=False)

    competition = relationship(Competition)
    competitor = relationship(Competitor)

    age = column_property(select([cast((func.julianday(Competition.date) - func.julianday(Competitor.birthdate)) / 365.2425, Integer)]).
                          where(and_(Competition.id == competition_id,
                                     Competitor.id == competitor_id)).
                          correlate_except())

    age_class_id = column_property(select([AgeClass.id]).
                                   where(and_(AgeClass.competition_id == competition_id,
                                              AgeClass.min <= age)).
                                   order_by(AgeClass.min.desc()).limit(1).
                                   correlate_except(AgeClass))

#    age_class = relationship(AgeClass, viewonly=True)
#    age_class = relationship(AgeClass, foreign_keys=[age_class_id], viewonly=True)
#    age_class = relationship(AgeClass, primaryjoin=AgeClass.id == remote(age_class_id), viewonly=True)
    age_class = relationship(AgeClass, primaryjoin=foreign(AgeClass.id) == remote(age_class_id), viewonly=True)


DBModel.metadata.create_all()

with transaction.manager:
    competition = Competition(date=datetime.date(2020, 2, 9))
    competitor = Competitor(birthdate=datetime.date(2000, 2, 9))
    db.add(AgeClass(competition=competition, min=6))
    db.add(AgeClass(competition=competition, min=10))
    db.add(Registration(competition=competition, competitor=competitor))

query = db.query(Registration).options(joinedload(Registration.competition, innerjoin=True),
                                       joinedload(Registration.competitor, innerjoin=True),
                                       joinedload(Registration.age_class, innerjoin=True),
                                       Load(Registration).raiseload('*'))
print([x.age_class for x in query.first()])

Первые две версии атрибут age_class приводит к сообщению об ошибке

sqlalchemy.ex c .NoForeignKeysError: Не удалось определить условие соединения между родительскими / дочерними таблицами для отношения Registration.age_class - нет внешних ключей, связывающих эти таблицы , Убедитесь, что ссылающиеся столбцы связаны с ForeignKey или ForeignKeyConstraint, или укажите выражение «primaryjoin».

Поэтому я попытался указать условие первичного соединения, которое выдает мне следующее сообщение об ошибке для версий 3 и 4:

sqlalchemy.ex c .ArgumentError: Relationship Registration.age_class не может определить однозначные пары локальных / удаленных столбцов на основе условия соединения и аргументов remote_side. Попробуйте использовать аннотацию remote (), чтобы точно пометить те элементы условия соединения, которые находятся на удаленной стороне отношения.

Есть ли шанс запустить это или я пытаюсь что-то сделать здесь невозможно? Или есть другой способ подойти к этому вопросу?

...