Связь SQLAlchemy: список объектов, соответствующих одному из двух столбцов внешнего ключа

#python #sqlalchemy #relationship

Вопрос:

В моей базе данных есть две таблицы: одна для матчей и одна для команд. В матче есть столбцы, в которых хранятся идентификаторы играющих команд.

 class Match(Base):
    id_ = Column(UUID(as_uuid=True, primary_key=True)
    team1_id = Column(UUID(as_uuid=True), ForeignKey('teams.id')
    team2_id = Column(UUID(as_uuid=True), ForeignKey('teams.id')

class Team(Base):
    id_ = Column(UUID(as_uuid=True, primary_key=True)
 

Я хотел бы создать взаимосвязь между двумя таблицами таким образом, чтобы:

  • Match.teams возвращает список из двух Team объектов, содержащий команды с идентификаторами, совпадающими team1_id или team2_id
  • Team.matches возвращает список Match объектов, содержащий все совпадения, в которых идентификатор этой команды присутствует в team1_id team2_id столбцах или

С точки зрения SQL мне не нужна другая таблица — вся информация о взаимоотношениях присутствует в таблицах, как указано выше. Однако требуется ли SQLAlchemy таблица ассоциаций для этой работы?

Попытка 1:

 class Match(Base):
    ...
    teams = relationship('Team', back_populates='matches', lazy='selectin',
                         foreign_keys=[team1_id, team2_id])

class Team(Base):
    ...
    matches = relationship('Match', back_populates='teams', lazy='selectin')
 

В результате возникла следующая ошибка:

 sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join condition between parent/child tables on relationship Match.teams - there are multiple foreign key paths linking the tables.
Specify the 'foreign_keys' argument, providing a list of those columns which should be counted as containing a foreign key reference to the parent table.
 

Что смутило меня, когда я подумал, что именно это я и делал! Что я сделал не так?

Попытка 2:

 class Match(Base):
    ...
    teams = relationship('Team',
                         back_populates='matches',
                         lazy='selectin',
                         primaryjoin=('or_(Team.id==Match.team1_id,'
                                          'Team.id==Match.team2_id)'))


class Team(Base):
    ...
    matches = relationship('Match', back_populates='teams', lazy='selectin')
 

Это привело к Match.teams возвращению одного значения первой команды с идентификатором, соответствующим team1_id или team2_id .

Возможно ли то, что я хочу сделать? Как это должно быть сделано? Можно ли обойтись без создания еще одной таблицы в базе данных?

Ответ №1:

Для этого можно использовать свойство, которое может быть легче прочитать:

 class Match(Base):
    ...
    @property
    def teams(self) -> Tuple[Team, Team]:
        return session.get(Team, self.team1_id), session.get(Team, self.team2_id)

 

Вы бы использовали его так же, как и любой другой атрибут: match.teams это кортеж командных объектов.

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

1. Учитывая, что это вариант, почему relationship существуют sqlalchemy s? В чем видимое преимущество?

2. Обратите внимание, что это не будет работать с асинхронными базами данных

3. Это relationship просто и легко для обычного случая: это поле относится к этому полю. Он дает вам описание на английском языке, и SQLAlchemy выполняет запросы за вас. Пример в вопросе, безусловно, необычный случай, поэтому я не удивлен, что он не поддерживается из коробки.