Принудительная оценка предварительно запрошенных результатов в запросах 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
.