Сводные данные и сложные аннотации в Django ORM

#python #sql #django #django-orm

#python #sql #django #django-orm

Вопрос:

ORM в Django позволяет нам легко аннотировать (добавлять поля к) наборам запросов на основе связанных данных, однако я не могу найти способ получить несколько аннотаций для разных отфильтрованных подмножеств связанных данных.

Этот вопрос задается в связи с django-helpdesk, средством отслеживания проблем с открытым исходным кодом на базе Django. Мне нужно, чтобы данные сводились подобным образом для построения графиков и создания отчетов

Рассмотрим эти модели:

 CHOICE_LIST = (
    ('open', 'Open'),
    ('closed', 'Closed'),
)

class Queue(models.model):
    name = models.CharField(max_length=40)

class Issue(models.Model):
    subject = models.CharField(max_length=40)
    queue = models.ForeignKey(Queue)
    status = models.CharField(max_length=10, choices=CHOICE_LIST)
  

И этот набор данных:

Очереди:

 ID | Name
--- ------------------------------
1  | Product Information Requests
2  | Service Requests
  

Проблемы:

 ID | Queue | Status
--- ------- ---------
1  | 1     | open
2  | 1     | open
3  | 1     | closed
4  | 2     | open
5  | 2     | closed
6  | 2     | closed
7  | 2     | closed
  

Я хотел бы видеть аннотацию / агрегат, выглядящий примерно так:

 Queue ID | Name                          | open | closed
--------- ------------------------------- ------ --------
1        | Product Information Requests  | 2    | 1
2        | Service Requests              | 1    | 3
  

По сути, это перекрестная таблица или сводная таблица, на языке Excel. В настоящее время я создаю этот вывод, используя некоторые пользовательские SQL-запросы, однако, если я смогу перейти к использованию Django ORM, я смогу более легко динамически фильтровать данные, не выполняя хитроумную вставку предложений WHERE в моем SQL.

Что касается «бонусных баллов»: как бы это сделать, если бы сводным полем ( status в приведенном выше примере) была дата, а мы хотели, чтобы столбцы были месяцами / неделями / кварталами / днями?

Ответ №1:

У вас есть Python, используйте его.

 from collections import defaultdict
summary = defaultdict( int )
for issue in Issues.objects.all():
    summary[issue.queue, issue.status]  = 1
  

Теперь у вашего summary объекта есть очередь, статус ключа из двух кортежей. Вы можете отобразить это напрямую, используя различные методы создания шаблонов.

Или вы можете перегруппировать их в табличную структуру, если это проще.

 table = []
queues = list( q for q,_ in summary.keys() )
for q in sorted( queues ):
    table.append( q.id, q.name, summary.count(q,'open'), summary.count(q.'closed') )
  

У вас есть много-много методов Python для создания сводных таблиц.

Если вы измерите, вы можете обнаружить, что решение, основанное в основном на Python, подобное этому, на самом деле быстрее, чем решение на чистом SQL. Почему? Сопоставления могут быть быстрее, чем алгоритмы SQL, которые требуют сортировки как части ГРУППИРОВАНИЯ.

Комментарии:

1. Это решение рухнет, если таблица проблем будет большой и ее вряд ли можно рассматривать как общую.

2. Для всех, кто читает это несколько лет спустя: я получаю collections.defaultdict' object has no attribute 'count' с python3, также есть опечатка в summary.count(q.'closed')

Ответ №2:

Django добавил много функциональности в ORM с тех пор, как этот вопрос был задан изначально. Ответ на вопрос о том, как сводить данные начиная с Django 1.8, заключается в использовании условных выражений Case / When. И есть стороннее приложение, которое сделает это за вас, PyPI и документация

Комментарии:

1. django-sql-explorer также создает сводные таблицы.

2. django-sql-explorer — хороший инструмент, и функциональность сводной таблицы может быть полезна в этом случае, но первоначальный вопрос был о том, как создать сводную таблицу в ORM, а django-sql-explorer делает это в javascript после того, как ORM возвращает все необработанные данные.