Как получить первые X-объекты с уникальным значением атрибута - PullRequest
0 голосов
/ 02 июля 2011

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

Учитывая эти объекты:

class Author(models.Model):
    name = models.CharField()

class Book(models.Model):
    collection = models.ForeignKey(Collection)
    publication = models.DateField()

class Collection(models.Model):
    name = models.CharField()
    author = models.ForeignKey(Author)

Я хотел бы получить 4 (или любое другое небольшое количество) последних опубликованных книг, но я также хочу иметь 4 разных авторов. Это означает, что если 2 последние опубликованные книги принадлежат одному и тому же автору, я хочу получить только одну из моих лучших 4 и оставить 3 места для других авторов.

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

Любая помощь будет принята с благодарностью. Спасибо

Ответы [ 2 ]

0 голосов
/ 02 июля 2011

Вы можете использовать annotate, extra или raw. Вот как бы вы использовали annotatate:

books = [a.book_set.latest('pub_date') for a in Author.objects
                   .annotate(latest=Max('book__pub_date'))
                   .order_by('-latest')[:5]]

При условии, что у авторов нет нескольких книг с одинаковым pub_date, вы можете использовать extra следующим образом:

sql = '''SELECT MAX(app_book.pub_date)
         FROM app_book
         WHERE app_book.author_id=app_author.id'''
latest = Author.objects.extra(
                select={'latest': sql},
                order_by=['-latest'])[:5].values_list('latest')
books = Book.objects.filter(pub_date__in=[x[0] for x in latest]).order_by('-pub_date')

Если вы используете raw, вы можете получить все книги одним запросом:

sql = '''SELECT * FROM app_book
         WHERE app_book.pub_date IN
           (SELECT MAX(app_book.pub_date)
            FROM app_book
            GROUP BY app_book.author_id)
         ORDER BY app_book.pub_date DESC'''
books = list(Book.objects.raw(sql)[:5])

Я предполагаю, что модели похожи на следующие:

class Author(models.Model):
    name = models.CharField(max_length=50)

class Book(models.Model):
    title = models.CharField(max_length=50)
    author = models.ForeignKey(Author)
    pub_date = models.DateTimeField()

    class Meta:
        get_latest_by = 'pub_date'

Ради забавы, я подумал, что смогу сравнить три подхода (используя БД, заполненную примерно 100 тысячами пустышек):

>>> %time annotate()
(0.274) SELECT "app_author"."id", "app_author"."name", MAX("app_book"."pub_date") AS "latest" FROM "app_author" LEFT OUTER JOIN "app_book" ON ("app_author"."id" = "app_book"."author_id") GROUP BY "app_author"."id", "app_author"."name", "app_author"."id", "app_author"."name" ORDER BY "latest" DESC LIMIT 5; args=()
(0.035) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."author_id" = 10  ORDER BY "app_book"."pub_date" DESC LIMIT 1; args=(10,)
(0.036) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."author_id" = 9  ORDER BY "app_book"."pub_date" DESC LIMIT 1; args=(9,)
(0.036) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."author_id" = 8  ORDER BY "app_book"."pub_date" DESC LIMIT 1; args=(8,)
(0.036) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."author_id" = 7  ORDER BY "app_book"."pub_date" DESC LIMIT 1; args=(7,)
(0.040) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."author_id" = 6  ORDER BY "app_book"."pub_date" DESC LIMIT 1; args=(6,)
CPU times: user 0.32 s, sys: 0.15 s, total: 0.47 s
Wall time: 0.47 s
<<< [<Book: Susan>, <Book: Yasmin>, <Book: Carl>, <Book: Benny>, <Book: George>]

>>> %time extra()
(0.445) SELECT (SELECT MAX(app_book.pub_date)
             FROM app_book
             WHERE app_book.author_id=app_author.id) AS "latest" FROM "app_author" ORDER BY "latest" DESC LIMIT 5; args=()
(0.045) SELECT "app_book"."id", "app_book"."title", "app_book"."author_id", "app_book"."pub_date" FROM "app_book" WHERE "app_book"."pub_date" IN (2038-11-25 11:33:30.425836, 2038-11-24 11:33:30.424598, 2038-11-23 11:33:30.423435, 2038-11-22 11:33:30.422227, 2038-11-21 11:33:30.421045) ORDER BY "app_book"."pub_date" DESC; args=(u'2038-11-25 11:33:30.425836', u'2038-11-24 11:33:30.424598', u'2038-11-23 11:33:30.423435', u'2038-11-22 11:33:30.422227', u'2038-11-21 11:33:30.421045')
CPU times: user 0.32 s, sys: 0.18 s, total: 0.50 s
Wall time: 0.50 s
<<< [<Book: Susan>, <Book: Yasmin>, <Book: Carl>, <Book: Benny>, <Book: George>]

>>> %time raw()
(0.279) SELECT * FROM app_book
             WHERE app_book.pub_date IN
               (SELECT MAX(app_book.pub_date)
                FROM app_book
                GROUP BY app_book.author_id)
            ORDER BY app_book.pub_date DESC; args=()
CPU times: user 0.19 s, sys: 0.09 s, total: 0.28 s
Wall time: 0.28 s
<<< [<Book: Susan>, <Book: Yasmin>, <Book: Carl>, <Book: Benny>, <Book: George>]
0 голосов
/ 02 июля 2011

Вероятно, этот пост отвечает на ваш вопрос

Django: отличительные внешние ключи

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