Python 如何在Flask SQLAlchemy中为db.session联接查询分页?

Python 如何在Flask SQLAlchemy中为db.session联接查询分页?,python,sqlalchemy,flask,flask-sqlalchemy,Python,Sqlalchemy,Flask,Flask Sqlalchemy,比如说,我们有以下关系: 一个人可以有许多电子邮件地址 电子邮件服务提供商(显然)可以提供多个电子邮件地址 所以,这是一种多对多的关系。我有三个表格:电子邮件、提供商和用户。电子邮件有两个提供者和用户的外来ID 现在,给定一个特定的人,我想打印所有的电子邮件提供商和该人的电子邮件地址(如果存在)。(如果这个人在Gmail上没有电子邮件,我仍然希望Gmail会出现在结果中。否则我相信我只需要一个左内连接就可以解决这个问题。) 我通过以下子查询(遵循sqlalchemy教程)了解了如何做到这一点

比如说,我们有以下关系:

  • 一个人可以有许多电子邮件地址
  • 电子邮件服务提供商(显然)可以提供多个电子邮件地址
所以,这是一种多对多的关系。我有三个表格:电子邮件、提供商和用户。电子邮件有两个提供者和用户的外来ID

现在,给定一个特定的人,我想打印所有的电子邮件提供商和该人的电子邮件地址(如果存在)。(如果这个人在Gmail上没有电子邮件,我仍然希望Gmail会出现在结果中。否则我相信我只需要一个左内连接就可以解决这个问题。)

我通过以下子查询(遵循sqlalchemy教程)了解了如何做到这一点:

这可以正常工作(它返回一个4元组的
(提供者、用户id、提供者id、电子邮件地址)
,所有我想要的信息),但我后来发现这不是使用Flask
BaseQuery
类,所以Flask SQLAlchemy提供的
分页
不起作用。显然
db.session.query()
不是Flask SQLAlchemy查询实例

我尝试执行
Emails.query.outerjoin[…]
,但这只返回电子邮件表中的列,尽管我需要提供商信息和电子邮件

我的问题:我如何使用炼金术做同样的事情,从而不必重新实现已经存在的分页



我想目前最简单的选择是实现我自己的paginate函数,但我想知道是否还有另一种正确的方法。

我可能错了,但我认为你的问题可能是.all()。通过使用它,您将得到一个列表,而不是一个查询对象

试着不使用它,并像这样将查询传递给分页方法(为了清晰起见,我省略了所有子查询细节):


如何使用SQLAlchemy初始化应用程序

可能您当前的SQLAlchemy连接与flask.ext.sqalchemy无关,您使用的是原始的SQLAlchemy

检查本教程并检查您的导入,确保它们确实来自flask.ext.sqlalchemy


我不确定这是否是一个长期的解决方案,它也没有直接解决我对不使用Flask SQLAlchemy的BaseQuery的担忧,但实现我想要的最简单的方法是重新实现paginate函数

事实上,使用最初的炼金术例程很容易做到这一点:

def paginate(query, page, per_page=20, error_out=True):
    if error_out and page < 1:
        abort(404)
    items = query.limit(per_page).offset((page - 1) * per_page).all()
    if not items and page != 1 and error_out:
        abort(404)

    # No need to count if we're on the first page and there are fewer
    # items than we expected.
    if page == 1 and len(items) < per_page:
        total = len(items)
    else:
        total = query.order_by(None).count()

    return Pagination(query, page, per_page, total, items)
query = BaseQuery(provider_and_email.subquery(), db.session())
def paginate(查询,第页,每页=20,错误=True):
如果出现错误且页面<1:
中止(404)
items=query.limit(每页)。offset((第-1页)*每页)。all()
如果不是项目和页面!=1和错误_out:
中止(404)
#如果我们在第一页,而且数量更少,就不需要计算了
#比我们预期的要好。
如果页面==1且每个页面的长度(项目)<:
总计=长度(项目)
其他:
总计=query.order\u by(无).count()
返回分页(查询、页面、每页、总计、项目)

