Руслан Волшебник
Руслан Волшебник13. Dezember 2019 07:53

Как уменьшить количество запросов в 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 дня.

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

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

9
Evgenii Legotckoi
  • 13. Dezember 2019 09:00

Добрый день,

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

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

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

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

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

        Илья Чичак
        • 13. Dezember 2019 09:26
        • (bearbeitet)

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

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

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

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

          Руслан Волшебник
          • 13. Dezember 2019 09:42
          • (bearbeitet)

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

                Руслан Волшебник
                • 13. Dezember 2019 10:16
                • (bearbeitet)

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

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

                  Руслан Волшебник
                  • 15. Dezember 2019 04:38
                  • (bearbeitet)

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

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

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

                    Kommentare

                    Nur autorisierte Benutzer können Kommentare posten.
                    Bitte Anmelden oder Registrieren
                    Letzte Kommentare
                    ИМ
                    Игорь Максимов5. Oktober 2024 07:51
                    Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
                    d
                    dblas55. Juli 2024 11:02
                    QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
                    k
                    kmssr8. Februar 2024 18:43
                    Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
                    Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
                    EVA
                    EVA25. Dezember 2023 10:30
                    Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
                    Jetzt im Forum diskutieren
                    J
                    JacobFib17. Oktober 2024 03:27
                    добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
                    JW
                    Jhon Wick1. Oktober 2024 15:52
                    Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
                    КГ
                    Кирилл Гусарев27. September 2024 09:09
                    Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
                    F
                    Fynjy22. Juli 2024 04:15
                    при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

                    Folgen Sie uns in sozialen Netzwerken