【问题标题】:Cannot update a query once a slice has been taken获取切片后无法更新查询
【发布时间】:2011-05-16 04:27:30
【问题描述】:

我正在尝试这样做:

UserLog.objects.filter(user=user).filter(action='message').filter(timestamp__lt=now)[0:5].update(read=True)

但我收到此错误:

Cannot update a query once a slice has been taken.

(使用 django 1.2.1)

我做错了什么?

【问题讨论】:

    标签: django django-queryset


    【解决方案1】:

    documentation suggests 可能会出现以下情况可能 - 我不确定在内部 QuerySet 中进行限制是否会绕过切片后调用 update() 的检查:

    inner_q = UserLog.objects.filter(user=user,
                                     action='message',
                                     timestamp__lt=now).values('pk')[0:5]
    UserLog.objects.filter(pk__in=inner_q).update(read=True)
    

    如果做不到这一点,您可以像这样使用in field lookup

    ids = UserLog.objects.filter(user=user,
                                 action='message',
                                 timestamp__lt=now).values_list('pk', flat=True)[0:5]
    UserLog.objects.filter(pk__in=list(ids)).update(read=True)
    

    【讨论】:

    • 可能应该将这些放在“with transaction.atomic():”块中。
    【解决方案2】:

    如错误所述,如果取出切片,则无法在 QuerySet 上调用 update()

    原因:

    1. 获取切片相当于 SQL 中的 LIMIT 语句。
    2. 发布更新会将您的查询变成UPDATE 语句。

    你正在尝试做的将相当于

    UPDATE ... WHERE ... LIMIT 5

    这是不可能的,至少在标准 SQL 中是不可能的。

    【讨论】:

    • 非常感谢。我看到了我的错误。有什么解决方法吗? (除了循环 pk 并更新每一个?)
    • UPDATE...WHERE...LIMIT 1 在 MySQL 中是可能的。避免“SELECT ... FOR UPDATE”锁非常有用。
    • @est 你是对的,我稍微扩展了我的答案。请注意,OP 从未提及所使用的数据库类型。
    【解决方案3】:

    我在尝试限制查询集返回的记录数时遇到了同样的错误。

    我发现如果我们使用 Django 的class-based generic views 之一,例如ArchiveIndexView,我们可以使用paginate_by = 属性来限制记录数。

    例如(在views.py中):

    from django.views.generic import ArchiveIndexView
    from .models import Entry
    
    class HomeListView(ArchiveIndexView):
        """ Blog Homepage """
        model = Entry
        date_field = 'pub_date' 
        template_name = 'appname/home.html'
        queryset = Entry.objects.filter(
            is_active=True).order_by('-pub_date', 'title')
        paginate_by = 30
    

    【讨论】:

    • 这非常漂亮和干净,只要你不想要分页。我想你也可能会抱怨你让代码不清楚,因为你说它是分页的,但没有在模板中分页,但它比其他方法更快更容易看出正在发生的事情。
    【解决方案4】:

    从 Django 2.2 开始,您可以使用批量更新:

    queryset = UserLog.objects.filter(user=user).filter(action='message').filter(timestamp__lt=now)
    bulk = []
    for userlog in queryset[0:5]:
        userlog.read = True
        bulk.append(userlog)
    UserLog.objects.bulk_update(bulk,['read'])
    
    

    【讨论】:

      【解决方案5】:

      你不能那样做。来自 Django 文档:QuerySet API reference - update

      【讨论】:

        【解决方案6】:

        如果你想切出查询集的一些结果,你可以复制它 它到另一个变量(浅拷贝就足够了,这样更快 比深拷贝,因为它只使用对原始的引用 对象。)

        import copy
        
        queryset = Mytable.objects.all()
        pieceOfQuery = copy.copy(queryset)
        pieceOfQuery = pieceOfQuery[:10]
        

        如果你有一个 order_by 过滤器,这将使 Django 不会抱怨 你的桌子,因为如果你在主桌子上做切片,那会发生在切片之后 查询集对象

        【讨论】:

        • 这非常低效,因为它会加载 Mytable 中的所有项目。
        【解决方案7】:

        由于切片发生的位置,您的代码不正确。它应该发生在调用update() 之后,而不是之前。

        错误:

        UserLog.objects.filter(user=user).filter(action='message').filter(timestamp__lt=now)[0:5].update(read=True)
        

        对:

        UserLog.objects.filter(user=user).filter(action='message').filter(timestamp__lt=now).update(read=True)[0:5]
        

        【讨论】:

        • 感谢您的贡献。仅提供代码作为答案不会清楚地帮助 OP 和读者标记为正确答案。如果您提供此代码解析查询的原因和方式,将会很有意义。您在 OP 的代码中进行了哪些更改以解决查询。
        • 我知道。但就我而言,我不明白他的回答。然后,我这样做了,我的问题就解决了。我的问题也一样。这就是为什么我把我的答案放在这里。
        • 这是一个不正确(甚至可能很危险)的答案。 OP 显然想要更新前五个结果。我很确定你的代码会更新所有结果,然后它会抛出一个错误,因为update 返回行数,你不能切片一个整数。
        猜你喜欢
        • 1970-01-01
        • 2021-03-05
        • 2021-08-17
        • 2012-09-11
        • 1970-01-01
        • 2016-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多