由于游标()方法中“IN过滤器”的限制,使用游标分页查询会导致错误...应该选择什么?

时间:2011-05-16 08:13:54

标签: python google-app-engine google-cloud-datastore

我正在使用以下模型开发类似微博系统的推特:

class Member(db.Model):    
    user = db.UserProperty(required=True)
    follower_count = db.IntegerProperty(default=0) # members following you    
    following_count = db.IntegerProperty(default=0) # members you are following

class NewsItem(db.Model):    
    text = db.StringProperty(required=True)
    posted_by = db.ReferenceProperty(reference_class=Member,required=True,collection_name="posted_items")
    posted_on = db.DateTimeProperty(auto_now_add=True)
    status = db.IntegerProperty(default=1) # 0: deleted

class Follow(db.Model):    
    member = db.ReferenceProperty(reference_class=Member,required=True,collection_name="followings")    
    followed_member = db.ReferenceProperty(reference_class=Member,required=True,collection_name="followers")    
    added_on = db.DateTimeProperty(auto_now_add=True) 

在此模型结构中,我使用以下代码检索当前用户所遵循的成员的消息:

follow_log_list = Follow.gql('WHERE member = :1 ', member)
followed_member_list = []
for follow_log in follow_log_list:
    followed_member_list.append(follow_log.followed_member)

query = NewsItem.all()
query.filter('posted_by IN', followed_member_list)
query.filter('status =', 1)
query.order('-posted_on')
query.with_cursor(cursor)   
newsList = query.fetch(10)  

template_values['cursor'] = query.cursor()

当调用query.cursor()方法时,我收到以下错误:

  

“没有可用于MultiQuery的游标   (使用“IN”或“!=”查询   运营商)“

这是正常的,因为在游标的文档中,这种限制明确表示为:

http://code.google.com/appengine/docs/python/datastore/queries.html

“您不能对使用IN或!=过滤器运算符的查询使用游标。”

获取所关注成员的帖子的替代方法是什么?

谢谢,

编辑:已发布的消息按其状态进行过滤,并按发布日期排序......但样本未显示在此处,我已将其更改为...

5 个答案:

答案 0 :(得分:2)

快速而讨厌的方式......

下载此pagintor.py 将其导入您的项目。

然后你可以为分页做这样的事情

    from paginator import Paginator, InvalidPage, EmptyPage
     model = Member.all().fetch(100)
     paginator = Paginator(model,5)

                if(self.request.GET):
                    page = int(self.request.GET.get('page', '1'))
                    if(page is not None):
                        try:
                            page = int(self.request.GET.get('page', '1'))
                        except ValueError:
                            page = 1

                        # If page request (9999) is out of range, deliver last page of results.
                        try:
                            paginator = paginator.page(page)
                        except (EmptyPage, InvalidPage):
                            paginator = paginator.page(paginator.num_pages)
    return self.response.out.write( template.render(path+'.html',{'paginator':paginator}))


#In templates

{% if paginator.object_list %}

{% for values in paginator.object_list %}

#do your tasks

{% endfor %}
<div  align="right" class="pagination" >
        {% if paginator.has_previous %}
            <a  id="previous" href="{{ paginator.previous_page_number }}">Previous</a>
        {% else %}
         <span class="page-nulled" >
            Previous
        </span>
        {% endif %}

        <span class="current" id="pagenum" title="{{ paginator.number }}">
            &nbsp;&nbsp;&nbsp;Page {{ paginator.number }} of {{paginator.paginator.num_pages }}&nbsp;&nbsp;&nbsp;
        </span>

        {% if paginator.has_next %}
            <a  id="next" href="{{ paginator.next_page_number }}"> Next </a>
            {% else %}
         <span class="page-nulled" >
            Next 
        </span>
        {% endif %}

</div>
单击下一个或上一个

,取href val()并将其作为get变量传递给url http://someurl?page=

更多参考here

答案 1 :(得分:1)

此限制的原因是IN!=查询是通过将查询拆分为多个基础查询来执行的,这些查询由数据存储区单独执行,然后按排序顺序合并在一起。

如果要以分页方式执行此类查询,则必须自己执行查询,并自行进行合并。要获取游标,您需要从各个子查询中获取游标并将它们连接在一起。此外,您需要跟踪已经获取但未消耗的结果数量,因此您可以从中断的位置准确地获取。

正如您所看到的,这很复杂并导致游标值过长,这就是目前SDK尚未实现的原因。不幸的是,这是唯一可行的方法,除非你能找到一种方法来避免使用IN子句,或者放弃你对另一个子句的排序要求(在这种情况下你可以直接执行查询) ,将每个分页。)

答案 2 :(得分:1)

我的解决方案是使用像游标一样的日期值,正如我所描述的对Nick Johnson的答案的评论......就像这样:

if cursor: # This is not actually a cursor! It is base64 datetime string
  cursordate = _strptime(base64.b64decode(cursor)) # _strptime is a local method that converts str to datetime

# IN has a limit for lists: 30 items allowed
listofNewsLists = []
listofMemberLists = [followed_member_list[i:i+30] for i in range(0, len(followed_member_list), 30)]
for eachList in listofMemberLists:
   query = NewsItem.all()
   query.filter('posted_by IN', eachList).filter('status =', 1)
   if cursor:
      query.filter('posted_on <', cursordate)
   query.order('-posted_on')                        
   listofNewsLists.append(query.fetch(PAGE_SIZE))

  newsList = []
  if listofNewsLists:
    emptyListCount = 0
    while len(newsList) < PAGE_SIZE and emptyListCount < len(listofNewsLists):
      max = datetime.datetime.min
      maxInd = -1
      emptyListCount = 0
      for i in range(len(listofNewsLists)):
        if listofNewsLists[i] == []:
          emptyListCount += 1
        elif listofNewsLists[i][0].posted_on > max:
          max = listofNewsLists[i][0].posted_on
          maxInd = i
      if max > datetime.datetime.min:
        newsList.append(listofNewsLists[maxInd].pop(0))

template_values['cursor'] = base64.b64encode(newsList[-1].posted_on.isoformat())

那是;我将最后显示的项目的日期值存储为新列表的起点......

除非我的项目具有相同的publish_on值,否则这个效果很好(我猜)

答案 3 :(得分:0)

考虑您有一个名为member_obj的成员对象。从你的模型你可以做这样的事情

要获取以下列表,

member_obj.followings将为您提供密钥列表,只需将其传递给

即可
followings_keys = member_obj.followings
Member.get(following_keys).

然后循环浏览成员并显示他们的消息.. Modeling in appengine上的这篇文章将对您有所帮助。

答案 4 :(得分:0)

我已经能够使用搜索API解决此问题。

https://developers.google.com/appengine/docs/python/search/

您需要在搜索文档中镜像对象的相关位,并将其保存到_pre_put_hook或_post_put_hook中的索引(您可以在_pre_delete_hook或_post_delete_hook中清除它们)。使用密钥的序列化作为搜索doc_id。

一旦你完成了这项工作,你可以使用搜索进行搜索,这是你在上面尝试做的。它快!只需返回doc_ids,然后使用它们来获取()数据存储区对象。