Как я могу оптимизировать этот запрос и избавиться от подзапросов?

Столкнулся с проблемой DDoS, из за огромного запроса. Затрачиватеся слишком много памяти и долгая обработка запроса.

Единственный запрос, который можно обработать данные правильно предоставлен ниже, но он ниразу не оптимизированный.

Имеется таблица course:

введите сюда описание изображения

Имеется таблица block_of_course:

введите сюда описание изображения

Имеется таблица user_to_course_completed_block:

введите сюда описание изображения

Имеется таблица employee:

введите сюда описание изображения

Для решения получился вот такой запрос:

SELECT
    CONCAT_WS(' ', employee.first_name, employee.middle_name, employee.last_name) AS full_name,
    GROUP_CONCAT(DISTINCT
        CONCAT_WS('/', course.name,
            (
                SELECT count(DISTINCT bfc.id)
                FROM user_to_course_completed_block as user_to_course_completed_block
                    INNER JOIN block_for_course as bfc ON bfc.id = user_to_course_completed_block.block_id
                WHERE user_to_course_completed_block.user_id = user_to_course_completed_block.user_id and bfc.course_id = course.id
            ),
                (SELECT COUNT(*) FROM block_for_course WHERE block_for_course.course_id = course.id)
            )
     ) as result,
    employee.user_id,
    course.category_id
FROM
    user_to_course_completed_block AS uc_complete
        INNER JOIN employee ON employee.user_id = uc_complete.user_id
        INNER JOIN block_for_course ON block_for_course.id = uc_complete.block_id
        INNER JOIN course ON course.id = block_for_course.course_id
group by uc_complete.user_id, category_id;

Уже пробовал использовать и временные таблицы и пытался через join подсоединить еще, но результат всегда разный и с таким результатом нельзя работать в gridview. Надо именно такой который выводит этот результат, ну или максимально приближенный: Пример

Версия mariaDb - 10.11.6


Ответы (1 шт):

Автор решения: Arkee

То что первое приходит в голову - попытаться сократить/упростить повторных обращений в таблицы и методы группировки/сортировки. Либо как-то ограничивать получаемый набор данных - фильтровать по id, ставить limit/offset и пр. Ну и напоследок не забываем про индексы/статистику. При оптимизации нет такого понятия как одного единственного решения, ведь в конечном итоге упретесь в tradeoff по cpu/mem/disk.

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

-- кто, какой курс, сколько блоков выполнил
WITH user_completed AS (
  SELECT
    uc.user_id,
    c.id AS course_id,
    c.category_id,
    c.name,
    COUNT(bc.id) AS block_completed
  FROM
    user_to_course_completed_block uc
    INNER JOIN block_for_course bc ON bc.id = uc.block_id
    INNER JOIN course c ON c.id = bc.course_id
  -- если нужно вывести по конкретному user_id
  -- WHERE
  --   uc.user_id = 1
  GROUP BY
    uc.user_id,
    c.category_id,
    c.id
),

-- сколько всего блоков имеется в курсах
user_course AS (
  SELECT
    uc.user_id,
    uc.category_id,
    uc.name,
    uc.block_completed,
    COUNT(bc.id) AS block_total
  FROM
    user_completed uc
    INNER JOIN block_for_course bc ON bc.course_id = uc.course_id
  GROUP BY
    uc.user_id,
    uc.course_id,
    uc.category_id
)

SELECT
  CONCAT_WS(' ', e.first_name, e.middle_name, e.last_name) AS full_name,
  GROUP_CONCAT(
    CASE
      WHEN uc.block_total > 0
      THEN CONCAT_WS('/', uc.name, COALESCE(uc.block_completed, 0), uc.block_total)
      ELSE NULL
    END
  ) AS result,
  e.user_id,
  uc.category_id
FROM
  employee e
  -- LEFT JOIN user_course uc ON uc.user_id = e.user_id
  INNER JOIN user_course uc ON uc.user_id = e.user_id
GROUP BY
  e.user_id,
  uc.category_id
→ Ссылка