SQLAlchemy 'entity' for `add_columns` 没有 table 支持

SQLAlchemy 'entity' for `add_columns` not backed by a table

使用像这样的 SQLAlchemy 查询:

result = db.session.query(Model).add_columns(
    func.min(Model.foo).over().label("min_foo"),
    func.max(Model.foo).over().label("max_foo"),
    # ...
)

结果是一个可迭代的元组,首先由 Model 行组成,然后是添加的列。

我怎样才能:

  1. 将添加的列贡献给 Model,这样就可以像 model.min_foo 等一样从每个元素访问它们;或者
  2. 将添加的列映射到一个单独的数据类中,这样它们就可以像extra.min_foo?

我在这里试图实现的主要目标是按名称访问 - 例如给定的标签 - 而不是将它们全部枚举为 model, min_foo, max_foo, ... 并依赖于保持相同的顺序。对于 model, *extraextra 只是聚合值的普通列表,没有对标签的引用。

如果我先将列动态添加到模型中:

Model.min_foo = Column(Numeric)

然后它抱怨:

Implicitly combining column modeltable.min_foo with column modeltable.min_foo under attribute 'min_foo'. Please configure one or more attributes for these same-named columns explicitly

Apparently the solution 即显式加入 table。但这不是一个!

看来 'mappers' 应该可以做到这一点,但我找不到任何未明确映射到 'table name' 或其列的示例,我没有真的在这里 - 我不清楚 if/how 它们可以与聚合一起使用,或者查询中实际上没有存储在任何 table.[=24= 中的其他 'virtual' 列]

我认为你要找的是 Query-time SQL expressions as mapped attributes:

from sqlalchemy import create_engine, Column, Integer, select, func
from sqlalchemy.orm import (Session, declarative_base, query_expression,
                            with_expression)

Base = declarative_base()


class Model(Base):
    __tablename__ = 'model'

    id = Column(Integer, primary_key=True)
    foo = Column(Integer)
    foo2 = Column(Integer, default=0)


engine = create_engine('sqlite:///', future=True)

Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)

with Session(engine) as session:
    session.add(Model(foo=10))
    session.add(Model(foo=20))
    session.add(Model(foo=30))
    session.add(Model(foo=40))
    session.add(Model(foo=50, foo2=1))
    session.add(Model(foo=60, foo2=1))
    session.add(Model(foo=70, foo2=1))
    session.add(Model(foo=80))
    session.add(Model(foo=90))
    session.add(Model(foo=100))

    session.commit()

    Model.min_foo = query_expression(func.min(Model.foo).over())
    stmt = select(Model).where(Model.foo2 == 1)
    models = session.execute(stmt).all()
    for model, in models:
        print(model.min_foo)


with Session(engine) as session:
    Model.max_foo = query_expression()
    stmt = select(Model).options(with_expression(Model.max_foo,
                                 func.max(Model.foo).over())
                                 ).where(Model.foo2 == 1)
    models = session.execute(stmt).all()
    for model, in models:
        print(model.max_foo)

您可以在定义 query_expression 时定义一个 default 表达式,或者使用 .optionswith_expression 您可以定义一个运行时表达式。唯一的问题是 Mapped 属性无法取消映射,并且将 return None for max_foo 因为没有定义默认表达式。