У меня есть две модели, которые наследуются от другой модели. Пример:
class Parent(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, verbose_name = "ID")
class A(Parent):
name = models.CharField(max_length=255, verbose_name = "Name")
class BProxy(Parent):
target = models.OneToOneField('B', on_delete=models.CASCADE)
class B(models.Model):
name = models.CharField(max_length=255, verbose_name = "Name")
Мой запрос на данный момент выглядит так:
Parent.objects.all()
В своем сериализаторе я проверяю, к какому подклассу относится родительский объект (hasattr(obj, 'a')
), а затем использую либо name = obj.a.name
, либо name = obj.b.target.name
для сериализованных данных.
Но теперь я хотел бы отсортировать набор запросов для вывода.
Обычно я бы использовал здесь Parent.objects.all().order_by('name')
. Но имя находится в подклассах.
Можно ли объединить столбцы «имени» двух подклассов и затем выполнить сортировку по ним? Или есть другое решение?
order_by
может принимать имя поля в подклассах, т. е. Parent.objects.all().order_by('-bproxy__target__name', '-a__name')
. Это даст следующий запрос, который будет упорядочен на основе имени A и имени B.
SELECT "tmp_parent"."id" FROM "tmp_parent" LEFT OUTER JOIN "tmp_bproxy" ON ("tmp_parent"."id" = "tmp_bproxy"."parent_ptr_id") LEFT OUTER JOIN "tmp_b" ON ("tmp_bproxy"."target_id" = "tmp_b"."id") LEFT OUTER JOIN "tmp_a" ON ("tmp_parent"."id" = "tmp_a"."parent_ptr_id") ORDER BY "tmp_b"."name" DESC, "tmp_a"."name" DESC
сначала будет заказан A, а затем B, если вы хотите сделать заказ как для A, так и для B, рассмотрите ответ @temunel или используйте
from django.db.models.functions import Coalesce
Parent.objects.annotate(
name=Coalesce("a__name", "bproxy__target__name")
).order_by('name')
это означает, что если a__name
имеет значение null или не существует, будет использоваться значение bproxy__target__name
.
Примечание: не все базы данных имеют COALESCE(), а значения a__name
и bproxy__target__name
должны быть совместимого типа для упорядочивания.
Я попробовал и, к сожалению, не работает
с каким полем вы хотите сделать заказ? если вы можете и то, и другое, проверьте, что ответил @temunel здесь, stackoverflow.com/a/78947333/14383952
Чтобы отсортировать объекты Parent
на основе поля name
их подклассов, вам необходимо выполнить annotating
набор запросов с полем name
из обоих подклассов, а затем упорядочить их по этому аннотированному полю.
Вот как вы можете это сделать:
from django.db.models import Case, When, Value, CharField
queryset = Parent.objects.annotate(
name=Case(
When(a__isnull=False, then='a__name'),
When(bproxy__isnull=False, then='bproxy__target__name'),
default=Value(''),
output_field=CharField(),
)
).order_by('name')
Я надеюсь, что это решит вашу проблему.
Спасибо, это отлично работает! Я никогда раньше не работал с Case, When и так далее. Есть ли проблемы с производительностью аннотации?
Сама по себе аннотация не является узким местом производительности, но ее влияние на производительность зависит от того, как она используется, особенно при использовании сложных выражений, объединений или больших наборов данных.
Для меня это звучит почти как проблема XY, но трудно сказать, не имея дополнительной информации о том, чего вы пытаетесь достичь. Например, вы спрашиваете о том, как сделать X, но вам действительно нужен ответ, как лучше сделать Y. Однако я отвечу, предполагая, что вам нужно сделать X.
Судя по вашему запросу Parent.objects.all()
, вы не сможете делать заказ по имени, поскольку вы запрашиваете только таблицу Parent
. Вам все равно придется искать нужные данные в A
или BProxy
(что затем позволит вам выполнить каскадный поиск B
). Мой вопрос заключается в том, действительно ли Parent
должна быть отдельной таблицей или вам следует использовать абстрактную таблицу и/или прокси-таблицу. Я не могу советовать вам это, не имея дополнительных знаний о том, что вы пытаетесь сделать, но вы можете прочитать некоторые статьи об этом.
Одним из подходов, помогающих преобразовать Parent
в его A
или BProxy
дочерние элементы, было бы создание такой вспомогательной функции:
class Parent(models.Model):
...
def child(self):
try:
return A.objects.get(id=self.id)
except A.DoesNotExist:
return BProxy.objects.get(id=self.id)
Как написано, он не идеален, поскольку выдает сбивающую с толку ошибку BProxy.DoesNotExist
, если строка Parent
не соответствует ничему ни в A
, ни в BProxy
. Однако было бы нормально, если бы все Parent
всегда были одним или другим. Теперь вы можете преобразовать QuerySet
в список дочерних объектов:
children = [x.child() for x in m.Parent.objects.all()]
Это означает, что все запрошенные строки будут получены в это время, и вам придется отсортировать их в Python, но на данный момент это относительно просто. Чтобы сделать BProxy
более похожим на A
, вы можете определить для него свойство следующим образом:
class BProxy(Parent):
...
@property
def name(self):
return self.target.name
Теперь вы можете использовать sorted()
для свойства name, которое есть у них обоих:
ordered_children = sorted(children, key=lambda x: x.name)
Спасибо, но ваш ответ не помогает. Моя текущая структура данных фиксирована, и причины этого здесь не актуальны. Мой вопрос четко сформулирован: «Можно ли объединить столбцы «имя» двух подклассов и затем отсортировать их по ним? Или есть другое решение?».
В моем примере я не изменяю базовые структуры данных таблицы, а просто добавляю вспомогательные функции к классам Python. Даже если это неразумно, их не нужно добавлять в определения классов, они просто существуют как автономные функции. Однако отсортированный результат в конечном итоге будет представлять собой Python list()
различных объектов модели django вместо того, чтобы хранить их в QuerySet
, если это необходимо.
В любом случае, я думаю, у @temunel есть для вас правильный ответ. Я протестировал его, и он, кажется, выполняет свою работу.
Спасибо! Но смешивает ли это оба столбца order_by? Например, если у меня есть два объекта с именами «AAA» и «CCC» в A и один объект с именем «BBB» в B, является ли результат order_by порядком «AAA», «BBB», «CCC»?