Загрузка данных


Ниже представлено полное решение практического задания «Проектирование и анализ БД IT-компании».
Скрипт написан в соответствии со стандартами SQLite / PostgreSQL (судя по типам данных INTEGER PRIMARY KEY, TEXT, NUMERIC и флагу is_remote типа INTEGER).
Этап 1: DDL – Проектирование схемы базы данных
Создание таблиц departments, employees и tasks со всеми ограничениями (PRIMARY KEY, FOREIGN KEY, NOT NULL, UNIQUE, CHECK, DEFAULT):
-- Включение поддержки внешних ключей (актуально для SQLite)
PRAGMA foreign_keys = ON;

-- 1. Таблица departments (Отделы)
CREATE TABLE departments (
    id INTEGER PRIMARY KEY,
    name TEXT NOT NULL UNIQUE,
    budget NUMERIC(12, 2) DEFAULT 0.00,
    location TEXT
);

-- 2. Таблица employees (Сотрудники)
CREATE TABLE employees (
    id INTEGER PRIMARY KEY,
    first_name TEXT NOT NULL,
    last_name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    hire_date DATE DEFAULT CURRENT_DATE,
    salary NUMERIC(10, 2) CHECK (salary > 0),
    department_id INTEGER,
    is_remote INTEGER DEFAULT 0 CHECK (is_remote IN (0, 1)),
    tech_stack TEXT,
    FOREIGN KEY (department_id) REFERENCES departments(id)
);

-- 3. Таблица tasks (Задачи)
CREATE TABLE tasks (
    id INTEGER PRIMARY KEY,
    title TEXT NOT NULL,
    employee_id INTEGER,
    status TEXT CHECK (status IN ('new', 'in_progress', 'done')),
    priority INTEGER CHECK (priority >= 1 AND priority <= 5),
    deadline DATE,
    FOREIGN KEY (employee_id) REFERENCES employees(id)
);

Этап 2: DML – Наполнение и модификация данных
1. Множественная вставка (INSERT)
Наполнение таблиц тестовыми данными (с валидным JSON в tech_stack, а также прошедшими и будущими датами в deadline):
-- Вставка 3 отделов
INSERT INTO departments (id, name, budget, location) VALUES
(1, 'Backend', 5000000.00, 'Москва'),
(2, 'Data Science', 7500000.00, 'Новосибирск'),
(3, 'Frontend', 4000000.00, 'Санкт-Петербург');

-- Вставка 6 сотрудников (с JSON в поле tech_stack)
INSERT INTO employees (id, first_name, last_name, email, hire_date, salary, department_id, is_remote, tech_stack) VALUES
(1, 'Иван', 'Иванов', 'ivanov@itcompany.ru', '2024-02-15', 180000.00, 1, 0, '{"Python": "senior", "Docker": "middle", "SQL": "senior"}'),
(2, 'Анна', 'Смирнова', 'smirnova@itcompany.ru', '2024-05-10', 220000.00, 2, 1, '{"Python": "expert", "ML": "senior", "PyTorch": "senior"}'),
(3, 'Петр', 'Кузнецов', 'kuznetsov@itcompany.ru', '2025-01-20', 140000.00, 2, 1, '{"Python": "middle", "SQL": "middle", "Pandas": "senior"}'),
(4, 'Мария', 'Попова', 'popova@itcompany.ru', '2023-11-01', 160000.00, 3, 0, '{"JavaScript": "senior", "React": "senior", "TypeScript": "middle"}'),
(5, 'Алексей', 'Соколов', 'sokolov@itcompany.ru', '2025-03-12', 195000.00, 1, 1, '{"Go": "senior", "Python": "senior", "Kubernetes": "middle"}'),
(6, 'Елена', 'Новикова', 'novikova@itcompany.ru', '2025-08-01', 120000.00, 3, 0, '{"JavaScript": "middle", "Vue": "middle", "CSS": "senior"}');

-- Вставка 9 задач (часть задач с дедлайном в прошлом, у сотрудника №6 задач нет для проверки LEFT JOIN)
INSERT INTO tasks (id, title, employee_id, status, priority, deadline) VALUES
(1, 'Разработка REST API авторизации', 1, 'in_progress', 5, '2026-11-15'),
(2, 'Оптимизация SQL-запросов отчетов', 1, 'new', 3, '2026-01-10'),        -- просрочена
(3, 'Обучение модели рекомендаций', 2, 'in_progress', 5, '2026-12-01'),
(4, 'Очистка датасета логов', 2, 'done', 2, '2026-02-01'),                 -- в прошлом, но уже done
(5, 'Построение дашборда метрик', 3, 'new', 4, '2026-03-01'),              -- просрочена
(6, 'Настройка CI/CD пайплайна', 5, 'new', 1, '2026-11-20'),               -- статус 'new', приоритет 1 (под удаление)
(7, 'Верстка главной страницы', 4, 'done', 3, '2026-02-15'),
(8, 'Миграция на микросервисы', 5, 'in_progress', 5, '2026-11-30'),
(9, 'Обновление документации API', 3, 'new', 1, '2026-12-10');             -- статус 'new', приоритет 1 (под удаление)

2. Обновление данных (UPDATE)
Повышение зарплаты на 15% отделу 'Data Science' и перевод просроченных незавершенных задач в 'in_progress':
-- Повышение зарплаты на 15% сотрудникам отдела 'Data Science' через подзапрос
UPDATE employees
SET salary = salary * 1.15
WHERE department_id = (
    SELECT id FROM departments WHERE name = 'Data Science'
);

