Руслан Волшебник
Руслан ВолшебникDec. 13, 2019, 6:53 p.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.

Do you like it? Share on social networks!

9
Evgenii Legotckoi
  • Dec. 13, 2019, 8 p.m.

Добрый день,

Я правильно понимаю, что вы в итоге сваливаете в кучу все вопросы в объект 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)
    Evgenii Legotckoi
    • Dec. 13, 2019, 8:02 p.m.

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

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

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

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

        Илья Чичак
        • Dec. 13, 2019, 8:26 p.m.
        • (edited)

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

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

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

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

          Руслан Волшебник
          • Dec. 13, 2019, 8:42 p.m.
          • (edited)

          На 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('?') мне кажется относительно рандомным, поскольку он выполняет сортировку по случайному столбцу и в случайном направлении (по алфавиту/ против алфавита), если я правильно понял его суть. В общем он настолько рандомный, сколько у вас полей в модели.

                Руслан Волшебник
                • Dec. 13, 2019, 9:16 p.m.
                • (edited)

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

                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('?').

                  Руслан Волшебник
                  • Dec. 15, 2019, 3:38 p.m.
                  • (edited)

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

                  И да, после использования 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
                    Ua

                    Qt - Test 001. Signals and slots

                    • Result:84points,
                    • Rating points4
                    Ua

                    Qt - Test 001. Signals and slots

                    • Result:42points,
                    • Rating points-8
                    ОК

                    Qt - Test 001. Signals and slots

                    • Result:47points,
                    • Rating points-6
                    Last comments
                    ИМ
                    Игорь МаксимовNov. 22, 2024, 9:51 p.m.
                    Django - Tutorial 017. Customize the login page to Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
                    Evgenii Legotckoi
                    Evgenii LegotckoiOct. 31, 2024, 11:37 p.m.
                    Django - Lesson 064. How to write a Python Markdown extension Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
                    A
                    ALO1ZEOct. 19, 2024, 5:19 p.m.
                    Fb3 file reader on Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
                    ИМ
                    Игорь МаксимовOct. 5, 2024, 4:51 p.m.
                    Django - Lesson 064. How to write a Python Markdown extension Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
                    d
                    dblas5July 5, 2024, 8:02 p.m.
                    QML - Lesson 016. SQLite database and the working with it in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                    Now discuss on the forum
                    f
                    firstlunoxodFeb. 15, 2025, 1:46 p.m.
                    Рисование на QGraphicsScene при зажатой кнопке мыши Подскажите, пожалуйста! Как данный класс можно дополнить, чтобы созданные объекты можно было перемещать мышкой по сцене?
                    Дмитрий
                    ДмитрийFeb. 3, 2025, 4:24 p.m.
                    Создание deb-пакета. Как создать ярлык на рабочем столе после установки собственного deb-пакета? Всем привет. Сделал свой deb-пакет с программой. Всё устанавливается и работает. Ставлю по пути /usr/bin/my_application. Как для пользователя при установке пакета сразу создать ярлык на раб…
                    NW
                    Nayo WaiJan. 30, 2025, 7:22 p.m.
                    не запускается компьютер!!! Не запускается компьютер (точнее работает блок , но сам монитор вообще жесть)В общем я ничего с интернета не скачивала в последнее время. На компе никаких левых пр…
                    n
                    nklyJan. 3, 2025, 12:52 p.m.
                    Нужно запретить перемещение только некоторых итемов, остальные перемещать можно. Вопрос решен. Узнать QModelIndex элемента на который мы перетаскиваем другой элемент, можно с помощью функции indexAt(event->position().toPoint()) представления QTreeViev вызываемой в переопр…
                    M
                    MarselAug. 17, 2023, 12:26 a.m.
                    OAuth2.0 через VK, получение email Спасибо большое за помощь и простите за то что отнял время своей невнимательностью.

                    Follow us in social networks