Упорядочение набора запросов Django с использованием атрибутов JSONField, при условии, что этот атрибут присутствует не везде - PullRequest
0 голосов
/ 09 октября 2018

Это очень сложный вопрос, поэтому позвольте мне объяснить.У меня есть модель с именем Person, которая хранит большую часть своих данных в JSONField.

class Person(models.Model):
    data = JSONField()

Теперь поле данных обычно имеет следующий формат:

{"name" : <String>, "age" : <int>}

Теперь, чтоЯ хочу сделать, это создать набор запросов Person, который упорядочивает объекты, используя атрибут age из своего поля data, в порядке убывания.Это решается с помощью следующего кода:

from django.db.models.expressions import RawSQL
from .models import Person

qs = Person.objects.annotate(age=RawSQL("(data->>'age')::int", [])).order_by('-age')

Это замечательно и хорошо работает.Однако во время тестирования я изменил атрибут data одного объекта Person на что-то вроде этого:

{"name" : <String>, "profession" : <String>} 

То есть этот объект не имеет атрибута age в своем поле data,Теперь, когда я выполняю запрос выше, он все еще работает нормально, но этот объект (объект без атрибута age) находится на самом верху.Это из-за одной из двух причин:

  • Поскольку его age равен нулю, он отправляется наверх из-за убывающей функции order_by.
  • Это объект, который я создал в последний разТаким образом, это всегда было в начале, но поскольку он не имеет атрибута age, на него просто не влияет функция order_by, и он остается в исходном положении.

На самом деле я хочу отправить все объекты, которые не имеют атрибута age в их поле data, в самый конец набора запросов.

Я попробовал метод объединениясоздав 2 набора запросов (один, где age не равен null, а другой - где он есть), и объединил их, используя оператор |.Это не сработало, потому что порядок был испорчен.Я также попробовал этот странный метод, который я нашел в другом вопросе (который также не работал):

qs = Person.objects.annotate(age=RawSQL("(data->>'age')::int", [])).extra(select={'is_top': "age__isnull=True"})
qs = qs.extra(order_by('-is_top')

Ссылка на странное решение, которое не работало

В любом случаеЕсть ли способ сделать это, который не включает списки, itertools и цепочки?Потому что я слышал, что иногда они могут быть довольно медленными.

Спасибо!

Примечание: Пожалуйста, не отвечайте о нормализации базы данных для этих запросов вместо использования JSONFields.Я хорошо осведомлен о преимуществах нормализации, но для моего случая использования это должен быть JSONField.

1 Ответ

0 голосов
/ 09 октября 2018

В случае сбоя при поиске ключа результат равен NULL, как указано в документации PostgreSQL :

Примечание: существуют параллельные варианты этих операторов для обоихтипы JSON и JSONB.Операторы извлечения поля / элемента / пути возвращают тот же тип, что и их левый ввод (либо json, либо jsonb), за исключением тех, которые указаны как возвращающий текст, которые приводят значение к тексту.Операторы извлечения поля / элемента / пути возвращают NULL вместо сбоя, если вход JSON не имеет правильной структуры, соответствующей запросу ;например, если такого элемента не существует.

Вы можете сделать это, вы просто заказываете с помощью .desc(nulls_last=True) [Django-doc] :

from django.db.models import F, RawSQL
from .models import Person

qs = Person.objects.annotate(
    age=RawSQL("(data->>'age')::int", [])
).order_by(F('age')<b>.desc(nulls_last=True)</b>)

Это упорядочит элементы следующим образом:

-- SQL query
ORDER BY <b>age IS NULL</b>, age DESC

Итак, при первом упорядочении по age IS NULL это приведет к TRUE, и, как результат,он упорядочен по основанию таблицы результатов.

...