JavaRush /Курси /SQL SELF /Типові помилки при роботі з CTE та як їх уникнути

Типові помилки при роботі з CTE та як їх уникнути

SQL SELF
Рівень 28 , Лекція 4
Відкрита

І ось настав час поговорити про темний бік роботи з CTE — типові помилки. Навіть найкрутіший запит може впасти, якщо неправильно юзати ці потужні інструменти. Але не переймайся, у нас є для тебе ціла інструкція по їх діагностиці та запобіганню!

1. Помилка: матеріалізація CTE та її наслідки

Одна з ключових фішок PostgreSQL при роботі з CTE — це їх матеріалізація за замовчуванням. Це означає, що результат CTE обробляється і тимчасово зберігається в пам’яті (або на диску, якщо даних забагато). Якщо запитів багато або об’єм даних великий, це може серйозно загальмувати виконання.

Приклад:

WITH heavy_data AS (
    SELECT * FROM large_table
)
SELECT * FROM heavy_data WHERE column_a > 100;

На перший погляд здається, що CTE просто фільтрує дані. Але насправді heavy_data спочатку повністю завантажується і матеріалізується, а тільки потім застосовується фільтрація. Це може зайняти купу часу.

Як уникнути?

З PostgreSQL версії 12 з’явилась можливість використовувати CTE як inline-вираз (аналог підзапиту), що вирішує проблему матеріалізації. Для цього достатньо вказувати такі CTE, які використовуються лише один раз і немає потреби зберігати проміжні результати.

Приклад оптимізованого підходу:

WITH inline_data AS MATERIALIZED (
    SELECT * FROM large_table
)
SELECT * FROM inline_data WHERE column_a > 100;

Порада: якщо хочеш, щоб матеріалізація все ж відбувалась, вкажи MATERIALIZED. Якщо ні — юзай NOT MATERIALIZED.

2. Помилка: рекурсивні CTE зациклюються

Рекурсивні CTE — штука потужна, але їх використання без обмежень на глибину ітерацій може призвести до зациклення. Це не тільки загальмує виконання, а й з’їсть всі доступні ресурси.

Приклад:

WITH RECURSIVE endless_loop AS (
    SELECT 1 AS value

    UNION ALL

    SELECT value + 1
    FROM endless_loop
)
SELECT * FROM endless_loop;

Ця штука буде породжувати нескінченну кількість рядків, бо немає умови, яка зупиняє рекурсію.

Як уникнути?

Додай чітку умову зупинки за допомогою WHERE. Наприклад:

WITH RECURSIVE limited_loop AS (
    SELECT 1 AS value

    UNION ALL

    SELECT value + 1
    FROM limited_loop
    WHERE value < 10
)
SELECT * FROM limited_loop;

Порада: якщо ти юзаєш рекурсивні CTE для великих ієрархій, обмеж глибину рекурсії через опцію max_recursion_depth PostgreSQL.

3. Помилка: неправильне використання UNION і UNION ALL

Коли ти об’єднуєш базовий і рекурсивний запити в CTE, невірний вибір між UNION і UNION ALL може призвести до неочікуваних результатів. Наприклад, UNION прибирає дублікати рядків, що має додаткову обчислювальну вартість.

Приклад:

WITH RECURSIVE employee_hierarchy AS (
    SELECT employee_id, manager_id
    FROM employees
    WHERE manager_id IS NULL

    UNION  -- Тут краще використовувати UNION ALL

    SELECT e.employee_id, e.manager_id
    FROM employees e
    JOIN employee_hierarchy eh ON e.manager_id = eh.employee_id
)
SELECT * FROM employee_hierarchy;

У цьому прикладі UNION може видалити важливі рядки з ієрархії, якщо вони випадково повторюються. А ще це загальмує запит!

Як виправити?

Використовуй UNION ALL, якщо тобі не треба жорстко прибирати дублікати:

UNION ALL