-- Изменение статуса просроченных задач на 'in_progress', если они еще не 'done'
UPDATE tasks
SET status = 'in_progress'
WHERE deadline < CURRENT_DATE 
  AND status != 'done';

3. Удаление данных (DELETE)
Удаление задач со статусом 'new' и приоритетом 1:
DELETE FROM tasks
WHERE status = 'new' 
  AND priority = 1;

Этап 3: DQL – Аналитика и сложные выборки
1. Детальный список (LEFT JOIN)
Вывод сотрудников, их отделов и задач (включая сотрудников без задач) с сортировкой по имени:
SELECT 
    e.first_name,
    e.last_name,
    d.name AS department_name,
    t.title AS task_title,
    t.deadline
FROM employees e
LEFT JOIN departments d ON e.department_id = d.id
LEFT JOIN tasks t ON e.id = t.employee_id
ORDER BY e.first_name ASC;

2. Агрегация по отделам (GROUP BY)
Количество сотрудников, средняя и максимальная зарплата по каждому отделу:
SELECT 
    d.name AS department_name,
    COUNT(e.id) AS employee_count,
    ROUND(AVG(e.salary), 2) AS avg_salary,
    MAX(e.salary) AS max_salary
FROM departments d
LEFT JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name;

3. Фильтрация групп (HAVING)
Отделы со средней зарплатой выше 100 000 и числом сотрудников больше 1:
SELECT 
    d.name AS department_name,
    COUNT(e.id) AS employee_count,
    ROUND(AVG(e.salary), 2) AS avg_salary,
    MAX(e.salary) AS max_salary
FROM departments d
JOIN employees e ON d.id = e.department_id
GROUP BY d.id, d.name
HAVING AVG(e.salary) > 100000 
   AND COUNT(e.id) > 1;

4. Анализ задач
Количество задач в разрезе статусов и приоритетов по убыванию количества:
SELECT 
    status,
    priority,
    COUNT(id) AS task_count
FROM tasks
GROUP BY status, priority
ORDER BY task_count DESC;

5. Работа со специальными типами (JSON в tech_stack)
Поиск сотрудников, владеющих Python на уровне 'senior' или 'expert':
-- Вариант 1: Для SQLite (функция json_extract)
SELECT first_name, last_name
FROM employees
WHERE json_extract(tech_stack, '$.Python') IN ('senior', 'expert');

-- Вариант 2: Для PostgreSQL (приведение к jsonb и оператор ->>)
-- SELECT first_name, last_name
-- FROM employees
-- WHERE (tech_stack::jsonb ->> 'Python') IN ('senior', 'expert');

6. Лимит и смещение (LIMIT и OFFSET)
Топ-3 самых высокооплачиваемых сотрудников со 2-го по 4-е место (пропуск первого):
SELECT first_name, last_name, salary
FROM employees
ORDER BY salary DESC
LIMIT 3 OFFSET 1;

Этап 4: DDL – Эволюция схемы и теоретический блиц
Задание А: Модификация структуры (ALTER)
Добавление столбца phone, переименование is_remote в work_from_home и проверка:
-- 1. Добавление нового столбца phone типа TEXT
ALTER TABLE employees ADD COLUMN phone TEXT;

-- 2. Переименование столбца is_remote в work_from_home
ALTER TABLE employees RENAME COLUMN is_remote TO work_from_home;

-- 3. Проверка изменений
SELECT * FROM employees LIMIT 1;

Задание Б: Теоретический блиц
Ответы оформлены в виде SQL-комментариев --, как требуется в задании:
-- Вопрос 1: В чем ключевая разница между DELETE FROM table и TRUNCATE TABLE table 
-- с точки зрения скорости и сброса автоинкремента?
--
-- Ответ:
-- 1. Скорость: DELETE — это DML-операция, которая удаляет строки по одной, 
--    записывает каждую удаленную строку в лог транзакций и вызывает триггеры ON DELETE, 
--    поэтому работает медленно на больших таблицах. TRUNCATE — это DDL-операция, 
--    которая быстро освобождает страницы данных целиком (без построчного сканирования), 
--    поэтому выполняется практически мгновенно.
-- 2. Автоинкремент: DELETE сохраняет текущее значение счетчика автоинкремента 
--    (новые записи продолжат нумерацию со старого значения). TRUNCATE (в большинстве СУБД 
--    по умолчанию или с опцией RESTART IDENTITY) сбрасывает счетчик автоинкремента к начальному значению (обычно 1).


-- Вопрос 2: Почему в запросе нельзя использовать псевдоним столбца, 
-- заданный в SELECT, внутри условия WHERE?
--
-- Ответ:
-- Из-за логического порядка выполнения (Order of Execution) инструкций в SQL-запросе. 
-- СУБД выполняет блок WHERE задолго до блока SELECT:
-- 1. FROM / JOIN (сборка источников данных)
-- 2. WHERE (первичная фильтрация строк)
-- 3. GROUP BY (группировка)
-- 4. HAVING (фильтрация групп)
-- 5. SELECT (вычисление выражений и назначение псевдонимов AS)
-- 6. ORDER BY (сортировка — здесь псевдонимы уже доступны)
-- 7. LIMIT / OFFSET
-- На этапе выполнения WHERE псевдонимы из SELECT еще просто не существуют.