Синтаксис запроса для выбора ровно одного элемента для каждой категории - PullRequest
2 голосов
/ 24 декабря 2010
class Category(models.Model):
    pass

class Item(models.Model):
    cat = models.ForeignKey(Category)

Я хочу выбрать ровно один элемент для каждой категории, для чего используется синтаксис запроса?

1 Ответ

2 голосов
/ 24 декабря 2010

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

tl; dr версия: нет документированного способ явно использовать GROUP BY заявления в Django, кроме как с помощью сырой запрос. См. Внизу код для этого.

Проблема в том, что для выполнения того, что вы ищете в самом SQL, требуется небольшой взлом. Вы можете легко попробовать этот пример с помощью ввода sqlite3 :memory: в командной строке:

CREATE TABLE category
(
  id INT
);

CREATE TABLE item
(
  id INT,
  category_id INT
);

INSERT INTO category VALUES (1);
INSERT INTO category VALUES (2);
INSERT INTO category VALUES (3);


INSERT INTO item VALUES (1,1);
INSERT INTO item VALUES (2,2);
INSERT INTO item VALUES (3,3);
INSERT INTO item VALUES (4,1);
INSERT INTO item VALUES (5,2);

SELECT id, category_id, COUNT(category_id) FROM item GROUP BY category_id;

возвращает

4|1|2
5|2|2
3|3|1

Что именно вы ищете (один идентификатор элемента для каждого идентификатора категории), хотя и с посторонним счетом. Счетчик (или другая агрегатная функция) необходим для применения GROUP BY.

Примечание: при этом будут игнорироваться категории, в которых нет элементов, что выглядит как разумное поведение.

Теперь возникает вопрос, как это сделать в Django?

Очевидный ответ - использовать Поддержка агрегирования / аннотации Django , в частности, объединение annotete с значениями , как и , рекомендациями в других местах на запросы GROUP в Django.

Читая эти посты, может показаться, что мы могли бы достичь того, что искали, с помощью

Item.objects.values ​​( 'ID'). Аннотацию (unneeded_count = Count ( 'category_id'))

Однако это не работает. Здесь Django делает не просто GROUP BY "category_id", а группирует по все выбранные поля (т. Е. GROUP BY "id", "category_id") 1 . Я не верю, что есть способ (по крайней мере, в публичном API) изменить это поведение.

Решение состоит в том, чтобы вернуться к сырому SQL:

qs = Item.objects.raw('SELECT *, COUNT(category_id) FROM myapp_item GROUP BY category_id')

1 : обратите внимание, что вы можете проверить, с какими запросами работает Django:

from django.db import connection
print connection.queries[-1]

Edit:

Существует ряд других возможных подходов, но у большинства есть (возможно, серьезные) проблемы с производительностью. Вот пара:

1. Выберите предмет из каждой категории.

items = []
for c in Category.objects.all():
    items.append(c.item_set[0])

Это более понятный и гибкий подход, но он имеет очевидный недостаток: он требует гораздо больше обращений к базе данных.

2. Используйте select_related

items = Item.objects.select_related()

, а затем выполните группировку / фильтрацию самостоятельно (в Python).

Опять же, это, возможно, более понятно, чем использование необработанного SQL и требует только одного запроса, но этот один запрос может быть очень большим (он будет возвращать все элементы и их категории), и выполнение группирования / фильтрации самостоятельно, вероятно, менее эффективно, чем позволить базе данных сделать это за вас.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...