创建一个 hybrid_property 到 return 值的前一条记录

Create a hybrid_property to return the value of a previous record

我在尝试创建一个 hybrid_property 到 return 之前记录的值时已经做到了这一点:

from datetime import date
from sqlalchemy import Column, Integer, Date, select, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import sessionmaker

Base = declarative_base()


class A(Base):

    __tablename__ = "a"

    id_ = Column(Integer, primary_key=True)
    record_date = Column(Date)
    example_value = Column(Integer)

    @hybrid_property
    def prev_value(self):
        return
    
    @prev_value.expression
    def prev_value(cls):
        stmt = select(A.example_value)
        stmt = stmt.order_by(A.record_date.desc())
        stmt = stmt.limit(1)
        stmt = stmt.label("prev_value")
        return stmt


engine = create_engine("sqlite:///:memory:")
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

session.add(A(record_date=date(2022, 5 ,1), example_value=1))
session.add(A(record_date=date(2022, 5 ,2), example_value=2))
session.add(A(record_date=date(2022, 5 ,3), example_value=3))
session.commit()

prev_value = session.execute(select(A.prev_value).where(A.id_ == 3)).scalar()
print(prev_value)

当前 returning None 应该 return 2

我应该在 in-Python 方法和表达式变体中输入什么?

解决方案:

@prev_value.expression
def prev_value(cls):
    A1 = aliased(A, name="a_prev")
    stmt = select(A1.example_value)
    stmt = stmt.filter(A1.record_date < cls.record_date)
    stmt = stmt.order_by(A1.record_date.desc())
    stmt = stmt.limit(1)
    stmt = stmt.label("prev_value")
    return stmt

说明

当我运行逐字逐句地从你的问题中提取代码时(使用sqlite),我得到的结果实际上是3而不是你指出的None。事实上,对于查询 session.execute(select(A, A.prev_value)) # (1):

要求的所有行,所有行返回的结果都是 3 的相同值
(<A [1] (example_value = 1, id_ = 1, record_date = datetime.date(2022, 5, 1))>, 3)
(<A [2] (example_value = 2, id_ = 2, record_date = datetime.date(2022, 5, 2))>, 3)
(<A [3] (example_value = 3, id_ = 3, record_date = datetime.date(2022, 5, 3))>, 3)

为什么我的示例代码得到 3

我认为这是因为 sub-query 没有任何条件 link 将其添加到请求的行。假设之前的值应该是之前的“by record_date”,添加到查询中的 link 应该是:

stmt = stmt.filter(A.record_date < cls.record_date)

运行 但是,它现在将为所有结果生成 None。我们看一下生成的SQL以及发现none行的原因:

SELECT a.id_,
       a.record_date,
       a.example_value,

  (SELECT a.example_value
   FROM a
   WHERE a.record_date < a.record_date  # >>> the ISSUE is here: always FALSE
   ORDER BY a.record_date DESC
   LIMIT 1) AS prev_value
FROM a

问题是主查询和 sub-query 指向同一个 table/view。

解决子查询: 为了解决它,我们只需要显式创建一个sub-query,问题就解决了:

@prev_value.expression
def prev_value(cls):
    A1 = aliased(A, name="a_prev")
    stmt = select(A1.example_value)
    stmt = stmt.filter(A1.record_date < cls.record_date)
    stmt = stmt.order_by(A1.record_date.desc())
    stmt = stmt.limit(1)
    stmt = stmt.label("prev_value")
    return stmt

并且相同的查询 (1) 产生以下结果:

(<A [1] (example_value = 1, id_ = 1, record_date = datetime.date(2022, 5, 1))>, None)
(<A [2] (example_value = 2, id_ = 2, record_date = datetime.date(2022, 5, 2))>, 1)
(<A [3] (example_value = 3, id_ = 3, record_date = datetime.date(2022, 5, 3))>, 2)

根据以下生成SQL

SELECT a.id_,
       a.record_date,
       a.example_value,

  (SELECT a_prev.example_value
   FROM a AS a_prev
   WHERE a_prev.record_date < a.record_date
   ORDER BY a_prev.record_date DESC
   LIMIT 1) AS prev_value
FROM a