После некоторой отладки и изучения исходного кода у меня появилась идея, почему это происходит. Что я собираюсь сделать, так это попытаться объяснить, почему выполнение annotate
+ values
приводит к отображению id
и в чем разница между двумя приведенными выше случаями.
Чтобы упростить задачу, я напишем также напишем возможный результирующий sql запрос для каждого оператора.
1. annotate
сначала, но получите values
при запросе объединения
qs1 = ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a"))
Когда вы пишете что-то вроде этого, django получит все поля + аннотированные поля, поэтому результирующий запрос sql выглядит следующим образом:
select id, field_1a, field_2a, field_1a as field1, field_2a as field2 from ModelA
Итак, если у нас есть query
, который является результатом:
qs = qs1.union(qs2)
результирующий sql для django выглядит следующим образом:
(select id, field_1a, field_2a, field_1a as field1, field_2a as field2 from ModelA)
UNION
(select id, field_1b, field_2b, field_1b as field1, field_2b as field2 from ModelB)
Давайте go глубже в том, как это sql генерируется. Когда мы делаем union
, qs.query
и combined_queries
устанавливаются на *1026*, и результирующий sql генерируется с помощью , объединяющим sql отдельных запросов. Итак, в итоге:
qs.sql == qs1.sql UNION qs2.sql # in abstract sense
Когда мы делаем qs.values('field1', 'field2')
, col_count
в компиляторе устанавливается в 2, что является количеством полей. Как вы можете видеть, запрос на объединение, приведенный выше, возвращает 5 столбцов, но в окончательном возвращении компилятора каждая строка в результатах разбивается на с использованием col_count
. Теперь этот results
, содержащий только 2 столбца, передается обратно в ValuesIterable
, где он отображает каждое имя в выбранных полях с результирующими столбцами. Вот как это приводит к неверным результатам.
2. annotate
+ values
на отдельные запросы и затем выполните union
Теперь давайте посмотрим, что произойдет, когда annotate
используется с values
напрямую
qs1 = ModelA.objects.all().annotate(field1=F("field_1a"), field2=F("field_2a")).values('field1', 'field2')
Получившийся sql это:
select field_1a as field1, field_2a as field2 from ModelA
Теперь, когда мы делаем объединение:
qs = qs1.union(qs2)
sql это:
(select field_1a as field1, field_2a as field2 from ModelA)
UNION
(select field_1b as field1, field_2b as field2 from ModelB)
Теперь, когда qs.values('field1', 'field2')
выполняется, число столбцов, возвращаемых из запроса объединения, имеет 2 столбца, что совпадает с col_count
, равным 2, и каждое поле сопоставляется с отдельными столбцами, дающими ожидаемый результат.
3 , Разное количество аннотаций и порядок полей
В OP есть сценарий, когда даже использование .values
до union
не дает правильных результатов. Причина в том, что в ModelB
нет аннотации для поля extra
.
Итак, давайте посмотрим на запросы, сгенерированные для каждой модели:
ModelA.objects.all()
.annotate(
field1=F("field_1a"), field2=F("field_2a"), extra=F("extra_a")
)
.values(*values)
SQL становится:
select field_1a as field1, field_2a as field2, extra_a as extra from ModelA
Для модели B:
ModelB.objects.all()
.annotate(field1=F("field_1b"), field2=F("field_2b"))
.values(*values)
SQL:
select extra, field_1b as field1, field_2b as field2 from ModelB
и объединение:
(select field_1a as field1, field_2a as field2, extra_a as extra from ModelA)
UNION
(select extra, field_1b as field1, field_2b as field2 from ModelB)
Поскольку аннотированные поля перечислены после реальных полей БД, extra
из ModelB
смешивается с field1
из ModelB
. Чтобы получить правильные результаты, убедитесь, что порядок полей в сгенерированном SQL всегда правильный - с аннотацией или без нее. В этом случае я предлагаю также пометить extra
на ModelB
.