feat(db): миграции групп, курсов, статусов и профиля

This commit is contained in:
2026-10-26 15:47:11 +03:00
parent cde04487e8
commit de9535d35c
14 changed files with 460 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
-- Preflight (до миграций) для перехода на группы-когорты, курсы и перенос лаб/материалов.
-- Только чтение. Запускать на копии рабочей БД до применения миграций.
-- Пример:
-- psql -h localhost -U lms -d lms -f deploy/preflight/groups-courses.sql
\pset pager off
\echo '=== 1. Группы, не подходящие под шаблон ^(.+)-([0-9])([0-9]+)(БО|МО)$ ==='
SELECT id, name
FROM identity.groups
WHERE name !~ '^(.+)-([0-9])([0-9]+)(БО|МО)$'
ORDER BY name;
\echo '=== 2. Разобранные группы с одинаковым кортежем (stream, degree, number, course, enrollment_year) ==='
WITH params AS (
SELECT CASE
WHEN EXTRACT(MONTH FROM now()) >= 9 THEN EXTRACT(YEAR FROM now())
ELSE EXTRACT(YEAR FROM now()) - 1
END::int AS academic_year_start
),
parsed AS (
SELECT
g.name,
m[1] AS stream,
m[2]::int AS course,
m[3]::int AS number,
m[4] AS degree,
(SELECT academic_year_start FROM params) - (m[2]::int - 1) AS enrollment_year
FROM identity.groups g
CROSS JOIN LATERAL (
SELECT regexp_match(g.name, '^(.+)-([0-9])([0-9]+)(БО|МО)$') AS m
) parsed_name
WHERE m IS NOT NULL
)
SELECT stream, degree, number, course, enrollment_year,
count(*) AS groups,
array_agg(name ORDER BY name) AS names
FROM parsed
GROUP BY stream, degree, number, course, enrollment_year
HAVING count(*) > 1
ORDER BY stream, degree, course, number;
\echo '=== 3. Группы, которые не разберутся шаблоном (потребуют ручного разбора) ==='
WITH parsed AS (
SELECT
g.id,
g.name,
m[1] AS stream,
m[2]::int AS course,
m[3]::int AS number,
m[4] AS degree
FROM identity.groups g
CROSS JOIN LATERAL (
SELECT regexp_match(g.name, '^(.+)-([0-9])([0-9]+)(БО|МО)$') AS m
) parsed_name
)
SELECT id, name, stream, course, number, degree
FROM parsed
WHERE stream IS NULL
OR course IS NULL
OR number IS NULL
OR degree IS NULL
ORDER BY name;
\echo '=== 4. Одинаковые имена групп (после снятия UNIQUE станут допустимы) ==='
SELECT name, count(*) AS groups
FROM identity.groups
GROUP BY name
HAVING count(*) > 1
ORDER BY name;
\echo '=== 5. Студенты без текущей группы ==='
SELECT s.student_no, a.login, s.last_name, s.first_name
FROM identity.students s
JOIN identity.accounts a ON a.id = s.account_id
LEFT JOIN identity.enrollments e ON e.student_id = s.account_id
WHERE e.id IS NULL
ORDER BY s.student_no;
\echo '=== 6. Аккаунты по ролям (до появления статусов) ==='
SELECT role, count(*) AS accounts
FROM identity.accounts
GROUP BY role
ORDER BY role;
\echo '=== 7. Дубли номеров лабораторных (все лабы переезжают в один курс C) ==='
SELECT number,
count(*) AS labs,
array_agg(id ORDER BY created_at) AS ids,
array_agg(title ORDER BY created_at) AS titles
FROM academic.labs
GROUP BY number
HAVING count(*) > 1
ORDER BY number;
\echo '=== 8. Лабораторные по семестрам ==='
SELECT semester_id, number, count(*) AS labs
FROM academic.labs
GROUP BY semester_id, number
HAVING count(*) > 1
ORDER BY semester_id, number;
\echo '=== 9. Дубли slug глав (до перехода на частичные индексы) ==='
SELECT slug, count(*) AS chapters
FROM academic.chapters
GROUP BY slug
HAVING count(*) > 1
ORDER BY slug;
\echo '=== 10. Объём материалов ==='
SELECT
(SELECT count(*) FROM academic.chapters) AS chapters,
(SELECT count(*) FROM academic.sections) AS sections,
(SELECT count(*) FROM academic.articles) AS articles,
(SELECT count(*) FROM academic.article_assets) AS article_assets;
\echo '=== 11. Активы без статьи ==='
SELECT count(*) AS assets_without_article
FROM academic.article_assets
WHERE article_id IS NULL;
\echo '=== 12. Семестры ==='
SELECT id, title, year, active
FROM academic.semesters
ORDER BY year, title;