Обрабатывайте NULL как '0' в модели Джанго - PullRequest
5 голосов
/ 16 февраля 2009

Я использую следующий бит кода в моем приложении Django:

pictures = gallery.picture_set.annotate( score=models.Sum( 'picturevote__value' ) ).order_by( '-score' )

Есть таблица галерей. В каждой из них есть несколько картинок. Когда пользователь голосует за изображение вверх или вниз, вставляется новая строка в «picturevote» и подключается к нему. Тогда я могу получить общий балл за фотографии. Теперь я хочу упорядочить картины одной галереи по их оценочным значениям. Но из-за объединения таблиц может быть значение NULL для оценки, когда голосов не было вообще. Тем не менее, оценка «NULL» должна рассматриваться как «0».

Есть идеи?

редактирование: Хорошо, вот несколько дополнительных объяснений: Проблема заключается в том, что при агрегировании в приведенном выше примере для score устанавливается значение NULL. Когда я хочу отобразить счет, я использую что-то вроде этого:

score = self.picturevote_set.aggregate( models.Sum( 'value' ) )[ 'value__sum' ] or 0

Тогда агрегация приводит либо к NULL (если строк с картинками нет), либо к определенному значению. Если оно равно NULL, выражение or преобразует его в отображаемое целочисленное значение. Но это решает только проблемы с отображением, которые вызваны значением NULL. Когда я хочу отсортировать изображения по этому значению score, как в первом примере кода, все записи с NULL помещаются в конец упорядоченного результирующего набора. Сначала идут картинки с положительными оценками, затем - картинки с отрицательными значениями, а затем - картинки, за которые до сих пор не проголосовали, поскольку они имеют значение NULL как score.

У меня вопрос, как это поведение можно изменить, чтобы порядок был правильным.

Ответы [ 2 ]

14 голосов
/ 17 февраля 2009

До введения аннотаций вы могли бы использовать extra, чтобы сделать что-то подобное, что я думаю, должно вернуть 0 в случаях, когда нет голосов (если это не относится к какой-либо конкретной реализации базы данных, вы можете по крайней мере напрямую вставить необходимый COALESCE вызов функции - COALESCE(SUM(value), 0) - используя этот метод):

pictures = gallery.picture_set.extra(
    select={
        'score': 'SELECT SUM(value) FROM yourapp_picturevote WHERE yourapp_picturevote.picture_id = yourapp_picture.id',
    },
    order_by=['-score']
)

Я не вижу никакого встроенного способа добавить свой собственный SQL к новому аннотационному материалу (который я лично пока не использовал), но похоже, что вы должны быть в состоянии создать новую аннотацию следующим образом:

from django.db.models import aggregates
from django.db.models.sql import aggregates as sql_aggregates

class SumWithDefault(aggregates.Aggregate):
    name = 'SumWithDefault'

class SQLSumWithDefault(sql_aggregates.Sum):
    sql_template = 'COALESCE(%(function)s(%(field)s), %(default)s)'

setattr(sql_aggregates, 'SumWithDefault', SQLSumWithDefault)

Это выглядит довольно некрасиво, так как вам нужно monkeypatch новый агрегат в django.db.models.sql.aggregates из-за способа поиска классов агрегатов SQL, но все, что мы здесь сделали, добавили новый агрегат, который подклассов Sum, жесткое кодирование вызов функции COALESCE и добавление заполнителя для значения по умолчанию, которое вы должны предоставить в качестве аргумента ключевого слова (по крайней мере, в этом базовом примере реализации).

Это должно позволить вам сделать следующее:

pictures = gallery.picture_set.annotate(score=SumWithDefault('picturevote__value', default=0).order_by('-score')
4 голосов
/ 23 сентября 2015

В Django 1.8 есть функция базы данных Coalesce. Ваш запрос может выглядеть так:

from django.db.models.functions import Coalesce    

score = self.picturevote_set.aggregate(Coalesce(models.Sum('value'), 0))
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...