【问题标题】:Sqlalchemy: subquery in FROM must have an aliasSqlalchemy:FROM 中的子查询必须有别名
【发布时间】:2016-04-20 14:01:41
【问题描述】:

我如何构建这个 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() 得到答案。谢谢

【问题讨论】:

  • 该 SQL 在某些方面似乎不完整/不正确 - 它的 )( 多。但是,我可以看到缺少别名的子查询从第 7 行开始 - FROM (SELECT foo.id AS foo_id, - 并在第 17 行结束 - 单个 )
  • 看着它,我认为子查询是由 SQLAlchemy 创建的,因为它将您的查询解释为在 foofoo_max_time_q (FROM foo, (...) as foo_max_time_q) 之间具有隐含的交叉连接 以及您的显式连接规范 (JOIN (...) AS foo_max_time_q ON foo_max_time_q.foo_id = foo.id)。
  • @IMSoP:这就是它的生成方式。这就是整个问题
  • 嗯,这不是整个的问题。 “摆脱错误”不应该是您的目标——如果您设法在第 17 行添加别名,您将在最后一行收到错误 () AS foo_q,);解决这个问题,您可能会发现由于我的第二条评论中的问题,查询给出了不正确的结果。相反,“生成所需的 SQL”应该是您的目标。如果手动编写 SQL,生成的 SQL 有何不同?这给你任何线索吗?您能否生成任何表现出相同问题或看起来像所需 SQL 组件的更简单的查询?
  • 如果我手动编写 SQL,则没有什么可生成的。 SQL 是由 SQLAlchemy 生成的,作为其魔法的一部分。

标签: python postgresql select sqlalchemy psycopg2


【解决方案1】:

你快到了。创建一个"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 中找到。

【讨论】:

  • 这太酷了。谢谢你。它仍然依赖于 session.query,但我会接受它。我很难理解为什么它如此不同。我想做的是将整个 foo_max_time 列声明为一个单独的对象,我可以直接放入。
  • 所以唯一的区别是 foo_q 是用 session.query 而不是 select 完成的?在我看来,这是一个非常微妙的变化,现在对我来说并不直观。
  • 好的,我已经测试过了,它工作正常。这将为我提高性能。非常感谢
  • @slashdottir 是的,我尝试了很多不同的东西,但最终返回的结果非常接近您的初始版本。我很确定有几种方法可以使用 SQLAlchemy 生成相同的查询。很高兴为您提供帮助!
  • 这也适用于我。尽管这绝不是直观的,并且可以通过以透明的方式接受和理解 join 子句中的查询对象被 sqlalchemy “隐藏”。 (或者至少不会创建格式错误的 SQL,而是打印出有用的消息)。另请注意,您必须在选择中使用 [] 表示法,而不是在查询中。
【解决方案2】:

原因

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 找到。

【讨论】:

    猜你喜欢
    • 2013-01-23
    • 2017-12-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    • 2013-09-13
    相关资源
    最近更新 更多