Sqlalchemy:FROM中的子查询必须有别名

时间:2016-01-15 02:05:14

标签: python postgresql select sqlalchemy psycopg2

如何构建这个sqlalchemy查询以便它做正确的事情?

我已经给出了我能想到别名的一切,但我还是得到了:

ProgrammingError: (psycopg2.ProgrammingError) subquery in FROM must have an alias
LINE 4: FROM (SELECT foo.id AS foo_id, foo.version AS ...

此外,正如IMSoP所指出的那样,它似乎试图将其转变为交叉连接,但我只是希望它在同一个表上通过子查询加入一个表。

这是sqlalchemy:

(注意:我已将其重写为尽可能完整且可从python shell运行的独立文件)

from sqlalchemy import create_engine, func, select
from sqlalchemy import Column, BigInteger, DateTime, Integer, String, SmallInteger
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

engine = create_engine('postgresql://postgres:#######@localhost:5435/foo1234')
session = sessionmaker()
session.configure(bind=engine)
session = session()

Base = declarative_base()

class Foo(Base):
     __tablename__ = 'foo'
     __table_args__ = {'schema': 'public'}
     id = Column('id', BigInteger, primary_key=True)
     time = Column('time', DateTime(timezone=True))
     version = Column('version', String)
     revision = Column('revision', SmallInteger)

foo_max_time_q = select([
     func.max(Foo.time).label('foo_max_time'),
     Foo.id.label('foo_id')
 ]).group_by(Foo.id
 ).alias('foo_max_time_q')

foo_q = select([
    Foo.id.label('foo_id'),
    Foo.version.label('foo_version'),
    Foo.revision.label('foo_revision'),
    foo_max_time_q.c.foo_max_time.label('foo_max_time')
]).join(foo_max_time_q, foo_max_time_q.c.foo_id == Foo.id
).alias('foo_q')

thing = session.query(foo_q).all()
print thing

生成了sql:

SELECT foo_id AS foo_id,
    foo_version AS foo_version,
    foo_revision AS foo_revision,
    foo_max_time AS foo_max_time,
    foo_max_time_q.foo_max_time AS foo_max_time_q_foo_max_time,
    foo_max_time_q.foo_id AS foo_max_time_q_foo_id
    FROM (SELECT id AS foo_id,
        version AS foo_version,
        revision AS foo_revision,
        foo_max_time_q.foo_max_time AS foo_max_time
        FROM (SELECT max(time) AS foo_max_time,
            id AS foo_id GROUP BY id
        ) AS foo_max_time_q)
    JOIN (SELECT max(time) AS foo_max_time,
            id AS foo_id GROUP BY id
    ) AS foo_max_time_q
    ON foo_max_time_q.foo_id = id

这里是玩具表

CREATE TABLE foo (
id bigint ,
time timestamp with time zone,
version character varying(32),
revision smallint
);

我希望得到的SQL(所需的SQL)是这样的:

SELECT foo.id AS foo_id,
       foo.version AS foo_version,
       foo.revision AS foo_revision,
       foo_max_time_q.foo_max_time AS foo_max_time
       FROM foo
       JOIN (SELECT max(time) AS foo_max_time,
            id AS foo_id GROUP BY id
            ) AS foo_max_time_q
        ON foo_max_time_q.foo_id = foo.id

最后的笔记: 我希望尽可能使用select()而不是session.query()来获得答案。谢谢

2 个答案:

答案 0 :(得分:15)

你快到了。制作一个"selectable"子查询,并通过join()

将其与主查询相结合
foo_max_time_q = select([func.max(Foo.time).label('foo_max_time'),
                         Foo.id.label('foo_id')
                        ]).group_by(Foo.id
                         ).alias("foo_max_time_q")

foo_q = session.query(
          Foo.id.label('foo_id'),
          Foo.version.label('foo_version'),
          Foo.revision.label('foo_revision'),
          foo_max_time_q.c.foo_max_time.label('foo_max_time')
                ).join(foo_max_time_q, 
                       foo_max_time_q.c.foo_id == Foo.id)

print(foo_q.__str__())

打印(手动美化):

SELECT 
    foo.id AS foo_id, 
    foo.version AS foo_version, 
    foo.revision AS foo_revision, 
    foo_max_time_q.foo_max_time AS foo_max_time 
FROM 
    foo 
JOIN 
    (SELECT 
         max(foo.time) AS foo_max_time, 
         foo.id AS foo_id 
     FROM 
         foo 
     GROUP BY foo.id) AS foo_max_time_q 
ON 
    foo_max_time_q.foo_id = foo.id

完整的工作代码可在此gist中找到。

答案 1 :(得分:1)

原因

<块引用>

FROM 中的子查询必须有别名

此错误意味着子查询(我们正在尝试对其执行 join)没有别名。
即使我们.alias('t')只是为了满足这个要求,我们也会得到下一个错误:

<块引用>

缺少表“foo”的 FROM 子句条目

那是因为 join on 子句 (... == Foo.id) 对 Foo 不熟悉。
它只知道“左”和“右”表:t(子查询)和foo_max_time_q


解决方案

相反,select_fromFoofoo_max_time_q 的连接。

方法一

.join(B, on_clause) 替换 .select_from(B.join(A, on_clause)

]).join(foo_max_time_q, foo_max_time_q.c.foo_id == Foo.id
]).select_from(foo_max_time_q.join(Foo, foo_max_time_q.c.foo_id == Foo.id)

这在这里有效,因为 A INNER JOIN B 等价于 B INNER JOIN A

方法二

保持连接表的顺序:

from sqlalchemy import join

并将 .join(B, on_clause) 替换为 .select_from(join(A, B, on_clause))

]).join(foo_max_time_q, foo_max_time_q.c.foo_id == Foo.id
]).select_from(join(Foo, foo_max_time_q, foo_max_time_q.c.foo_id == Foo.id)

可以找到 session.query() 的替代方法 here