1st draft of lab2

This commit is contained in:
nik
2025-10-24 13:07:23 +03:00
parent ef72c7f9b9
commit 170350c402
13 changed files with 463 additions and 5 deletions

BIN
labs/lab2/assets/20.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

BIN
labs/lab2/assets/21.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 KiB

BIN
labs/lab2/assets/22.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 473 KiB

BIN
labs/lab2/assets/23.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

BIN
labs/lab2/assets/24.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 KiB

BIN
labs/lab2/assets/25.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 KiB

BIN
labs/lab2/assets/26.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 322 KiB

BIN
labs/lab2/assets/27.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

BIN
labs/lab2/assets/28.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 370 KiB

BIN
labs/lab2/assets/29.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
labs/lab2/assets/30.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 KiB

Binary file not shown.

View File

@@ -64,10 +64,92 @@
=== Цель работы
Изучение и практическое освоение основных конструкций языка SQL для работы с реляционными базами данных, включая:
- Формирование запросов на выборку данных с использованием оператора `SELECT`
- Применение предложений `WHERE` и `ORDER BY` для фильтрации и сортировки данных
- Использование встроенных функций SQL для обработки числовых, символьных данных и дат
- Работу с условными выражениями для формирования вычисляемых полей
- Получение навыков создания сложных запросов с применением различных операторов и функций SQL
=== Задачи, решаемые при выполнении работы
- Освоить базовые операции с таблицами:
- Изучить структуру таблиц базы данных
- Выполнить выборку данных из таблиц с использованием оператора SELECT
- Научиться задавать псевдонимы для столбцов
- Освоить сортировку данных с помощью предложения ORDER BY
- Научиться фильтровать и ограничивать выборку данных:
- Применять предложение WHERE для фильтрации строк по различным условиям
- Использовать операторы сравнения и логические операторы
- Работать с диапазонами значений и списками
- Выполнять выборку уникальных значений с помощью DISTINCT
- Освоить работу с числовыми функциями:
- Применять функции округления
- Выполнять арифметические операции над числовыми данными
- Создавать вычисляемые поля в результатах запросов
- Изучить символьные функции:
- Использовать функции конкатенации строк
- Применять функции изменения регистра
- Работать с функциями определения длины строк
- Использовать функции поиска позиции символов
- Освоить функции работы с датами:
- Выполнять операции с датами и временем
- Вычислять разницу между датами
- Извлекать компоненты дат
- Применять функции преобразования типов данных
- Научиться использовать условные выражения:
- Применять функцию COALESCE для обработки NULL-значений
- Использовать конструкцию CASE для создания условной логики в запросах
- Формировать сложные вычисляемые поля с использованием условий
- Развить навыки анализа и отладки SQL-запросов:
- Находить и исправлять синтаксические ошибки в запросах
- Оптимизировать структуру запросов для получения требуемых результатов
=== Исходные данные
Для выполнения практической работы используется учебная база данных `EmployeesDepartments`, содержащая информацию о сотрудниках компании и структуре отделов.
1. Таблица `EMPLOYEES`
Содержит информацию о сотрудниках компании со следующими полями:
- `EMPLOYEE_ID` идентификационный номер сотрудника (числовой)
- `FIRST_NAME` имя сотрудника (строковый)
- `LAST_NAME` фамилия сотрудника (строковый)
- `EMAIL` электронная почта (строковый)
- `PHONE_NUMBER` номер телефона (строковый)
- `HIRE_DATE` дата найма (дата)
- `JOB_ID` идентификатор должности (строковый)
- `SALARY` оклад сотрудника (числовой)
- `COMMISSION_PCT` процент комиссионных (числовой)
- `MANAGER_ID` идентификатор менеджера (числовой)
- `DEPARTMENT_ID` идентификатор отдела (числовой)
2. Таблица DEPARTMENTS
Содержит информацию об отделах компании со следующими полями:
- `DEPARTMENT_ID` идентификационный номер отдела (числовой)
- `DEPARTMENT_NAME` название отдела (строковый)
- `MANAGER_ID` идентификатор руководителя отдела (числовой)
- `LOCATION_ID` идентификатор местоположения (числовой)
3. Таблица JOB_GRADES
Содержит информацию о разрядах различных должностей.
=== Выполнение работы
==== Задание 1. Описание структуры таблицы, выборка данных из таблицы, задание имен столбцов, сортировка строк с помощью предложения ORDER BY
@@ -693,12 +775,25 @@ ORDER BY "LAST_NAME" ASC;
]
```sql
SELECT "EMPLOYEE_ID",
"LAST_NAME",
"SALARY",
ROUND("SALARY" * 1.095) AS "New Salary"
FROM "EmployeesDepartments"."EMPLOYEES"
ORDER BY "EMPLOYEE_ID";
```
- выбираются столбцы `EMPLOYEE_ID`, `LAST_NAME` и `SALARY` из таблицы `EMPLOYEES`;
- новый столбец `New Salary` рассчитывается как оклад, увеличенный на 9.5%: `SALARY` $dot$ 1.095;
- функция `ROUND` округляет полученное значение до целого числа;
- с помощью `ORDER BY "EMPLOYEE_ID"` строки сортируются по номеру сотрудника.
Результат выполнения запроса представлен на рисунке 20.
#align(center)[
#figure(
image("assets/"),
image("assets/20.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
@@ -715,12 +810,26 @@ ORDER BY "LAST_NAME" ASC;
]
```sql
SELECT "EMPLOYEE_ID",
"LAST_NAME",
"SALARY",
ROUND("SALARY" * 1.095) AS "New Salary",
ROUND("SALARY" * 1.095) - "SALARY" AS "Increase"
FROM "EmployeesDepartments"."EMPLOYEES"
ORDER BY "EMPLOYEE_ID";
```
- используется выражение из задания 3.1 для расчёта нового оклада, округлённого функцией `ROUND`;
- добавлен новый столбец `Increase`, который вычисляется как разница между новым и старым окладом;
- это позволяет увидеть, насколько увеличилась зарплата каждого сотрудника после повышения на 9.5%;
- строки сортируются по идентификатору сотрудника.
Результат выполнения запроса представлен на рисунке 21.
#align(center)[
#figure(
image("assets/"),
image("assets/21.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
@@ -730,27 +839,376 @@ ORDER BY "LAST_NAME" ASC;
===== 4.1 Создайте запрос для вывода всех данных из таблицы EMPLOYEES. Разделите столбцы запятыми. Назовите столбец THE_OUTPUT. Результат запроса должен быть схож с таблицей 19.
#align(center)[
#figure(
table(columns: 1)[*THE_OUTPUT*][100, Steven, King, SKING, 515.123.4567, 2002-06-17, AD_PRES, 24000.00, , , 90][101, Neena, Kochhar, NKOCHHAR, 515.123.4568, 2004-09-21, AD_VP, 17000.00, , 100, 90][102, Lex, De Haan, LDEHAAN, 515.123.4569, 2008-01-13, AD_VP, 17000.00, , 100, 90][103, Alexander, Hunold, AHUNOLD, 590.423.4567, 2005-01-03, IT_PROG, 9000.00, , 102, 60][104, Bruce, Ernst, BERNST, 590.423.4568, 2006-05-21, IT_PROG, 6000.00, , 103, 60][107, Diana, Lorentz, DLORENTZ, 590.423.5567, 2014-02-07, IT_PROG, 4200.00, , 103, 60][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
CONCAT(
"EMPLOYEE_ID", ', ',
"FIRST_NAME", ', ',
"LAST_NAME", ', ',
"EMAIL", ', ',
"PHONE_NUMBER", ', ',
"HIRE_DATE", ', ',
"JOB_ID", ', ',
"SALARY", ', ',
"COMMISSION_PCT", ', ',
"MANAGER_ID", ', ',
"DEPARTMENT_ID"
) AS "THE_OUTPUT"
FROM "EmployeesDepartments"."EMPLOYEES";
```
- каждое значение соединяется через запятую и пробел ', ';
- функция `CONCAT` объединяет текстовые фрагменты;
- все данные из таблицы выводятся в один столбец `THE_OUTPUT`.
Результат выполнения запроса представлен на рисунке 22.
#align(center)[
#figure(
image("assets/22.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 4.2 Создайте запрос для вывода фамилий служащих (первая буква каждой фамилии должна быть заглавной, а остальные - строчными) и длину каждой фамилии для тех служащих, фамилия которых начинается с символа _J, A_ или _M_. Присвойте соответствующие заголовки столбцам. Результат выполнения запроса должен соответствовать таблице 20.
#align(center)[
#figure(
table(columns: 2)[*Name*][*Length*]["Abel"][4]["Almeida Castro"][14]["Alves Rocha"][11]["Matos"][5]["Mourgos"][7],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
INITCAP("LAST_NAME") AS "Name",
LENGTH("LAST_NAME") AS "Length"
FROM "EmployeesDepartments"."EMPLOYEES"
WHERE UPPER(SUBSTR("LAST_NAME", 1, 1)) IN ('J', 'A', 'M');
```
- `INITCAP("LAST_NAME")` делает первую букву фамилии заглавной, а остальные строчными;
- `LENGTH("LAST_NAME")` вычисляет количество символов в фамилии;
- `SUBSTR("LAST_NAME", 1, 1)` извлекает первый символ фамилии;
- `UPPER(...) IN ('J','A','M')` отбирает только тех сотрудников, чья фамилия начинается на _J_, _A_ или _M_ (без учёта регистра);
- Результат выводится в два столбца:
- `Name` — фамилия с правильным регистром,
- `Length` — длина фамилии.
Результат выполнения запроса представлен на рисунке 23.
#align(center)[
#figure(
image("assets/23.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 4.3 Создайте запрос для вывода информации по каждому служащему в следующем виде: \<фамилия\> зарабатывает \<оклад\> в месяц, но желает \<утроенный оклад\>. Назовите столбец Dream Salaries. Результат запроса должен быть схож с таблицей 21.
#align(center)[
#figure(
table(columns: 1)[*Dream Salaries*][King зарабатывает 24000 в месяц, но желает 72000][Kochhar зарабатывает 17000 в месяц, но желает 51000][De Haan зарабатывает 17000 в месяц, но желает 51000][Whalen зарабатывает 4400 в месяц, но желает 13200][Higgins зарабатывает 12000 в месяц, но желает 36000][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
CONCAT(
"LAST_NAME",
' зарабатывает ',
"SALARY",
' в месяц, но желает ',
"SALARY" * 3
) AS "Dream Salaries"
FROM "EmployeesDepartments"."EMPLOYEES";
```
- `CONCAT` объединяет несколько частей строки в одно выражение;
- `LAST_NAME` фамилия сотрудника;
- `SALARY` его текущий оклад;
- `"SALARY" * 3` утроенный оклад;
- все фрагменты объединяются в предложение.
Результат выполнения скрипта представлен на рисунке 24.
#align(center)[
#figure(
image("assets/24.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
==== Задание 5. Составление запросов, требующих функций для работы с датами и функции преобразования типов.
===== 5.1 Напишите запрос для вывода текущей даты. Назовите столбец Date. Результат выполнения запроса должен быть подобен результату, представленному на таблице 22.
#align(center)[
#figure(
table(columns: 1)[*DATE*][2024-10-20],
supplement: [Табл.],
caption: [Результат выполнения запроса текущей даты.]
)
]
```sql
SELECT CURRENT_DATE AS "Date";
```
- `CURRENT_DATE` стандартная функция, возвращающая текущую системную дату;
- `AS "Date"` задаёт имя столбца.
Резултат выполнения запроса представлен на рисунке 25.
#align(center)[
#figure(
image("assets/25.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 5.2 Создайте запрос для вывода фамилии и даты найма всех служащих, нанятых в 2011 г. Результат выполнения запроса должен соответствовать таблице 23.
#align(center)[
#figure(
table(columns: 2)[*LAST_NAME*][*HIRE_DATE*][Alves Rocha][2011-02-06][Hartstein][2011-02-17][Abel][2011-05-11][Hernandez][2011-06-13],
supplement: [Табл.],
caption: [Результат выполнения запроса для служащих, нанятых в 2011г.]
)
]
```sql
SELECT "LAST_NAME", "HIRE_DATE"
FROM "EmployeesDepartments"."EMPLOYEES"
WHERE EXTRACT(YEAR FROM "HIRE_DATE") = 2011;
```
- `EXTRACT(YEAR FROM "HIRE_DATE")` извлекает год из даты найма;
- `= 2011` выбирает только тех сотрудников, у которых год найма равен 2011.
Результат выполнения запроса представлен на рисунке 26.
#align(center)[
#figure(
image("assets/26.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 5.3 Создайте запрос, который позволяет для каждого служащего вывести фамилию, дату найма и вычислят количество месяцев со дня найма до настоящего времени. Назовите столбец MONTH_WORKED. Результаты отсортируйте по количеству отработанных месяцев. Результат выполнения запроса должен быть схож с таблицей 24.
#align(center)[
#figure(
table(columns: 3)[*LAST_NAME*][*HIRE_DATE*][*MONTH_WORKED*][Safwah][1997-01-06][345][King][2002-06-17][280][Whalen][2002-09-17][277][Kochhar][2004-09-21][252][Steiner][2004-11-02][251][Hunold][2005-01-03][249][Ernst][2006-05-21][232][Reinhard][2007-07-25][218][$dots$][$dots$][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
"LAST_NAME",
"HIRE_DATE",
(EXTRACT(YEAR FROM AGE(CURRENT_DATE, "HIRE_DATE")) * 12
+ EXTRACT(MONTH FROM AGE(CURRENT_DATE, "HIRE_DATE"))) AS "MONTH_WORKED"
FROM "EmployeesDepartments"."EMPLOYEES"
ORDER BY "MONTH_WORKED" DESC;
```
- `AGE(CURRENT_DATE, "HIRE_DATE")` — возвращает интервал между двумя датами;
- `EXTRACT(YEAR FROM AGE(...)) * 12 + EXTRACT(MONTH FROM AGE(...))` переводит годы и месяцы в общее количество месяцев;
- `ORDER BY "MONTH_WORKED" DESC` сортирует по количеству месяцев в порядке убывания.
Результат выполнения запроса представлен на рисунке 27.
#align(center)[
#figure(
image("assets/27.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 5.4 Создайте запрос, который позволяет для каждого служащего вывести фамилию, дату найма и день недели, когда он был нанят на работу. Назовите последний столбец DAY. Отсортируйте результаты по датам. Результат выполнения запроса должен быть схож с таблицей 25.
#align(center)[
#figure(
table(columns: 3)[*LAST_NAME*][*HIRE_DATE*][*DAY*][Stocks][2015-12-16][WEDNESDAY][Newton][2015-12-16][WEDNESDAY][Heiden][2015-07-06][MONDAY][Ricci][2015-05-17][SUNDAY][Zlotkey][2015-01-29][THURSDAY][Mourgos][2014-11-16][SUNDAY][Grant][2014-05-24][SATURDAY][Bell][2014-04-01][TUESDAY][Lorentz][2014-02-07][FRIDAY][$dots$][$dots$][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
"LAST_NAME",
"HIRE_DATE",
TO_CHAR("HIRE_DATE", 'FMDay') AS "DAY"
FROM "EmployeesDepartments"."EMPLOYEES"
ORDER BY "HIRE_DATE";
```
- `TO_CHAR("HIRE_DATE", 'FMDay')` преобразует дату в название дня недели;
- `FM` убирает лишние пробелы в начале и конце;
- возвращается день недели полностью;
- столбец переименован в `DAY`;
- `ORDER BY "HIRE_DATE"` сортирует результаты по дате найма, от ранних к поздним.
Результат выполнения запроса представлен на рисунке 28.
#align(center)[
#figure(
image("assets/28.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
==== Задание 6. Составление запросов, требующих применения условных выражений.
===== 6.1 Создайте запрос, который позволяет для каждого служащего вывести фамилию и сумму комиссионных. Если служащий не зарабатывает комиссионных, укажите в столбце "No Commission". Назовите столбец COMM. Используются ф-ции преобразования типов и условное выражение COALESCE. Результат выполнения запроса должен быть схож с таблицей 25.
===== 6.2 Создайте запрос, который позволяет для каждого служащего вывести должность и её разряда (grade), используя условное выражение CASE. Разряд каждого типа должности JOB_ID приведён на рисунке 6.2, а результат выполнения запроса должен быть схож с таблицей 26.
#align(center)[
#figure(
table(columns: 2)[*LAST_NAME*][*COMM*][King][No Commission][Kochhar][No Commission][De Haan][No Commission][Hunold][No Commission][Ernst][No Commission][Lorentz][No Commission][Mourgos][No Commission][Rajs][No Commission][Davies][No Commission][Matos][No Commission][Vargas][No Commission][Zlotkey][0.20][Abel][0.30][$dots$][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
"LAST_NAME",
COALESCE(TO_CHAR("COMMISSION_PCT", 'FM0.00'), 'No Commission') AS "COMM"
FROM "EmployeesDepartments"."EMPLOYEES";
```
- `TO_CHAR("COMMISSION_PCT", 'FM0.00')` преобразует числовое значение комиссионных в текстовый формат с двумя десятичными знаками;
- `COALESCE(..., 'No Commission')` если значение комиссионных равно NULL, выводится строка 'No Commission';
- столбец переименован в `COMM`;
- вывод показывает фамилию сотрудника и либо его комиссионные, либо надпись 'No Commission'.
Результат выполнения запроса представлен на рисунке 29.
#align(center)[
#figure(
image("assets/29.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
===== 6.2 Создайте запрос, который позволяет для каждого служащего вывести должность и её разряда (grade), используя условное выражение CASE. Разряд каждого типа должности JOB_ID приведён в таблице 26, а результат выполнения запроса должен быть схож с таблицей 27.
#align(center)[
#figure(
table(columns: 2)[*Должность*][*Разряд*][AD_PRES][E][ST_MAN][D][IT_PROG][C][SA_REP][B][ST_CLERK][A][Другая][0],
supplement: [Табл.],
caption: [Соответствие каждого типа должности и разряда.]
)
]
#align(center)[
#figure(
table(columns: 3)[*LAST_NAME*][*JOB_ID*][*GRADE*][King][AD_PRES][E][Kochhar][AD_VP][0][De Haan][AD_VP][0][Whalen][AD_ASST][0][Higgins][AC_MGR][0][Gietz][AC_ACCOUNT][0][Zlotkey][SA_MAN][0][Abel][SA_REP][B][Taylor][SA_REP][B][Grant][SA_REP][B][Mourgos][ST_MAN][D][Rajs][ST_CLERK][A][King][AD_PRES][E][Kochhar][AD_VP][0][De Haan][AD_VP][0][Whalen][AD_ASST][0][$dots$][$dots$][$dots$],
supplement: [Табл.],
caption: [Результат выполнения запроса.]
)
]
```sql
SELECT
"JOB_ID" AS "Должность",
CASE "JOB_ID"
WHEN 'AD_PRES' THEN 'E'
WHEN 'ST_MAN' THEN 'D'
WHEN 'IT_PROG' THEN 'C'
WHEN 'SA_REP' THEN 'B'
WHEN 'ST_CLERK' THEN 'A'
ELSE '0'
END AS "GRADE"
FROM "EmployeesDepartments"."EMPLOYEES";
```
- `CASE ... WHEN ... THEN ... ELSE ... END` позволяет для каждого типа должности присвоить соответствующий разряд;
- должности, не указанные в таблице 26, получают разряд '0' через `ELSE '0'`;
- cтолбцы переименованы в `Должность` и `GRADE`;
Результат выполнения запроса представлен на рисунке 30.
#align(center)[
#figure(
image("assets/30.png"),
supplement: [Рис.],
caption: [Результат выполнения запроса.]
)
]
=== Выводы и анализ результатов работы
В ходе выполнения работы были успешно освоены основные конструкции языка SQL для работы с реляционными базами данных. Все поставленные задачи выполнены, запросы составлены корректно и возвращают ожидаемые результаты.
- Научился формировать запросы на выборку данных с использованием оператора `SELECT`
- Освоил использование предложений `WHERE` для фильтрации данных по различным условиям
- Изучил применение `ORDER BY` для сортировки результатов запросов
- Научился использовать `DISTINCT` для выборки уникальных значений
- Освоил создание псевдонимов для столбцов с помощью `AS`
- Применил операторы сравнения
- Использовал логические операторы
- Освоил работу с операторами `BETWEEN`, `IN`, `NOT IN` для проверки диапазонов и списков значений
- Научился работать с `NULL` значениями через `IS NULL` и `IS NOT NULL`
- Применил оператор `LIKE` для поиска по шаблону в текстовых данных
- Освоил функции округления `ROUND()` для математических вычислений
- Научился выполнять арифметические операции в запросах (сложение, вычитание, умножение, деление)
- Создавал вычисляемые поля для расчёта новых значений на основе существующих данных
- Применил функцию `CONCAT()` для объединения нескольких строковых значений
- Использовал `INITCAP()` для форматирования текста с заглавной первой буквой
- Освоил `LENGTH()` для определения длины строк
- Применил `SUBSTR()` для извлечения подстрок
- Научился использовать оператор конкатенации `||` в PostgreSQL
- Освоил функцию `CURRENT_DATE` для получения текущей системной даты
- Применил `EXTRACT()` для извлечения компонентов даты
- Использовал функцию `AGE()` для вычисления разницы между датами
- Научился форматировать даты с помощью `TO_CHAR()` для вывода дней недели
- Выполнил сложные вычисления с датами, такие как расчёт количества отработанных месяцев
- Освоил функцию `COALESCE()` для обработки `NULL` значений и замены их на заданные значения
- Применил конструкцию `CASE ... WHEN ... THEN ... ELSE ... END` для создания условной логики непосредственно в запросах
- Научился создавать сложные вычисляемые поля с использованием множественных условий
- Развил навыки поиска и исправления синтаксических ошибок в SQL-запросах
- Научился анализировать структуру таблиц через `information_schema.columns`
- Понял важность правильного использования кавычек для регистрозависимых имён в PostgreSQL
- Освоил методику пошаговой разработки сложных запросов
В процессе выполнения работы возникли следующие сложности:
- В PostgreSQL при создании объектов с кавычками имена становятся регистрозависимыми. Решение: последовательное использование двойных кавычек для всех идентификаторов таблиц и столбцов.
- Особенности сравнения и обработки NULL потребовали использования специальных конструкций. Решение: изучение документации и применение соответствующих функций.
- Получение названий дней недели на английском языке потребовало использования функции `TO_CHAR()` с правильным форматом. Решение: применение модификатора `FM` для удаления лишних пробелов.
- Расчёт количества месяцев между датами потребовал комбинирования функций `AGE()` и `EXTRACT()`. Решение: декомпозиция задачи на вычисление отдельно лет и месяцев с последующим их объединением.
В результате выполнения практической работы была достигнута поставленная цель: освоены основные конструкции SQL и получены практические навыки составления запросов различной сложности для работы с реляционными базами данных.