从第376行附近的
paginate
函数修改:

嘿,我找到了一个快速修复方法,它是:

provider_and_email = Provider.query.with_entities(email_subq).\
            outerjoin(email_subq, Provider.emails).paginate(page, POST_PER_PAGE_LONG, False)

您的问题是如何在常规SQLAlchemy查询中使用Flask SQLAlchemy的分页

由于Flask SQLAlchemy的BaseQuery对象没有自己的状态,并且是从SQLAlchemy的查询派生的,实际上只是方法的容器,因此您可以使用以下技巧:

from flask.ext.sqlalchemy import BaseQuery
def paginate(sa_query, page, per_page=20, error_out=True):
  sa_query.__class__ = BaseQuery
  # We can now use BaseQuery methods like .paginate on our SA query
  return sa_query.paginate(page, per_page, error_out)
使用:

@route(...)
def provider_and_email_view(page):
  provider_and_email = db.session.query(...) # any SQLAlchemy query
  paginated_results = paginate(provider_and_email, page)
  return render_template('...', paginated_results=paginated_results)
*编辑:

做这件事请小心。这实际上只是一种避免复制/粘贴
paginate
函数的方法,如另一个答案所示。请注意,BaseQuery没有
\uuuuu init\uuuu
方法。看

*编辑2:


如果BaseQuery有一个
\uuuuuu init\uuuuuuu
,那么您可以使用SA查询对象来构造一个,而不是黑客攻击
\uuuuuuu类
我目前正在使用这种方法:

query = BaseQuery([Provider, email_subq], db.session())
创建我自己的
BaseQuery
db
SqlAlchemy
实例

更新:您也可以这样做:

def paginate(query, page, per_page=20, error_out=True):
    if error_out and page < 1:
        abort(404)
    items = query.limit(per_page).offset((page - 1) * per_page).all()
    if not items and page != 1 and error_out:
        abort(404)

    # No need to count if we're on the first page and there are fewer
    # items than we expected.
    if page == 1 and len(items) < per_page:
        total = len(items)
    else:
        total = query.order_by(None).count()

    return Pagination(query, page, per_page, total, items)
query = BaseQuery(provider_and_email.subquery(), db.session())

您可以尝试使用结果对列表进行分页

my_list = [my_list[i:i + per_page] for i in range(0, len(my_list), per_page)][page]

我确实用
paginate()
和正确的参数替换了
all()
,因此我在sqlalchemy.orm.query.query对象上调用了
paginate
(在Flask error page debugger中验证)。我想这不是这里的错误来源。对不起,问题不清楚。该死,我本来希望这是件容易的事。如果使用Flask SQLAlchemy查询别名会发生什么?类似这样的内容:Email.query.filter(Emails.user\u id==current\u user.id).outerjoin(ProviderEmail,ProviderEmail.provider\u id==Email.id)它生成一个SELECT查询,其中只包含
Email
中的字段,因此不会返回任何关于提供者的信息;至少我上次尝试时是这样。paginate不能与
db.session.query(Table).filter(**condition).paginate()一起使用。
。只有当我们调用模型类,如
Table.query.filter(**condition).paginate()
时,这才有效。这个答案是不正确的。我几乎可以肯定我是以正确的方式开始炼金术的。所有的
模式.query
命令对我来说都能正常工作。我确实遵循了上面的教程。您是否尝试过查看您构建的查询是否可以用于构建新的BaseQuery对象(类似于您创建分页对象的方式),从而允许您重用BaseQuery Paginate函数?啊,是的。我想我可以,但还没试过。谢谢你指出。我得到了一个sau查询。\uu class\uuu=BaseQuery类型错误:类分配:只有对于堆类型,你也可以使用任何SqlAlchemy查询对象,只需调用.subquery()方法。因此,使用OP的例子:
query\u with_pagination=BaseQuery(provider\u and_email.subquery(),db.session())
你的答案应该是可以接受的,依我看。@afilbert其他人在'13年发布,添加了我的,因为我喜欢我