SQLAlchemy: частично указанная связь - PullRequest
3 голосов
/ 11 февраля 2020

Я пытаюсь описать отношения SQLAlchemy, которые существуют в устаревшей схеме SQL. Я перевел схему на:

class Lang(Base):
    __tablename__ = "tblLang"
    id = Column("ID", Integer, primary_key=True)
    # ...

class ItemName(Base):
    __tablename__ = "tblItemName"
    # ItemName has a composite primary key
    id = Column("ID", Integer, primary_key=True)
    lang_id = Colunm("Lang", Integer, ForeignKey("Lang.id"), primary_key=True)
    name = Column("Name", String)

class Item(Base):
    __tablename__ = "tblItem"
    id = Column("id", Integer, primary_key=True)
    inventory = Column("Inventory", Integer)
    # item_name_id specifies only part of the table
    # relationship
    item_name_id = Column("ItemNameID", Integer, ForeignKey("tblItemName.ID"))
    # lang_id should come from outside the database:
    # a user preference stored in a configuration file
    # or a session cookie, etc.
    item_name = relationship(???)

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

I sh, чтобы можно было создать запрос объект, значение которого было установлено до выполнения запроса.

Мой вопрос: Что я должен поставить вместо ??? в приведенном выше коде, чтобы можно было создать такой запрос, и как Я указываю lang_id до времени выполнения?


Редактировать:

За @ sanya sh комментарий ниже: вот Пример запроса, который я надеюсь получить:

SELECT tblItemName.Name, tblItem.Inventory
FROM tblItem 
  LEFT JOIN tblTiemName 
    ON tblItemName.ID = tblTiem.NameID 
    AND tblItem.Lang = :lang

с :lang, уже установленным на 2 (получено из пользовательских настроек)

При записи что-то вроде:

query = session.query(Item).with_entities([
    Iten.c.item_name.name, 
    Item.c.inventory
])

Ответы [ 2 ]

2 голосов
/ 27 февраля 2020

Что касается определения relationship, вы можете просто использовать пользовательское условие primaryjoin. См. Указание альтернативных условий соединения для документации.

relationship(
    ItemName,
    uselist=False,
    primaryjoin=f"and_(Item.item_name_id==ItemName.id, ItemName.lang_id=={LANG_ID})",
)

Что касается того, когда требуемый lang_id станет доступным для вашего приложения, вы можете просто определить (или переопределить) relationship через много времени после определения модели и полностью вне отображения Item модели:

# ....
# now define/assign the relationship once the LANG_ID is known

LANG_NAME = "English"
LANG_ID = session.query(Lang).filter(Lang.name == LANG_NAME).one().id
print(LANG_ID, LANG_NAME)

Item.item_name = relationship(
    ItemName,
    uselist=False,
    primaryjoin=f"and_(Item.item_name_id==ItemName.id, ItemName.lang_id=={LANG_ID})",
)

Однако, если язык настроен для пользователя (вошел в систему), вам может потребоваться использовать более позднюю привязку или сохранить настройку языка в контексте сеанса, например.

В любом случае вы можете затем создать требуемый запрос, как показано ниже:

q = (
    session
    .query(ItemName.name, Item.inventory)
    .select_from(Item)
    .join(ItemName, Item.item_name)
)

for res in q:
    print(res)
0 голосов
/ 24 февраля 2020

ВНИМАНИЕ: это обходной путь, который может быть неоптимальным или даже не правильным

Я не смог найти заклинание магии c, которое заставит отношения делать то, что я хочу , Вместо этого я могу написать обходной путь:

from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method


class Item(Base):
    __tablename__ = "tblItem"
    id = Column("id", Integer, primary_key=True)
    inventory = Column("Inventory", Integer)

    @hybrid_method
    def item_name(self, lang):
        return ItemName.query.filter(
            ItemName.id == self.id,
            ItemName.lang_id == lang
        )

    @hybrid_property
    def item_names(self):
        return ItemName.query.filter(ItemName.id == self.id)

Затем я могу использовать его следующим образом:

item_names = Item.get(123).item_names.all()
item_klingon_name = Item.get(123).item(KLINGON_LANG_ID).one()

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...