Как выполнить самореферентный коррелированный подзапрос с помощью SQLAlchemy?

#python #sqlalchemy

#python #sqlalchemy

Вопрос:

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

У меня есть что-то вроде связанного списка, реализованного следующим образом:

 class TransactionModel(Base):
    __tablename__ = 'transactionlog'
    chain = Column(String(36), primary_key = True)
    id = Column(String(36), primary_key = True)
    overwrite_id = Column(String(36))
    user_id = Column(Integer, ForeignKey('users.id'))
    timestamp = Column(DateTime, nullable=False, server_default=func.now())
    user = relationship(UserModel, foreign_keys=[user_id])

    __table_args__ = (
        ForeignKeyConstraint(
            ['chain', 'overwrite_id'],
            ['transactionlog.chain', 'transactionlog.id']
        ),
    )
  

Большинство вещей кажутся довольно простыми:

 # Prev Item in list
TransactionModel.query.filter((TransactionModel.chain == current.chain) amp; (TransactionModel.id == current.overwrite_id)).one()
# Next item in list
TransactionModel.query.filter((TransactionModel.chain == current.chain) amp; (TransactionModel.overwrite_id == current.id)).one()
  

Но я не могу понять, как получить последний элемент в списке. Если бы я писал SQL вручную, я бы сделал это:

 SELECT * 
FROM transactionlog AS t
WHERE chain = "somevalue"
AND NOT EXISTS (
    SELECT *
    FROM transactionlog AS other
    WHERE other.chain = t.chain
    AND other.overwrite_id = t.id
)
  

Однако я не могу понять, как перевести это в SQLAlchemy. Может кто-нибудь указать мне правильное направление?

Ответ №1:

Если бы я должен был написать это в SQLAlchemy, я бы использовал sqlalchemy.exists() and sqlalchemy.orm.aliased . Последнее позволяет вам иметь по существу именованную копию модели, чтобы вы могли различать одну и ту же таблицу:

 from sqlalchemy import exists
from sqlalchemy.orm import aliased

other = aliased(TransactionModel)

TransactionModel.query.filter(
    TransactionModel.chain == "somevalue",
    ~exists().where(
        (other.overwrite_id == TransactionModel.id) amp;
        (other.chain == TransactionModel.chain)
    )
)
  

или эквивалентно (используя and_ вместо amp; )

 from sqlalchemy import and_, exists
from sqlalchemy.orm import aliased

other = aliased(TransactionModel)

TransactionModel.query.filter(
    TransactionModel.chain == "somevalue",
    ~exists().where(and_(
        other.overwrite_id == TransactionModel.id,
        other.chain == TransactionModel.chain
    ))
)
  

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

1. Мне пришлось заменить other.c часть на simply other . После этого он отлично работал. Мне также нравится, что это выглядит довольно близко к тому, что я написал бы в обычном SQL. Спасибо!