Dec. 13, 2019, 7:53 a.m.

Как уменьшить количество запросов в Django ORM?

Django, ORM, DataBase

Доброго времени суток.

Задача такая.
Нужно получить QuerySet, который состоит из вопросов разных категорий. Вопросы должны быть разной сложности, а для каждой сложности своё количество. Все вопросы нужно вытащить из бд в случайном порядке.

Есть вот такой код.

# models.py
class Question(models.Model):
    EASY = 1
    MEDIUM = 2
    HARD = 3

    DIFFICULTY_CHOICES = (
        (EASY, 'Легкий'),
        (MEDIUM, 'Средний'),
        (HARD, 'Сложный')
    )

    text = models.TextField(max_length=300, null=True, verbose_name='текст вопроса')
    categories = models.ManyToManyField(Category, related_name="questions", verbose_name='категории')
    difficulty = models.PositiveSmallIntegerField(choices=DIFFICULTY_CHOICES, default=MEDIUM, verbose_name='сложность')
questions = None

for category in categories:
    category_id = category['id']

    easy_question_count = category['easy_question_count']
    medium_question_count = category['medium_question_count']
    hard_question_count = category['hard_question_count']

    easy_questions = Question.objects.filter(categories__id=category_id, difficulty=1)
    medium_questions = Question.objects.filter(categories__id=category_id, difficulty=2)
    hard_questions = Question.objects.filter(categories__id=category_id, difficulty=3)

    q1 = easy_questions.objects.random(easy_question_count)
    q2 = medium_questions.objects.random(medium_question_count)
    q3 = hard_questions.objects.random(hard_question_count)

    if questions:
        questions = questions.union(q1).union(q2).union(q3)
    else:
        questions = q1.union(q2).union(q3)

Метод random взят из библиотеки https://github.com/rremizov/django-random-queryset.git
Это библиотека позволяет получить QuerySet определенной длины, состоящий из элементов в случайном порядке.

Вот скрин запросов.

Это для одной категории, а если категорий, к примеру 10, то получится 60 запросов.
Как это дело оптимизировать? Ломаю голову 3 дня.

Может кто-нибудь подсказать, как мне решить задачу?

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.
9

Добрый день,

Я правильно понимаю, что вы в итоге сваливаете в кучу все вопросы в объект questions?

Возможно, что стоит это переписать иначе, например так

questions = None

easy_questions = Question.objects.filter(categories__in=categories, difficulty=1)
medium_questions = Question.objects.filter(categories__in=categories, difficulty=2)
hard_questions = Question.objects.filter(categories__in=categories, difficulty=3)

q1 = easy_questions.objects.random(easy_questions.count())
q2 = medium_questions.objects.random(medium_questions.count())
q3 = hard_questions.objects.random(hard_questions.count())

questions = q1.union(q2).union(q3)

Ну а если вам нужно просто срандомизировать всё в одной куче, то я бы так попробовал написать

questions = Question.objects.filter(categories__in=categories)
questions = questions.objects.random(questions.count())

-"Я правильно понимаю, что вы в итоге сваливаете в кучу все вопросы в объект questions?"
-Да. Это для дальнейших манипуляций) Возможно, переделаю.

Проблема в том, что для каждой категории нужно разное кол-во вопросов.
Например:
Категория биология, 1 легкий вопрос, 2 средних и 3 сложных;
Категория математика, 2 легких, 2 средних и 2 сложных....и т.д.

Кстати, сделать случайный порядок можно крайне просто:

Question.objects.all().order_by('?')

Документация

Вообще, я слабо понял задачу. Если чуть более понятно сможете описать ее, возможно, смогу помочь

На stackoverflow пишут, что это дорогая операция и лучше по-другому.
В доках пишут:
Note: order_by('?') queries may be expensive and slow, depending on the database backend you’re using.

Я конечно не спец(всего лишь любитель), но я попробую таким способ тоже, может в моем случае будет всё норм.

На счёт задачи.
Есть, к примеру, категории: биология, математика, физика...и т.д.
В каждой категории есть вопросы. У всех вопросов есть сложность от 1 до 3.

Нужно вот что.
Из категории физика нужно достать в случайном порядке 3 легких вопроса, 2 средних и 1 сложный;
Из категории математика 1 легкий, 3 средних, 2 сложных. Тоже в случайном порядке .

Моя проблема в том, что чем больше категорий, тем больше запросов.
Если нужно сделать поиск вопросов только по 1-ой категории, то 6 запросов, по 2-ум категориям 12 запросов и т.д.

Хотелось бы, чтобы при увеличении количества категорий, количество запросов не увеличивалось.

Руслан, посмотрите исходники той библиотеки, которую вы использовали. Там как раз и используется order_by('?') в конечном итоге, а ещё перед этим используется агрегация id всех объектов в queryset. Метод aggregate , кстати, тоже достаточно дорогой, я стараюсь избегать его или кешировать.

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

Я вашу задачу понял, но вопрос действительно сложный, пока мыслей нет.

