SQL-запросы для HR-аналитика
HR-аналитика в двух словах
HR-аналитика — применение данных для решений о персонале. В крупной компании с тысячами сотрудников без данных управлять становится невозможно: непонятно, откуда берутся дорогие найм, почему уходят лучшие, работает ли программа обучения.
Аналитик в HR обычно работает с таблицами сотрудников, вакансий, собеседований, зарплат, performance review, обучения. SQL — главный инструмент, потому что данных обычно меньше, чем в продуктовой аналитике, и хватает обычной БД.
Ниже — типовые запросы для HR, которые можно адаптировать под свои таблицы.
Headcount по подразделениям
Базовый отчёт — сколько сотрудников в каждом отделе на текущий момент.
SELECT
d.department_name,
COUNT(*) AS headcount,
COUNT(*) FILTER (WHERE e.seniority = 'senior') AS senior_count,
COUNT(*) FILTER (WHERE e.seniority = 'middle') AS middle_count,
COUNT(*) FILTER (WHERE e.seniority = 'junior') AS junior_count
FROM employees e
JOIN departments d USING (department_id)
WHERE e.left_at IS NULL
GROUP BY d.department_name
ORDER BY headcount DESC;Это даёт картину распределения команды. Если в одном отделе 80% — senior, а в другом 80% — junior, это сигнал о дисбалансе.
Динамика headcount по месяцам
Показывает рост команды во времени. Нужен recursive CTE, потому что мы хотим знать headcount на каждый месяц, даже если в нём не было изменений.
WITH RECURSIVE months AS (
SELECT DATE '2025-01-01' AS month
UNION ALL
SELECT month + INTERVAL '1 month' FROM months
WHERE month < CURRENT_DATE
)
SELECT
m.month,
COUNT(*) FILTER (
WHERE e.hired_at <= m.month
AND (e.left_at IS NULL OR e.left_at > m.month)
) AS active_headcount
FROM months m
CROSS JOIN employees e
GROUP BY m.month
ORDER BY m.month;Главное здесь — учитывать тех, кто уже ушёл, но на дату месяца был ещё в компании.
Если хочется сразу закрепить тему на практике — открой тренажёр в Telegram. 10 минут в день — и синтаксис в пальцах.
Retention сотрудников
Сколько сотрудников остаётся через год, два, три года после найма. Аналог когортного анализа из продуктовой аналитики, но по сотрудникам.
WITH cohorts AS (
SELECT
DATE_TRUNC('month', hired_at)::DATE AS hire_month,
employee_id,
hired_at,
left_at
FROM employees
WHERE hired_at < CURRENT_DATE - INTERVAL '1 year'
)
SELECT
hire_month,
COUNT(*) AS cohort_size,
COUNT(*) FILTER (
WHERE left_at IS NULL
OR left_at >= hired_at + INTERVAL '1 year'
) * 100.0 / COUNT(*) AS retention_1y_pct,
COUNT(*) FILTER (
WHERE left_at IS NULL
OR left_at >= hired_at + INTERVAL '2 year'
) * 100.0 / COUNT(*) AS retention_2y_pct
FROM cohorts
GROUP BY hire_month
ORDER BY hire_month;Обычно видно, что в разные периоды (после реорганизации, пандемии, изменения политики работы) retention резко меняется.
Текучка (attrition)
Противоположность retention — сколько сотрудников ушло за период относительно среднего headcount.
WITH monthly AS (
SELECT
DATE_TRUNC('month', left_at)::DATE AS month,
COUNT(*) AS left_count
FROM employees
WHERE left_at IS NOT NULL
GROUP BY DATE_TRUNC('month', left_at)
),
headcount AS (
SELECT
DATE_TRUNC('month', hired_at)::DATE AS month,
COUNT(*) FILTER (WHERE left_at IS NULL) AS active
FROM employees
GROUP BY DATE_TRUNC('month', hired_at)
)
SELECT
m.month,
m.left_count,
h.active,
m.left_count * 100.0 / h.active AS attrition_pct
FROM monthly m
JOIN headcount h USING (month)
ORDER BY m.month;Нормальный attrition в IT — 10–15% в год. 20%+ — тревожный сигнал.
Зарплатная статистика по ролям
SELECT
role,
COUNT(*) AS cnt,
ROUND(AVG(salary), 0) AS avg_salary,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median,
PERCENTILE_CONT(0.75) WITHIN GROUP (ORDER BY salary) AS p75,
MIN(salary) AS min_salary,
MAX(salary) AS max_salary
FROM employees
WHERE left_at IS NULL
GROUP BY role
ORDER BY median DESC;Медиана важнее среднего: несколько топ-менеджеров могут исказить среднее. Перцентили показывают разброс — широкий диапазон означает либо несправедливость оплаты, либо сильную разницу в seniority.
Gender pay gap
SELECT
role,
gender,
COUNT(*) AS cnt,
ROUND(AVG(salary), 0) AS avg_salary,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY salary) AS median_salary
FROM employees
WHERE left_at IS NULL
GROUP BY role, gender
HAVING COUNT(*) > 5
ORDER BY role, gender;Чтобы увидеть реальный gap, нужно сравнивать одинаковые роли и seniority, а не total compensation в компании — иначе на результат влияют просто пропорции ролей.
Чтобы не только читать теорию, но и решать реальные задачи — загляните в бот Карьерника. Там по каждой теме подборка вопросов с разборами.
Конверсия собеседований
SELECT
DATE_TRUNC('month', applied_at)::DATE AS month,
COUNT(*) AS applications,
COUNT(*) FILTER (WHERE passed_screening) AS screened,
COUNT(*) FILTER (WHERE passed_technical) AS technical_passed,
COUNT(*) FILTER (WHERE got_offer) AS offers,
COUNT(*) FILTER (WHERE accepted_offer) AS hired
FROM interview_applications
GROUP BY 1
ORDER BY 1;Эта воронка показывает, где вы теряете кандидатов. Если screening высокий, а technical — низкий, значит, recruiter пропускает не тех. Если offer высокий, а acceptance — низкий, проблема с конкурентоспособностью предложения.
Время закрытия вакансии
SELECT
department,
role,
COUNT(*) AS filled_positions,
AVG(EXTRACT(DAY FROM (filled_at - opened_at))) AS avg_days_to_fill,
PERCENTILE_CONT(0.5) WITHIN GROUP (
ORDER BY EXTRACT(DAY FROM (filled_at - opened_at))
) AS median_days_to_fill
FROM vacancies
WHERE filled_at IS NOT NULL
AND opened_at >= CURRENT_DATE - INTERVAL '1 year'
GROUP BY department, role
ORDER BY median_days_to_fill DESC;Обычно senior-роли закрываются дольше: 60–90 дней vs 30–45 для middle. Если что-то отходит далеко от нормы — разбираемся, в чём проблема.
Читайте также
FAQ
HR-аналитика это отдельное направление?
Да, в крупных компаниях есть специализированные People Analytics команды. В маленьких — HR-аналитика часть общей data-команды.
Сколько данных нужно для HR-аналитики?
Обычно компания размером 200+ сотрудников уже даёт достаточно данных для анализа. Меньше — и статистика плавает.
Нужен ли продвинутый SQL?
Базовый + оконные функции достаточны. ClickHouse или Spark для HR обычно не нужны.
Зарплата HR-аналитика?
Обычно 120–250k в РФ на middle-уровень. Ниже чем продуктовый, потому что компания может обойтись без отдельной роли.