JavaRush /Курси /SQL SELF /Аналіз типових помилок при створенні аналітичних процедур...

Аналіз типових помилок при створенні аналітичних процедур

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

Сьогодні, щоб завершити цю епічну подорож по PL/pgSQL, давай зрозуміємо: помилки в аналітичних процедурах неминучі. Чому? Бо в аналітиці працюють з великими даними, складними розрахунками і часом дуже хитрими умовами. Чим складніший запит або процедура, тим більше вона схожа на лабіринт, де пара невірних кроків може привести до некоректних результатів.

На щастя, більшість помилок типові і їх можна передбачити (і уникнути). Розглянемо їх одну за одною.

1. Відсутність індексів на ключових полях

Індекси — це як навігатор у світі баз даних. Якщо їх нема, база даних змушена бігати пішки по всіх рядках таблиці. У невеликих таблицях це ще терпимо, але як тільки даних стає мільйони рядків, твої запити починають працювати повільніше, ніж Windows XP на Pentium III.

Припустимо, у тебе є таблиця замовлень, і ти хочеш порахувати продажі за останній місяць:

SELECT SUM(order_total)
FROM orders
WHERE order_date >= CURRENT_DATE - INTERVAL '1 month';

Якщо на полі order_date немає індексу, PostgreSQL робить повний перегляд таблиці (Seq Scan). А це майже завжди повільно.

Рішення: використовуй індекси! Для цього достатньо команди:

CREATE INDEX idx_order_date ON orders (order_date);

Тепер PostgreSQL зможе шукати в таблиці по полю order_date набагато швидше.

Використання неефективних запитів

Деякі запити виглядають красиво, але працюють як бетонна цеглина замість ключа. Наприклад, використання підзапитів, які можна замінити об'єднанням таблиць (JOIN), або зайва фільтрація.

Замість цього:

SELECT product_id, SUM(order_total)
FROM orders
WHERE product_id IN (SELECT id FROM products WHERE category = 'electronics')
GROUP BY product_id;

Краще зробити так:

SELECT o.product_id, SUM(o.order_total)
FROM orders o
JOIN products p ON o.product_id = p.id
WHERE p.category = 'electronics'
GROUP BY o.product_id;

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

Неправильна структура тимчасових таблиць

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

Наводимо приклад. Створимо тимчасову таблицю для проміжних розрахунків:

CREATE TEMP TABLE temp_sales AS
SELECT region, SUM(order_total) AS total_sales
FROM orders
GROUP BY region;

Але потім треба виконати фільтрацію по колонці total_sales, а індексу на цьому полі нема.

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

CREATE INDEX idx_temp_sales_total_sales ON temp_sales (total_sales);

Помилки в розрахунках (наприклад, ділення на нуль)

Ділення на нуль — це класична проблема аналітики. SQL не буде люб'язно закривати очі на цю помилку, він просто розірве виконання запиту.

Припустимо, ти хочеш обчислити середню вартість замовлень:

SELECT SUM(order_total) / COUNT(*) AS avg_order_value
FROM orders;

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

Щоб уникнути проблеми, використовуй обробку випадків, коли лічильник дорівнює нулю:

SELECT
    CASE 
        WHEN COUNT(*) = 0 THEN 0
        ELSE SUM(order_total) / COUNT(*)
    END AS avg_order_value
FROM orders;

Відсутність логування і контролю виконання

Процедури PL/pgSQL можуть бути складними і складатися з кількох етапів: від проміжних обчислень до фінальних звітів. Якщо в цьому ланцюжку стається збій, без логування ти просто не дізнаєшся, на якому етапі і чому все пішло не так.

Припустимо, ми створюємо процедуру для розрахунку метрик, але забуваємо перевіряти очікувані дані на кожному етапі. В результаті вся процедура падає, коли стикається з неочікуваними даними (наприклад, порожніми таблицями).

Щоб уникнути цього, можна додати логування на кожному важливому етапі процедури. Наприклад:

RAISE NOTICE 'Початок розрахунку продажів';
-- Твій код тут...

RAISE NOTICE 'Модуль % завершено успішно', модуль;

Для складніших процедур краще зберігати логи в окрему таблицю:

CREATE TABLE log_analytics (
    log_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    log_message TEXT
);

У процедурі додавай:

INSERT INTO log_analytics (log_message)
VALUES ('Процедуру успішно завершено');

Проблеми продуктивності через відсутність оптимізації

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

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

CREATE OR REPLACE FUNCTION calculate_sales()
RETURNS VOID AS $$
BEGIN
    -- Перерахунок для всіх регіонів
    INSERT INTO sales_metrics(region, total_sales)
    SELECT region, SUM(order_total)
    FROM orders
    GROUP BY region;
END;
$$ LANGUAGE plpgsql;

Це створює зайве навантаження.

Як з цим боротися? Додай можливість фільтрувати дані, передаючи регіон як параметр:

CREATE OR REPLACE FUNCTION calculate_sales(p_region TEXT)
RETURNS VOID AS $$
BEGIN
    INSERT INTO sales_metrics(region, total_sales)
    SELECT region, SUM(order_total)
    FROM orders
    WHERE region = p_region
    GROUP BY region;
END;
$$ LANGUAGE plpgsql;

Тепер процедура не буде обробляти зайві дані, а запит завершиться швидше.

Ігнорування інструментів аналізу продуктивності

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

Наводимо приклад. Проблема в цьому запиті буде видна через EXPLAIN ANALYZE:

SELECT *
FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2023;

Цей запит неефективний, бо функція EXTRACT() відключає використання індексів.

Вирішити проблему можна так. Проаналізуй запит через:

EXPLAIN ANALYZE
SELECT *
FROM orders
WHERE order_date >= DATE '2023-01-01' AND order_date < DATE '2024-01-01';

Як уникати типових помилок?

Щоб уникати помилок, дотримуйся таких практик:

  1. Використовуй індекси на полях, які беруть участь у фільтрації або з'єднаннях.
  2. Оптимізуй запити: прибирай зайві підзапити, використовуй JOIN.
  3. Логуй виконання. Це спростить відлагодження, якщо щось піде не так.
  4. Завжди перевіряй свої процедури інструментами типу EXPLAIN ANALYZE.
  5. Помітив проблему з продуктивністю? Подумай про використання партиціонування або переробку логіки запиту.

Тепер ти озброєний знаннями, щоб передбачати й уникати помилок, які могли б залишити твоїх аналітиків без кавомашини і Wi-Fi через повільні запити.

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