К слову говоря, этот метод order_by('?') мне кажется относительно рандомным, поскольку он выполняет сортировку по случайному столбцу и в случайном направлении (по алфавиту/ против алфавита), если я правильно понял его суть. В общем он настолько рандомный, сколько у вас полей в модели.

Да, я посмотрел, вы абсолютно правы. Единственное, если я правильно понял, если срабатывает условие

if (aggregates["max_id"] - aggregates["min_id"]) + 1 == aggregates["count"]:
    return self.filter(
        id__in=strategies.min_max(
            amount,
            aggregates["min_id"],
            aggregates["max_id"],
            aggregates["count"],
        )
    )

то он возвращает QuerySet минуя order_by('?').

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

И да, после использования union, нельзя юзать filter, а так как мне нужно потом отфильтровать ещё раз, то я сделал так

easy_question_count = quiz.easy_question_count
medium_question_count = quiz.medium_question_count
hard_question_count = quiz.hard_question_count

easy_questions = questions.filter(difficulty=1)
medium_questions = questions.filter(difficulty=2)
hard_questions = questions.filter(difficulty=3)

easy_questions = get_random_questions(easy_questions, easy_question_count)
medium_questions = get_random_questions(medium_questions, medium_question_count)
hard_questions = get_random_questions(hard_questions, hard_question_count)

question_ids = easy_questions.union(medium_questions).union(hard_questions).values_list('id', flat=True)
questions = questions.filter(id__in=question_ids)

А для получения рандомных вопросов нужного мне количества написал вот такую функцию.

def get_random_questions(questions, question_count):
    question_id_array = list(questions.values_list('id', flat=True))
    random_ids = random.sample(question_id_array, min(len(question_id_array), question_count))
    questions = questions.filter(id__in=random_ids)
    return questions

Получилось всего 5 запросов.

Готов выслушать критику, если есть)

Comments

Only authorized users can post comments.
Please, Log in or Sign up
How to become an author?

Contribute to the evolution of the EVILEG community.

Learn how to become a site author.

Learn it
Donate

Good day, Dear Users!!!

I am Evgenii Legotckoi, developer of EVILEG. And it is my hobby project, which helps to learn programming another programmers and developers

If the site helped you, and you want also support the development of the site, than you can donate by following ways

PayPalYandex.Money
Timeweb

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting Timeweb
s
June 3, 2020, 2:56 a.m.
silo1995

C++ - Тест 003. Условия и циклы

  • Result:35points,
  • Rating points-10
AP
June 2, 2020, 10:11 p.m.
Aleksej Pikenin

C++ - Test 005. Structures and Classes

  • Result:75points,
  • Rating points2
June 2, 2020, 2:04 p.m.
Daniil Chizhevskij

C++ - Test 001. The first program and data types

  • Result:86points,
  • Rating points6
Last comments
June 5, 2020, 2:39 a.m.
Evgenij Legotskoj

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

По-моему, смысла в этом нет особого. Если делегат будет игнорировать настройки таблицы, то это приведёт ещё к большему непониманию, что вообще происходит, для программиста, который после вас буд…
June 5, 2020, 2:34 a.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Сижу, размышляю: можно ли переписать делегата так, чтобы независимо от настроек строк выделялись строки?
June 5, 2020, 2:31 a.m.
Evgenij Legotskoj

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Понятно. Я не обратил внимания на то, что там было в старом коде по настройкам строк :)
June 5, 2020, 2:27 a.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Разобрался. У вас изначально в проекте были вот эти настройки: ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows);ui->tableView->setSelectionMode(QAbstractItemVie…
June 4, 2020, 12:10 p.m.
IscanderChe

Qt/C++ - Tutorial 091. How to write a custom delegate controlling the highlighting of a row in a table

Полностью скопировал пример - всё правильно работает. Значит, где-то у меня ошибки в тестовом проекте. Буду разбираться. Извините за беспокойство. :)
Now discuss on the forum
MA
June 4, 2020, 3:46 a.m.
Mihail A

Qt- C++ QTableView подсветить строку

Спасибо.
f
June 3, 2020, 2:49 a.m.
fryn3

Можно ли сделать в QML таблицу как в Excel?

edi-tableview - нашел пока такое выглядит коряво, посмотрим что можно сделать
June 2, 2020, 3:46 a.m.
Evgenij Legotskoj

Медиа файлы Google Firebase

Картинки можете попробовать сжимать через QPixmap, там есть возможность установки scaleFactor, через него можете устанавливать нужные параметры. А что касается конвертации видео, то лучше п…
June 2, 2020, 3:01 a.m.
Evgenij Legotskoj

Перехват обращения к локальным файлам QWebEngineView

В вашем случае вполне адекватное решение. Так сказать меньше зло. В противном случае пришлось бы очень много переписывать и перепиливать.
a
June 1, 2020, 11:26 a.m.
alekseyttrv

SSL на Android

у меня стоит версия Qt 5.14.2. В настройках android поставил openssl из коробки, и этот прроект автоматически стянулся. Достаточно было только добавить в .pro-файл строку после этого и все …
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB