Объединенный столбец order_by в Джанго

У меня есть две модели, которые наследуются от другой модели. Пример:

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'). Но имя находится в подклассах.

Можно ли объединить столбцы «имени» двух подклассов и затем выполнить сортировку по ним? Или есть другое решение?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
50
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

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 должны быть совместимого типа для упорядочивания.

Спасибо! Но смешивает ли это оба столбца order_by? Например, если у меня есть два объекта с именами «AAA» и «CCC» в A и один объект с именем «BBB» в B, является ли результат order_by порядком «AAA», «BBB», «CCC»?

Basti 04.09.2024 10:18

Я попробовал и, к сожалению, не работает

Basti 04.09.2024 10:27

с каким полем вы хотите сделать заказ? если вы можете и то, и другое, проверьте, что ответил @temunel здесь, stackoverflow.com/a/78947333/14383952

Gathiira M. 04.09.2024 10:35
Ответ принят как подходящий

Чтобы отсортировать объекты 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 и так далее. Есть ли проблемы с производительностью аннотации?

Basti 04.09.2024 10:39

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

temunel 04.09.2024 16:41

Для меня это звучит почти как проблема 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)

Спасибо, но ваш ответ не помогает. Моя текущая структура данных фиксирована, и причины этого здесь не актуальны. Мой вопрос четко сформулирован: «Можно ли объединить столбцы «имя» двух подклассов и затем отсортировать их по ним? Или есть другое решение?».

Basti 04.09.2024 10:20

В моем примере я не изменяю базовые структуры данных таблицы, а просто добавляю вспомогательные функции к классам Python. Даже если это неразумно, их не нужно добавлять в определения классов, они просто существуют как автономные функции. Однако отсортированный результат в конечном итоге будет представлять собой Python list() различных объектов модели django вместо того, чтобы хранить их в QuerySet, если это необходимо.

penguin359 04.09.2024 10:31

В любом случае, я думаю, у @temunel есть для вас правильный ответ. Я протестировал его, и он, кажется, выполняет свою работу.

penguin359 04.09.2024 10:35

Другие вопросы по теме