4. Помилка: забагато CTE в одному запиті

Намагаючись зробити запит максимально структурованим, дехто починає додавати десятки CTE. Це не тільки заплутує код, а й перевантажує планувальник запитів PostgreSQL.

Приклад:

WITH cte1 AS (...),
     cte2 AS (...),
     cte3 AS (...),
     ...
     cte20 AS (...)
SELECT ...
FROM cte20;

Виглядає як нічний жах для будь-якого розробника.

Як виправити?

— Розбий запит на кілька простіших. Замість одного мегазапиту з десятками CTE — зроби кілька незалежних запитів.

— Ще один варіант: для проміжних результатів, які треба використовувати кілька разів, зберігай у тимчасових таблицях.

5. Помилка: складні CTE без індексації

Якщо CTE працює з великою кількістю даних, але ти забув додати індекси для таблиць, запити будуть виконуватись дуже повільно. Індекси — це як допінг для твоєї бази даних.

Приклад:

WITH filtered_data AS (
    SELECT * FROM large_table WHERE unindexed_column = 'value'
)
SELECT * FROM filtered_data;

Як виправити?

Перед використанням CTE переконайся, що твої таблиці оптимізовані:

CREATE INDEX idx_large_table ON large_table(unindexed_column);

6. Помилка: спроба використовувати CTE для багаторазового виклику даних

CTE створюється, виконується, а потім "заморожується". Якщо тобі треба використати його результат у кількох місцях, дані не будуть перераховуватись — і це іноді призводить до помилок.

Приклад:

WITH data AS (
    SELECT x, y FROM some_table
)
SELECT x FROM data
WHERE y > 10;
-- Якщо ще раз треба буде перерахувати data, цього не станеться.

Як виправити?

Якщо потрібна динаміка або перерахунок, можливо, CTE — не найкращий вибір. Використовуй підзапити.

7. Помилка: відсутність коментарів

CTE можуть бути крутим інструментом, але кому потрібен складний SQL-запит, який ніхто, включаючи тебе, не зможе прочитати через два тижні?

Приклад:

WITH data_filtered AS (
    SELECT *
    FROM large_table
    WHERE some_column > 100
)
SELECT * FROM data_filtered;

Через місяць вже ніхто не згадає, навіщо фільтрувались ці дані!

Тому коментуй запити, особливо якщо використовуєш складні або рекурсивні CTE:

WITH data_filtered AS (
    -- Фільтр даних за умовою значень стовпця some_column > 100
    SELECT *
    FROM large_table
    WHERE some_column > 100
)
SELECT * FROM data_filtered;

8. Помилка: зловживання CTE замість тимчасових таблиць

Іноді тимчасові таблиці підходять набагато краще. Наприклад, якщо результат треба використовувати багато разів у різних запитах або ти працюєш з величезним набором даних.

Приклад:

WITH temp_data AS (
    SELECT * FROM large_table
)
SELECT * FROM temp_data WHERE column_a > 100;
SELECT * FROM temp_data WHERE column_b < 50;

Такий запит з CTE виконається двічі, хоча дані не змінюються!

Як виправити?

Створи тимчасову таблицю, якщо дані використовуються багато разів:

CREATE TEMP TABLE temp_table AS
SELECT * FROM large_table;

SELECT * FROM temp_table WHERE column_a > 100;
SELECT * FROM temp_table WHERE column_b < 50;

Фінальна порада

Як і з будь-якою потужною фічею, при роботі з CTE важливо розуміти, що вони не завжди найкращий інструмент для вирішення задач. Обмірковуй, навіщо і як ти їх використовуєш. Підхід "чим більше CTE, тим краще" може серйозно погіршити продуктивність і читабельність коду. І, звісно, не забувай проводити тести продуктивності та оптимізувати запити.

1
Опитування
Оптимізація запитів, рівень 28, лекція 4
Недоступний
Оптимізація запитів
Оптимізація запитів
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