django 获取切片后无法更新查询

ldioqlga  于 2022-12-14  发布在  Go
关注(0)|答案(8)|浏览(168)

我正在努力做到这一点:

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)
我哪里做错了?

8e2ybdfx

8e2ybdfx1#

文档建议类似下面的 * 可能 * 是可能的-我不确定在内部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字段查找,如下所示:

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

r8uurelv2#

如错误所述,如果取出切片,则不能在QuerySet上调用update()
理由是:
1.获取切片相当于SQL中的LIMIT语句。
1.发出更新会将查询转换为UPDATE语句。
你要做的事就相当于
UPDATE ... WHERE ... LIMIT 5
这是不可能的,至少对于标准SQL是不可能的。

eivgtgni

eivgtgni3#

自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'])
6mw9ycah

6mw9ycah4#

我在尝试限制查询集返回的记录数时也遇到了同样的错误。
我发现如果我们使用Django的class-based generic views,比如ArchiveIndexView,我们可以使用paginate_by =属性来限制记录的数量。
例如(在www.example.com中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
5rgfhyps

5rgfhyps5#

你不能这么做。从Django的文档中可以看出:QuerySet API参考-更新

b4wnujal

b4wnujal6#

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

import copy

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

如果你的表上有order_by过滤器,这将使Django不会抱怨,因为如果你在主queryset对象上做切片,那是在切片之后发生的

j9per5c4

j9per5c47#

我认为这个答案(https://stackoverflow.com/a/4286144/12120968)是可行的,但是如果你担心竞争条件,这里有另一个选择。
有一个select_for_update()方法,在事务内部使用时会锁行,这里有[
到文档的链接
]1和StackOverlflow中的一个相关帖子:获取切片后无法更新查询
因此,对于您的用例,它可能类似于:

from typing import List
from django.db import transaction

with transaction.atomic():
    items_you_want_to_update: List[UserLog] = (
        UserLog.objects.select_for_update().filter(
            user=user,
            action='message',
            timestamp__lt=now
        )[:5] 
        # slicing evaluates the queryset and returns a list:
        # those row are now locked because of select_for_update()
    )
    
    for item in items_you_want_to_update:
        item.read = True

    # Using bulk_update() instead of .save() on each item to get better performance
    UserLog.objects.bulk_update(items_you_want_to_update)
b5lpy0ml

b5lpy0ml8#

您的代码不正确,因为切片发生的位置。它应该发生在调用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]

相关问题