在SQLAlchemy中,为什么在映射任意选择时必须别名select构造?

4 投票
1 回答
4259 浏览
提问于 2025-04-17 16:54

我尝试按照文档中的代码来实现,具体是关于如何将模型映射到任意表的内容,链接在这里:映射模型到任意表,但是我遇到了以下错误:

sqlalchemy.exc.InvalidRequestError: When mapping against a select() construct, map against an alias() of the construct instead.This because several databases don't allow a SELECT from a subquery that does not have an alias.

这是我实现代码示例的方式。

from sqlalchemy import (
    select, func,
    Table, Column,
    Integer, ForeignKey,
    MetaData,
)

from sqlalchemy.ext.declarative import declarative_base

metadata = MetaData()
Base = declarative_base()

customers = Table('customer', metadata,
                  Column('id', Integer, primary_key=True),
                  )

orders = Table('order', metadata,
               Column('id', Integer, primary_key=True),
               Column('price', Integer),
               Column('customer_id', Integer, ForeignKey('customer.id')),
               )

subq = select([
            func.count(orders.c.id).label('order_count'),
            func.max(orders.c.price).label('highest_order'),
            orders.c.customer_id
            ]).group_by(orders.c.customer_id).alias()

customer_select = select([customers,subq]).\
            where(customers.c.id==subq.c.customer_id)

class Customer(Base):
    __table__ = customer_select

我可以通过使用以下方法让它工作:

class Customer(Base):
    __table__ = customer_select.alias()

不幸的是,这样做会把所有查询放在一个子查询中,这样会非常慢。

有没有办法将模型映射到任意选择的结果上?这是文档中的错误吗?因为文档里的代码示例在我这里(在sqlalchemy==0.8.0b2或0.7.10版本)无法正常工作。

1 个回答

6

如果一个子查询变得“非常慢”,这很可能是因为你在使用MySQL。正如我在其他地方提到的,MySQL的子查询性能真的很差。

在进行选择时,需要把这个语句用括号括起来,基本上是为了防止它内部的结构泄露到外部的上下文中,同时也为了不需要改变select()这个结构本身。

想象一下,如果你要映射到这样的SELECT:

SELECT id, name FROM table WHERE id = 5

现在假设你要从这个映射中选择:

ma = aliased(Mapping)
query(Mapping).join(ma, Mapping.name == ma.name)

如果你的映射选择没有使用别名,SQLAlchemy就必须在原地修改你的select(),这意味着它需要深入理解这个选择的结构,而这可能会非常复杂(想想如果它有LIMIT、ORDER BY、GROUP BY、聚合等):

SELECT id, name FROM table 
 JOIN (SELECT id, name FROM table) as anon_1 ON table.name = anon_1.name 
 WHERE table.id = 5

而当它是自包含的,SQLAlchemy就可以不去了解你的select()的结构,像对待其他表一样使用它:

SELECT id, name FROM (SELECT id, name FROM table WHERE id = 5) as a1 
JOIN (SELECT id, name FROM table WHERE id = 5) as a2 ON a1.name = a2.name

实际上,如今映射到SELECT语句的情况很少见,因为Query构造通常足够灵活,可以满足你需要的任何模式。SQLAlchemy的早期版本主要使用mapper()作为查询对象,内部确实在某些情况下利用了mapper()的灵活性(特别是在连接继承时),但这种显式的方法并不常用。

这是文档错误吗?文档中的代码示例在我使用sqlalchemy==0.8.0b2或0.7.10时不起作用。

谢谢你指出这个问题。我已经修正了示例,并添加了一个重要说明,不鼓励这种做法,因为它的使用并不多(我知道这一点,因为我从来不使用它)。

撰写回答