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-уровень. Ниже чем продуктовый, потому что компания может обойтись без отдельной роли.