Django ORM объединяет отношение многие ко многим в одном запросе - PullRequest
0 голосов
/ 20 ноября 2018

Если у нас есть 2 модели A, B с отношением многие ко многим.

Я хочу получить SQL-запрос, подобный следующему:

SELECT *
FROM a LEFT JOIN ab_relation 
ON ab_relation.a_id = a.id
JOIN b ON ab_relation.b_id = b.id;

Так что в django, когда я пытаюсь:

A.objects.prefetch_related('bees')

Я получаю 2 запроса, похожих на:

SELECT * FROM a;
SELECT ab_relation.a_id AS prefetch_related_val_a_id, b.* 
FROM b JOIN ab_relation ON b.id = ab_relation.b_id
WHERE ab_relation.a_id IN (123, 456... list of all a.id);

Учитывая, что таблицы A и B имеют достаточно большие таблицы, я считаю, что django делает это слишком медленно для моих нужд.

Вопрос заключается в следующем: можно ли получить вручную написанный запрос на левое объединение через ORM?

Редактирует, чтобы ответить на некоторые уточнения:

  • Да aLEFT OUTER JOIN было бы предпочтительным получить все A в наборе запросов, а не только те, которые имеют отношение с B (обновленный sql).

  • Умеренно большие означает ~ 4 тыс. Строк каждая, и слишком медленноозначает ~ 3 секунды (при первой загрузке, до повторного кэширования.) Имейте в виду, что на странице есть другие запросы.

  • На самом деле да, нам нужен только B.one_field, но мы попробовали сPrefetch('bees', queryset=B.objects.values('one_field')) ошибка говорит, что вы не можете использовать values в предварительной выборке.

  • Набор запросов будет использоваться какопции для поля выбора нескольких полей, где нам нужно будет представить объекты A, имеющие отношение к B, с дополнительной строкой из поля B.

1 Ответ

0 голосов
/ 20 ноября 2018

Для прямого ответа перейдите к пункту 6)

Давайте поговорим шаг за шагом.

1) N: M выберите .Вы говорите, что хотите запрос, подобный следующему:

SELECT *
FROM a JOIN ab_relation ON ab_relation.a_id = a.id
JOIN b ON ab_relation.b_id = b.id;

Но это не настоящий запрос N: M, поскольку вы получаете только объекты, связанные с AB. В запросе следует использовать outer joins.По крайней мере, как:

SELECT *
FROM a left outer JOIN 
     ab_relation ON ab_relation.a_id = a.id left outer JOIN 
     b ON ab_relation.b_id = b.id;

В других случаях вы получаете только A модели с соответствующими B.

2) Чтение больших таблиц Вы говорите "умеренно большие столы" .Тогда вы уверены, что хотите прочитать всю таблицу из базы данных?В веб-среде это не принято для чтения большого количества данных, и в этом случае вы можете разбивать данные на страницы.Может быть, это не веб-приложение?Зачем вам нужно читать эти большие таблицы?Нам нужен контекст, чтобы ответить на ваш вопрос.Вы уверены, что вам нужны все поля из обеих таблиц?

3) Выберите * из Вы уверены, что вам нужны все поля из обеих таблиц?Может быть, если вы прочитаете только некоторые значения, этот запрос будет выполняться быстрее.

A.objects.values( "some_a_field", "anoter_a_field", "Bs__some_b_field" )

4) Как сводка .ORM - мощный инструмент, две отдельные операции чтения являются «быстрыми».Я пишу некоторые идеи, но, возможно, нам нужно больше контекста, чтобы ответить на ваш вопрос.Что означает умеренные большие таблицы, пшеница означает медленный, что вы делаете с этими данными, сколько полей или байтов имеет каждая строка из каждой таблицы, ....

Editedd Поскольку OP имеетотредактировал вопрос.

5) Используйте правильные элементы управления пользовательского интерфейса .Вы говорите:

Набор запросов будет использоваться в качестве параметров для поля выбора с несколькими вариантами выбора, где нам нужно будет представить объекты A, имеющие отношение с B, с дополнительной строкой из B.field.

Выглядит как анти-шаблон для отправки клиенту 4k строк для формы.Я предлагаю вам перейти на живой контроль, который загружает только необходимые данные.Например, фильтрация по некоторому тексту.Взгляните на django-select2 потрясающий проект.

6) Вы говорите

Вопрос: возможно ли получить запрос на левое соединение, написанный вручнуючерез ORM?

Ответ: Да , вы можете сделать это, используя values, как я сказал в пункте 3. Пример: Material и ResultatAprenentatge является отношением N: M:

>>> print( Material
          .objects
          .values( "titol", "resultats_aprenentatge__codi" )
          .query )

Запрос:

SELECT "material_material"."titol", 
       "ufs_resultataprenentatge"."codi" 
FROM   "material_material" 
       LEFT OUTER JOIN "material_material_resultats_aprenentatge" 
                    ON ( "material_material"."id" = 
"material_material_resultats_aprenentatge"."material_id" ) 
LEFT OUTER JOIN "ufs_resultataprenentatge" 
ON ( 
"material_material_resultats_aprenentatge"."resultataprenentatge_id" = 
"ufs_resultataprenentatge"."id" ) 
ORDER  BY "material_material"."data_edicio" DESC 
...