Просунутий SQL для співбесід Data Analyst: підзапити, зведені таблиці та оптимізація запитів 2026
Детальний гайд з просунутого SQL для співбесід на позицію Data Analyst: корельовані підзапити, PIVOT-запити, оптимізація через EXPLAIN та стратегії індексування.

Позиції Data Analyst залишаються одними з найбільш затребуваних на ринку IT у 2026 році. Роботодавці в таких компаніях як Meta, Stripe та Airbnb тестують кандидатів на корельованих підзапитах, pivot-трансформаціях та оптимізації запитів — навичках, які відрізняють junior-аналітиків від senior-фахівців. Цей гайд охоплює саме ті патерни, які з'являються на технічних етапах відбору, з production-ready прикладами на PostgreSQL 17.
SQL-співбесіди для Data Analyst у 2026 році фокусуються на трьох напрямках: декомпозиція складної логіки на читабельні підзапити або CTE, трансформація рядкових даних у pivot-звіти за допомогою умовної агрегації та демонстрація розуміння продуктивності запитів через EXPLAIN-плани та індексування. Більшість кандидатів провалюється на оптимізації — знання цієї теми дає суттєву перевагу.
Корельовані підзапити проти звичайних підзапитів на SQL-співбесідах
Корельований підзапит посилається на колонку із зовнішнього запиту, що змушує рушій бази даних перераховувати підзапит для кожного рядка в зовнішньому результуючому наборі. Звичайний (некорельований) підзапит виконується один раз і повертає фіксований результат. Інтерв'юери спеціально перевіряють, чи розуміють кандидати, коли кожен підхід доречний — і коли JOIN працює краще за обидва.
Звичайний підзапит добре працює для порогових порівнянь. Внутрішній запит виконується один раз, а зовнішній запит фільтрує за цим єдиним значенням.
-- regular_subquery_threshold.sql
-- Find all orders above the average order value
SELECT
order_id,
customer_id,
total_amount
FROM orders
WHERE total_amount > (
-- Executes once, returns a single scalar value
SELECT AVG(total_amount)
FROM orders
);Корельований підзапит необхідний, коли порівняння залежить від контексту кожного рядка. Класичне питання на співбесіді: "Знайдіть працівників, які заробляють більше за середню зарплату у своєму відділі."
-- correlated_subquery_department.sql
-- Employees earning above their department average
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
WHERE e.salary > (
-- Re-evaluated for each employee's department
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.department = e.department
);Корельована версія повторно обчислює AVG для відділу кожного рядка. На великих таблицях це стає витратним. Оптимізований підхід використовує CTE або віконну функцію для одноразового обчислення середніх значень по відділах.
-- optimized_with_cte.sql
-- Same result, single pass over the data
WITH dept_avg AS (
SELECT
department,
AVG(salary) AS avg_salary
FROM employees
GROUP BY department
)
SELECT
e.employee_id,
e.name,
e.department,
e.salary
FROM employees e
JOIN dept_avg d ON e.department = d.department
WHERE e.salary > d.avg_salary;Інтерв'юери шукають кандидатів, які здатні написати корельовану версію, а потім відрефакторити її. Демонстрація обох підходів — з поясненням компромісу продуктивності — сигналізує про senior-рівень володіння SQL.
Патерни підзапитів на співбесідах для Data Analyst
Три патерни підзапитів регулярно з'являються на співбесідах: EXISTS для перевірки належності, скалярні підзапити в SELECT та похідні таблиці у FROM.
EXISTS проти IN — EXISTS зупиняється одразу після знаходження першого збігу, що робить його швидшим за IN для великих результуючих наборів. IN матеріалізує весь результат підзапиту перед порівнянням.
-- exists_vs_in.sql
-- Customers who placed at least one order in 2026 (EXISTS — preferred)
SELECT c.customer_id, c.name
FROM customers c
WHERE EXISTS (
SELECT 1
FROM orders o
WHERE o.customer_id = c.customer_id
AND o.order_date >= '2026-01-01'
);
-- Equivalent with IN (slower on large datasets)
SELECT customer_id, name
FROM customers
WHERE customer_id IN (
SELECT customer_id
FROM orders
WHERE order_date >= '2026-01-01'
);Скалярний підзапит у SELECT — корисний для додавання обчислюваної колонки без повного JOIN.
-- scalar_subquery_select.sql
-- Each product with its category's total revenue
SELECT
p.product_id,
p.product_name,
p.category,
(
SELECT SUM(oi.quantity * oi.unit_price)
FROM order_items oi
JOIN products p2 ON oi.product_id = p2.product_id
WHERE p2.category = p.category
) AS category_total_revenue
FROM products p;Цей патерн зручний для додавання однієї-двох колонок. Для більшої кількості CTE з JOIN є читабельнішим і зазвичай швидшим.
Готовий до співбесід з Data Analytics?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Зведені запити з умовною агрегацією в SQL
Зведені (pivot) запити трансформують рядкові дані в колонкові зведення — перетворюючи місяці, збережені як рядки, в колонки на кшталт jan_revenue, feb_revenue. Хоча деякі бази даних пропонують нативний оператор PIVOT, портативний підхід використовує вирази CASE всередині агрегатних функцій. Саме цю версію очікують інтерв'юери.
Зведений запит поєднує умовну агрегацію з виразами CASE всередині агрегатних функцій. Кожен вираз CASE фільтрує конкретну категорію, а агрегат (SUM, COUNT, AVG) згортає відфільтровані рядки до єдиного значення на групу.
-- pivot_monthly_revenue.sql
-- Monthly revenue pivot: one row per product, one column per month
SELECT
product_id,
product_name,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 1
THEN quantity * unit_price ELSE 0 END) AS jan_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 2
THEN quantity * unit_price ELSE 0 END) AS feb_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 3
THEN quantity * unit_price ELSE 0 END) AS mar_revenue,
SUM(CASE WHEN EXTRACT(MONTH FROM order_date) = 4
THEN quantity * unit_price ELSE 0 END) AS apr_revenue,
-- Repeat for remaining months
SUM(quantity * unit_price) AS total_revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE o.order_date >= '2026-01-01'
GROUP BY product_id, product_name
ORDER BY total_revenue DESC;Та сама техніка умовної агрегації працює для багатовимірних зведень. Поширене питання на співбесіді просить кандидатів побудувати звіт активності користувачів за днем тижня та типом пристрою.
-- pivot_user_activity.sql
-- User activity: sessions by day of week and device type
SELECT
user_id,
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) AS mobile_sessions,
COUNT(CASE WHEN device_type = 'desktop' THEN 1 END) AS desktop_sessions,
COUNT(CASE WHEN device_type = 'tablet' THEN 1 END) AS tablet_sessions,
ROUND(
COUNT(CASE WHEN device_type = 'mobile' THEN 1 END) * 100.0 / COUNT(*),
1
) AS mobile_pct
FROM user_sessions
WHERE session_date >= CURRENT_DATE - INTERVAL '30 days'
GROUP BY user_id
HAVING COUNT(*) >= 5 -- Only users with meaningful activity
ORDER BY mobile_pct DESC;Умовна агрегація з CASE є фундаментальною навичкою Data Analyst. Документація PostgreSQL з умовних виразів описує додаткові патерни, такі як FILTER-клаузи, що спрощують цей синтаксис.
Динамічний PIVOT з CROSSTAB у PostgreSQL
Розширення tablefunc PostgreSQL надає CROSSTAB для випадків, коли категорії зведення невідомі на момент написання запиту. Хоча менш поширене на співбесідах, воно зустрічається в домашніх завданнях.
-- crosstab_dynamic_pivot.sql
-- Enable the extension (once per database)
CREATE EXTENSION IF NOT EXISTS tablefunc;
-- Revenue by product and quarter using CROSSTAB
SELECT *
FROM crosstab(
$$
SELECT
product_name,
'Q' || EXTRACT(QUARTER FROM order_date)::TEXT AS quarter,
SUM(quantity * unit_price) AS revenue
FROM order_items oi
JOIN orders o ON oi.order_id = o.order_id
JOIN products p ON oi.product_id = p.product_id
WHERE EXTRACT(YEAR FROM o.order_date) = 2026
GROUP BY product_name, quarter
ORDER BY product_name, quarter
$$,
$$ VALUES ('Q1'), ('Q2'), ('Q3'), ('Q4') $$
) AS pivot_table(
product_name TEXT,
q1_revenue NUMERIC,
q2_revenue NUMERIC,
q3_revenue NUMERIC,
q4_revenue NUMERIC
);CROSSTAB вимагає, щоб вихідний запит повертав рівно три колонки: ідентифікатор рядка, категорію та значення. Другий аргумент перелічує всі можливі значення категорій. Відсутні комбінації генерують NULL замість 0.
Оптимізація SQL-запитів: EXPLAIN-плани та аналіз вартості
Питання з оптимізації запитів перевіряють, чи може кандидат діагностувати повільні запити — не просто писати правильні. Основним діагностичним інструментом є EXPLAIN ANALYZE, який виконує запит і повідомляє фактичні часи виконання, кількість рядків та операції, обрані планувальником.
-- explain_analyze_example.sql
-- Analyze a slow query to identify bottlenecks
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT
c.customer_id,
c.name,
COUNT(o.order_id) AS order_count,
SUM(o.total_amount) AS lifetime_value
FROM customers c
JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2025-01-01'
GROUP BY c.customer_id, c.name
HAVING SUM(o.total_amount) > 1000
ORDER BY lifetime_value DESC;Ключові елементи виводу EXPLAIN, про які запитують інтерв'юери:
- Seq Scan — повне сканування таблиці. Прийнятне на малих таблицях; проблематичне на мільйонах рядків. Вказує на відсутній індекс.
- Index Scan / Index Only Scan — запит використовує індекс. Index Only Scan швидший, оскільки читає дані безпосередньо з індексу без звернення до таблиці.
- Nested Loop / Hash Join / Merge Join — стратегія з'єднання. Hash Join підходить для великих несортованих наборів. Merge Join оптимальний, коли обидві сторони попередньо відсортовані. Nested Loop працює для малих зовнішніх таблиць, з'єднаних з індексованими внутрішніми.
- Rows — оцінена проти фактичної кількості рядків. Великі розбіжності свідчать про застарілу статистику (запустіть ANALYZE) або слабо селективні предикати.
- Buffers: shared hit / read — коефіцієнт влучень у кеш. Високі значення read означають, що дані не в пам'яті.
Документація PostgreSQL з EXPLAIN описує всі доступні формати виводу та опції.
Стратегії індексування для оптимізації SQL на рівні співбесіди
Індексування є найвпливовішою технікою оптимізації та частою темою співбесід. Питання зазвичай має форму: "Маючи цей повільний запит, які індекси покращать його?"
Три патерни індексування покривають більшість сценаріїв на співбесідах:
1. Одноколонковий індекс на високоселективних WHERE-клаузах
-- indexing_strategies.sql
-- Index on order_date: filters a small percentage of rows
CREATE INDEX idx_orders_date ON orders (order_date);
-- Composite index for queries filtering on both columns
CREATE INDEX idx_orders_customer_date
ON orders (customer_id, order_date);
-- Covering index: includes columns needed in SELECT
-- Enables Index Only Scan (no table access)
CREATE INDEX idx_orders_covering
ON orders (customer_id, order_date)
INCLUDE (total_amount, status);2. Порядок колонок у складеному індексі має значення. Найлівіша колонка повинна відповідати WHERE-клаузі запиту. Індекс на (customer_id, order_date) прискорює запити з фільтрацією по customer_id або по customer_id AND order_date, але НЕ запити з фільтрацією лише по order_date.
3. Часткові індекси зменшують розмір індексу та витрати на обслуговування, коли часто запитується лише підмножина рядків.
-- partial_index.sql
-- Only index active orders (smaller, faster index)
CREATE INDEX idx_orders_active
ON orders (customer_id, order_date)
WHERE status = 'active';
-- Only index recent data for dashboard queries
CREATE INDEX idx_orders_recent
ON orders (order_date)
WHERE order_date >= '2026-01-01';Слід уникати індексування кожної колонки — кожен індекс уповільнює операції INSERT, UPDATE та DELETE. Таблиця з понад 10 індексами при інтенсивних операціях запису зазнає помітної деградації. Також слід уникати індексування колонок з низькою кардинальністю (як boolean-прапорці), якщо вони не є частиною складеного або часткового індексу.
Поширені антипатерни запитів, що вбивають продуктивність
Інтерв'юери подають навмисно повільні запити та просять кандидатів ідентифікувати проблему. Ці п'ять антипатернів відповідають за більшість реальних проблем з продуктивністю.
Функції на індексованих колонках — застосування функції до індексованої колонки унеможливлює використання індексу.
-- anti_patterns.sql
-- BAD: function on indexed column disables the index
SELECT * FROM orders
WHERE EXTRACT(YEAR FROM order_date) = 2026;
-- GOOD: range comparison uses the index
SELECT * FROM orders
WHERE order_date >= '2026-01-01'
AND order_date < '2027-01-01';SELECT * у production-запитах — витягує всі колонки, включаючи великі поля TEXT або BYTEA. Вказуйте лише потрібні колонки.
NOT IN з NULL — якщо підзапит повертає будь-яке NULL, NOT IN не повертає жодного рядка. Замініть на NOT EXISTS.
-- not_in_null_trap.sql
-- BAD: returns empty if any customer_id is NULL in subquery
SELECT * FROM customers
WHERE customer_id NOT IN (
SELECT customer_id FROM orders
);
-- GOOD: NOT EXISTS handles NULLs correctly
SELECT * FROM customers c
WHERE NOT EXISTS (
SELECT 1 FROM orders o
WHERE o.customer_id = c.customer_id
);Неявне приведення типів — порівняння VARCHAR-колонки з цілим числом змушує виконувати приведення для кожного рядка, оминаючи індекс. Завжди явно узгоджуйте типи.
Відсутній LIMIT у дослідницьких запитах — сканування мільйонів рядків, коли потрібні лише перші 10. Завжди додавайте LIMIT під час розробки та в пагінованих API-запитах.
Для розвитку навичок роботи з підзапитами та CTE в SQL варто працювати над реальними сценаріями співбесід. Поєднання рефакторингу підзапитів з EXPLAIN-аналізом розвиває діагностичну інтуїцію, яку шукають інтерв'юери.
Готовий до співбесід з Data Analytics?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Висновки
- Корельовані підзапити виконуються повторно для кожного рядка — рефакторіть їх до CTE або віконних функцій для кращої продуктивності на великих наборах даних
- EXISTS перевершує IN при перевірці належності, особливо коли підзапит повертає багато рядків або містить NULL
- Умовна агрегація з CASE є портативною технікою зведення — вивчіть її перед синтаксисом PIVOT, специфічним для конкретної СУБД
- EXPLAIN ANALYZE розкриває фактичний план виконання; зосередьтесь на Seq Scan, точності оцінки рядків та стратегії з'єднання
- Порядок колонок складеного індексу повинен відповідати патернам фільтрації запитів — діє правило лівого префіксу
- Часткові та покриваючі індекси зменшують I/O для цільових патернів запитів без накладних витрат повнотабличних індексів
- Уникайте функцій на індексованих колонках, NOT IN з NULL та SELECT * у production-запитах
- Практикуйте поєднання віконних функцій з оптимізацією підзапитів для обробки багатокрокових аналітичних задач
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

SQL для аналітиків даних: віконні функції, CTE та розширені запити
Повний посібник з віконних функцій SQL (ROW_NUMBER, RANK, LAG/LEAD), Common Table Expressions та розширених технік запитів, необхідних для співбесід аналітиків даних та повсякденної роботи.

Топ-25 запитань на співбесіді з Data Analytics у 2026 році
Повний посібник з 25 найпоширеніших запитань на співбесідах для data analyst у 2026 році. SQL-запити, Python Pandas, статистика, Power BI та поведінкові запитання з модельними відповідями та кодом.

Pandas 3.0 у 2026: Нові API, Критичні Зміни та Питання для Співбесіди
Pandas 3.0 впроваджує Copy-on-Write, PyArrow strings та pd.col(). Аналіз breaking changes, шаблонів міграції та питань для співбесід з аналітики даних.