使用游标进行分页查询因游标()方法中的“IN过滤器”限制而导致错误... 该如何替代?

2 投票
5 回答
2295 浏览
提问于 2025-04-16 17:43

我正在开发一个类似Twitter的微博系统,使用了以下模型:

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 个回答

1

这个限制的原因是,IN!= 查询会被拆分成多个底层查询,然后这些查询会被单独执行,最后再把结果合并成一个排序好的结果。

如果你想以分页的方式进行这样的查询,你就得自己执行这些查询,并自己进行合并。要获取游标,你需要从每个子查询中获取游标,然后把它们连接在一起。此外,你还需要记录已经获取但还没使用的结果数量,这样才能准确地从上次停止的地方继续。

如你所见,这个过程很复杂,并且会导致游标值变得非常长,这也是为什么目前SDK没有实现这个功能。不幸的是,这就是唯一可行的方法,除非你能找到避免使用 IN 子句的方法,或者放弃按其他条件排序的要求(在这种情况下,你可以简单地串行执行查询,并对每个查询进行分页)。

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>

当你点击“下一页”或“上一页”时,获取链接的值,然后把它作为参数传递到网址中,比如像这样:http://someurl?page=

更多参考资料可以在 这里 找到。

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())

也就是说,我把最后显示的项目的日期值存起来,作为新列表的起点...

这样做效果不错(我想),除非我有一些项目的发布日期是一样的...

撰写回答