Принудительная оценка предварительно запрошенных результатов в запросах django

Я пытаюсь использовать select_related/prefetch_related для оптимизации некоторых запросов. Однако у меня есть проблемы с "принуждением" запросов к одновременной оценке.

Допустим, я делаю следующее:

fp_query = Fourprod.objects.filter(choisi=True).select_related("fk_fournis")    
pf = Prefetch("fourprod", queryset=fp_query)                # 
products = Products.objects.filter(id__in=fp_query).prefetch_related(pf)

С моделями:

class Fourprod(models.Model):
    fk_produit = models.ForeignKey(to=Produit, related_name="fourprod")
    fk_fournis = models.ForeignKey(to=Fournis,related_name="fourprod")
    choisi = models.BooleanField(...)


class Produit(models.Model):
    ... ordinary fields... 

class Fournis(models.Model):
    ... ordinary fields... 

По существу, Fourprod имеет fk к Fournis, Produit, и я хочу предварительно выбрать их, когда я строю кверисет Produits. Я проверил в отладке, что предварительная выборка действительно происходит, и она происходит.

У меня есть куча полей из разных моделей, которые мне нужно использовать для вычисления результатов. Я не очень контролирую структуру таблицы, поэтому мне приходится работать с этим. Я не могу придумать разумный запрос, чтобы сделать все это с помощью запросов (или используя raw), поэтому я хочу вычислять результаты на стороне python. Это несколько 1000 объектов, поэтому разумно делать это в памяти. Поэтому я привожу к списку, чтобы принудительно оценить запрос:

products = list(products)

На этом этапе, я думаю, что Products и связанные объекты, которые я предварительно отбирал, должны были быть извлечены из БД. В логах, сразу после вызова list(), я получаю следующее:

02/08/22 15:21:08 DEBUG DEFAULT: (0.019) SELECT "products_fourprod"."id", "products_fourprod"."fk_produit_id", "products_fourprod"."fk_fournis_id", "products_fourprod"."choisi", "products_fourprod"."code_four", "products_fourprod"."prix", "products_fourprod"."comment", "products_fournis"."id", "products_fournis"."fk_user_create_id", "products_fournis"."nom", "products_fournis"."adresse", "products_fournis"."ville", "products_fournis"."tel", "products_fournis"."fax", "products_fournis"."contact", "products_fournis"."note", "products_fournis"."pays", "products_fournis"."province", "products_fournis"."postal", "products_fournis"."monnaie", "products_fournis"."tel_long", "products_fournis"."inactif", "products_fournis"."inuse", "products_fournis"."par", "products_fournis"."fk_langue", "products_fournis"."NOTE2" FROM "products_fourprod" LEFT OUTER JOIN "products_fournis" ON ("products_fourprod"."fk_fournis_id" = "products_fournis"."id") WHERE ("products_fourprod"."choisi" AND "products_fourprod"."fk_produit_id" IN (... all Product.id meeting the conditions...)

Но тогда на составление списка понимания использования продуктов уходит целая вечность:

rows = [[p.id, p.fourprod.first().id, p.desuet, p.no_prod, ... ] for p in products]  

При этом, очевидно, каждый отдельный вызов p.fourprod приводит к попаданию в БД:

02/08/22 15:26:19 DEBUG DEFAULT: (0.000) SELECT "products_fourprod"."id", "products_fourprod"."fk_produit_id", "products_fourprod"."fk_fournis_id", "products_fourprod"."choisi", "products_fourprod"."code_four", "products_fourprod"."prix", "products_fourprod"."comment", "products_fournis"."id", "products_fournis"."fk_user_create_id", "products_fournis"."nom", "products_fournis"."adresse", "products_fournis"."ville", "products_fournis"."tel", "products_fournis"."fax", "products_fournis"."contact", "products_fournis"."note", "products_fournis"."pays", "products_fournis"."province", "products_fournis"."postal", "products_fournis"."monnaie", "products_fournis"."tel_long", "products_fournis"."inactif", "products_fournis"."inuse", "products_fournis"."par", "products_fournis"."fk_langue", "products_fournis"."NOTE2" FROM "products_fourprod" LEFT OUTER JOIN "products_fournis" ON ("products_fourprod"."fk_fournis_id" = "products_fournis"."id") WHERE ("products_fourprod"."choisi" AND "products_fourprod"."fk_produit_id" = 1185) ORDER BY "products_fourprod"."id" ASC LIMIT 1; args=(1185,)
02/08/22 15:26:19 DEBUG DEFAULT: (0.000) SELECT "products_fourprod"."id", (.... more similar db hits... )

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

Если простой вызов products = list(products) не заставит запросить базу данных и для предварительно выбранных объектов, есть ли способы заставить django's orm сделать это?

От docs:

Помните, что, как и всегда с QuerySets, любые последующие цепочки методов, которые подразумевают другой запрос к базе данных, будут игнорировать ранее кэшированные результаты и получать данные, используя свежий запрос к базе данных.

first() подразумевает запрос к базе данных, так что это приведет к тому, что ваш запрос не будет использовать предварительно найденные значения.

Попробуйте использовать p.fourprod.all()[0] вместо этого для доступа к первому связанному fourprod.

Вернуться на верх