Kaynağa Gözat

импорт

Евгений Колесников 11 ay önce
ebeveyn
işleme
c8ad5bbd76

+ 1 - 0
.gitignore

@@ -1,3 +1,4 @@
 *.bak
 *.mwb
 .~*
+*.csv

+ 257 - 0
data/module2.md

@@ -0,0 +1,257 @@
+# Модуль № 2: Соадминистрирование баз данных и серверов
+
+## Текст задания
+
+**Создайте базу данных на основании разработанной ER-диаграммы**, используя предпочтительную платформу, на сервере баз данных, который вам предоставлен. Создайте таблицы основных сущностей, атрибуты, отношения и необходимые ограничения. После создания базы данных требуется **импортировать данные** из файла `Номерной фонд.xlsx`.
+
+**Создайте запрос вычисляющий процент загрузки номерного фонда** – это отношение количества проданных ночей к общему количеству номеров в отеле.
+
+Необходимые приложения: `Текст брифинга.pdf`, `Документы заказчика.zip` (в этом репозитории уже распаковано в одноименный каталог)
+
+## Реализация
+
+### Создание базы данных и таблиц
+
+**MySQL Workbench** позволяет создавать DDL скрипт из ER-диаграммы.
+
+Можно делать экспорт сразу в базу данных, но лично мне **MySQL Workbench** как менеджер БД не нравится, поэтому рассмотрю вариант с генерацией DDL скрипта без записи в базу
+
+1. Запускаем процесс экспорта: _File -> Export -> Forward Engineer SQL CREATE Script..._
+
+    ![](../solution/export01.png)
+
+1. Задайте параметры экспорта
+
+    ![](../solution/export02.png)
+
+    - Название файла можно не вводить, сохранять мы его не будем
+    - _Generate DROP Statements Before Each CREATE Statement_ - удалять сущности перед созданием. При первой итерации не надо, но если что-то придётся менять, то надо включить
+    - _Generate DROP SHEMA_ - удалять базу данных. На практике можно, а на демо экзамене нет - там у вас не будет прав на удаление базы
+    - _Omit Shema Qualifier in Object Names_ - убрать название базы данных из запросов. По-умолчанию скрипт генерируется с названиями БД. Можно этот параметр не устанавливать, но в этом случае нужно задать название модели как у вашей БД (на экзамене `userNN`, на практике что-то типа `ekolesnikov_de2025`)
+
+1. Фильтр экспорта. Тут ничего менять не нужно
+
+    ![](../solution/export03.png)
+
+1. Правим скрипт
+
+    Если мы выбрали пункт _Omit Shema Qualifier in Object Names_ на первом шаге, то необходимо добавить команду выбора базы данных (предполагается, что БД уже существует)
+
+    ```sql
+    use ekolesnikov_de2025;
+    ```
+    
+    ![](../solution/export04.png)
+
+    Теперь скрипт можно скопировать и выполнить его в любом менеджере БД (мне больше нравится **DBeaver**)
+
+1. Создание базы данных из скрипта (в **DBeaver**)
+
+    ![](../solution/export05.png)
+
+    - вставляем созданный скрипт в окно SQL скрипта
+    - выполняем весь скрипт
+
+### Импорт данных
+
+>После создания базы данных требуется **импортировать данные** из файла `Номерной фонд.xlsx`.
+
+В данных никакой нормализации не требуется, поэтому можем сразу делать экспорт в CSV
+
+![](../solution/import01.png)
+
+Процесс импорта не расписываю, это вы уже должны уметь. В итоге должна получиться **таблица импорта**
+
+![](../solution/import02.png)
+
+Из **таблицы импорта** нам нужно разнести данные в таблицы **Room**, **RoomType**, **RoomFloor**, **RoomStatus**
+
+Сначала заполняем словари:
+
+1. **RoomType**
+
+    Сначала пишем запрос выбора уникальных категорий
+
+    ```sql
+    select distinct ri.`Категория` 
+    from room_import ri;
+    ```
+
+    ![](../solution/import03.png)
+
+    Затем добавляем команду вставки:
+
+    ```sql
+    insert into RoomType (title)
+    select distinct ri.`Категория` 
+    from room_import ri;
+    ```
+
+    При текущих типах данных получим ошибку - названия категорий длинее 45 символов, придётся поменять длину поля `title` (можно прямо в базе, но не забыть поправить и в ERD, чтобы выгрузить правильный PDF)
+
+    ![](../solution/import04.png)
+
+1. **RoomFloor**
+
+    Делаем аналогично
+
+    ```sql
+    insert into RoomFloor (title)
+    select distinct ri.`Этаж` 
+    from room_import ri;
+    ```
+
+1. **RoomStatus**
+
+    Исходных данных для этой таблицы нет, но мы должны добавить хотя бы один статус "Свободен" (или "Чистый"), иначе не сможем добавить номера в таблицу **Room**
+
+Теперь, имея все словари, можно заполнять таблицу **Room**
+
+Сначала пробуем выбрать данные со всеми связями. В значение **roomStatusId** пишем константу `1` (тут должен быть реально существующий `id` из таблицы **RoomStatus**)
+
+```sql
+-- insert into Room (roomTypeId, `number`, roomStatusId, roomFloorId)
+select rt.id, ri.Номер, 1, rf.id 
+from room_import ri, RoomType rt, RoomFloor rf
+where ri.Категория = rt.title 
+	AND ri.Этаж = rf.title;
+```
+
+![](../solution/import05.png)
+
+Убедившись, что всё нормально, раскомментируем **import** и выполняем вставку
+
+После импорта удалите таблицу **room_import**, т.к. она нарушает **3НФ**
+
+### Создание запроса
+
+**Создайте запрос вычисляющий процент загрузки номерного фонда** – это отношение количества проданных ночей к общему количеству номеров в отеле.
+
+Перед выполнением этого задания нужно добавить пару записей в таблицу **Booking** (а перед этим и в **User**)
+
+В информации о брони у нас есть даты заезда и выезда. Для нахождения количества дней между двумя датами в SQL есть функция **DATEDIFF**
+
+```sql
+SELECT DATEDIFF(b.dateTo, b.dateFrom)
+FROM Booking b;
+```
+
+Для суммы всех бронирований применяется аггрегирующая функция **SUM**:
+
+```sql
+SELECT SUM( DATEDIFF(b.dateTo, b.dateFrom) )
+FROM Booking b; 
+```
+
+Остаётся просто разделить на общее количество номеров:
+
+```sql
+SELECT COUNT(*) FROM Room;
+```
+
+Итоговый скрипт будет выглядеть так:
+
+```sql
+SELECT (
+    SELECT SUM( DATEDIFF(b.dateTo, b.dateFrom) )
+    FROM Booking b 
+) / (
+    SELECT COUNT(*) FROM Room 
+);
+```
+
+Для демо-экзамена этого достаточно, но желательно добавить выборку за указанный диапазон.
+
+В исходных данных два бронирования:
+
+* с `2025-01-10` по `2025-02-17`
+* с `2025-01-01` по `2025-01-09`
+
+Допустим хотим выбрать данные только за февраль.
+
+Для учёта даты использовать WHERE нельзя, нужно обрезать лишние данные в DATEDIFF:
+
+```sql
+SELECT SUM(
+	DATEDIFF(
+		LEAST(b.dateTo, '2025-02-28'), 
+		GREATEST(b.dateFrom, '2025-02-01')
+	)
+) as res
+from Booking b; 
+```
+
+где функция **LEAST** возвращает минимум из двух значений, а функция **GREATEST** возвращает максимум из двух значений (**MIN** и **MAX** являются аггрегатными функциями)
+
+Если запустить этот скрипт, то получим `-7` дней. если убрать SUM, то увидим такое:
+
+![](../solution/import06.png)
+
+Дело в том, что для январской брони у нас получился `DATEDIFF('2025-01-09', '2025-02-01')`
+
+Чтобы не учитывать отрицательные данные можно добавить ещё один **GREATEST**
+
+```sql
+SELECT SUM(
+	GREATEST( 
+        DATEDIFF(
+		    LEAST(b.dateTo, '2025-02-28'), 
+		    GREATEST(b.dateFrom, '2025-02-01')
+	    ), 
+        0
+    )
+) as res
+from Booking b; 
+```
+
+Ещё нужно учитывать количество ночей в знаменателе:
+
+```sql
+SELECT (SUM(
+	GREATEST( 
+        DATEDIFF(
+		    LEAST(b.dateTo, '2025-02-28'), 
+		    GREATEST(b.dateFrom, '2025-02-01')
+	    ), 
+        0
+    )
+)) / (
+    SELECT count(*) * DATEDIFF('2025-02-28', '2025-02-01') FROM Room
+) as res
+from Booking b; 
+```
+
+Можно завернуть это в пользовательскую функцию
+
+```sql
+DROP FUNCTION IF EXISTS PercentLoad;
+DELIMITER //
+CREATE FUNCTION PercentLoad(df DATE, dt DATE) 
+returns real
+DETERMINISTIC
+BEGIN
+    DECLARE result real DEFAULT 0;
+    SELECT (SUM(
+	    GREATEST( 
+            DATEDIFF(
+		        LEAST(b.dateTo, dt), 
+		        GREATEST(b.dateFrom, df)
+	        ), 
+            0
+        )
+    )) / (
+        SELECT count(*) * DATEDIFF(dt, df) 
+        FROM Room
+    ) 
+    INTO result
+    from Booking b;
+    return result;
+END//
+DELIMITER ; 
+```
+
+И использовать её при необходимости
+
+```sql
+select PercentLoad('2025-02-01', '2025-02-28');
+```

+ 2 - 1
readme.md

@@ -1,3 +1,4 @@
 # Разбор задания демо-экзамена 2025 (предварительная версия)
 
-1. [Модуль № 1: Проектирование и разработка информационных систем](./data/module1.md)
+1. [Модуль № 1: Проектирование и разработка информационных систем](./data/module1.md)
+1. [Модуль № 2: Соадминистрирование баз данных и серверов](./data/module2.md)

BIN
solution/export01.png


BIN
solution/export02.png


BIN
solution/export03.png


BIN
solution/export04.png


BIN
solution/export05.png


BIN
solution/import01.png


BIN
solution/import02.png


BIN
solution/import03.png


BIN
solution/import04.png


BIN
solution/import05.png


BIN
solution/import06.png