Невозможно обновить запрос после того, как был сделан срез

Я пытаюсь сделать это:

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.

(с использованием джанго 1.2.1)

Что я делаю не так?


person xpanta    schedule 26.11.2010    source источник


Ответы (7)


Как указано в ошибке, вы не можете вызвать update() для QuerySet, если вы вынули срез.

Причина:

  1. Взятие среза эквивалентно оператору LIMIT в SQL.
  2. Выпуск обновления превращает ваш запрос в оператор UPDATE.

То, что вы пытаетесь сделать, будет эквивалентно

UPDATE ... WHERE ... LIMIT 5

что невозможно, по крайней мере, не со стандартным SQL.

person Daniel Hepper    schedule 26.11.2010
comment
Большое спасибо. Я вижу свою ошибку. Есть ли обходной путь для этого? (кроме перебора pks и обновления каждого из них?) - person xpanta; 26.11.2010
comment
UPDATE...WHERE...LIMIT 1 возможно в MySQL. Довольно полезно, чтобы избежать блокировок SELECT ... FOR UPDATE. - person est; 26.08.2015
comment
@Вы правы, я немного расширил свой ответ. Обратите внимание, что OP никогда не упоминает тип используемой базы данных. - person Daniel Hepper; 26.08.2015

документация предполагает, что что-то вроде следующего может быть возможным - я не уверен, что выполнение ограничения во внутреннем 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)
person Jonny Buchanan    schedule 26.11.2010
comment
Вероятно, их следует разместить внутри блока transaction.atomic():. - person Dmitriy Sintsov; 19.10.2016

Я получал ту же ошибку при попытке ограничить количество записей, возвращаемых набором запросов.

Я обнаружил, что если мы используем одно из общих представлений Django на основе на основе классов например, 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
person sgriffee    schedule 27.04.2013
comment
Это довольно красиво и чисто, если вы не хотите разбиения на страницы. Я думаю, вы также могли бы ворчать, что вы делаете код неясным, поскольку вы говорите, что он разбит на страницы, но затем не разбивается на страницы в шаблоне, но это намного быстрее и легче понять, что происходит, чем другие методы. - person Tom Carrick; 08.09.2014

Начиная с 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'])

person Lucas B    schedule 27.01.2021

Вы не можете этого сделать. Из документов Django: справочник по API QuerySet — обновление

person Evan Porter    schedule 26.11.2010

Если вы хотите вырезать некоторые результаты набора запросов, вы можете скопировать их в другую переменную (достаточно поверхностной копии, которая быстрее, чем глубокая копия, потому что она просто использует ссылки на исходные объекты).

import copy

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

Это предотвратит жалобы Django, если у вас есть фильтр order_by в вашей таблице, поскольку это происходит после нарезки, если вы делаете это для основного объекта набора запросов.

person Mark McWiggins    schedule 13.03.2014
comment
Это очень неэффективно, так как загрузит все элементы в Mytable. - person personjerry; 23.11.2020

Ваш код неверен из-за того, где происходит нарезка. Это должно происходить после вызова 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]
person Fahimbd_14    schedule 21.01.2020
comment
Спасибо за ваш вклад. Предоставление только кодов в качестве ответа явно не поможет ОП и читателям отметить правильный ответ. Будет полезно, если вы объясните, почему и как этот код разрешает запрос. Какие изменения вы внесли в код OP, чтобы запрос был разрешен. - person Chirag Jain; 21.01.2020
comment
Я знаю это. но в моем случае я не понимаю его ответа. затем я делаю это, и моя проблема решена. и моя проблема тоже такая же. вот почему я помещаю здесь свой ответ. - person Fahimbd_14; 21.01.2020
comment
Это неправильный (возможно, даже опасный) ответ. ОП явно хочет обновить первые пять результатов. Я почти уверен, что ваш код обновит ВСЕ результаты, ТОГДА он выдаст ошибку, потому что update возвращает количество строк, и вы не можете разрезать целое число. - person Adam Easterling; 05.08.2020