# База данных и установка MySQL: таблицы, ключи, Docker и драйвер<no value>

## В этом уроке

Урок состоит из двух глав — в том же порядке, что и в презентации:

1. **Глава 1** — что такое база данных: таблица, строка, столбец, первичный ключ и типы данных
2. **Глава 2** — установка MySQL: Docker, Database Client и драйвер

## Глава 1: Что такое база данных — таблица, строка, столбец, первичный ключ и типы данных

### Что мы изучим?

- **Зачем** нужна база данных (Database).
- Что такое **реляционная база данных** (Relational Database).
- **Таблицу** (Table), **строку** (Row), **столбец** (Column).
- **Первичный ключ** (Primary Key).
- Базовые **типы данных** (Data Types).

{{< box "tip" "Чего мы здесь (намеренно) не изучим" >}}
Здесь мы изучаем **только основы, необходимые для CRUD**: подключение, одну таблицу и создание, чтение, обновление и удаление. Мы **не** будем изучать JOIN, GROUP BY, HAVING, подзапросы (Sub-Queries), продвинутую нормализацию, индексы или хранимые процедуры (Stored Procedures). Их вы глубоко изучите позже в треке.
{{< /box >}}

### Зачем это нужно?

В теме файлов мы сохраняли данные в файл JSON. Это работало для одной программы на нашем компьютере. На настоящем сервере это ломается:

| Проблема файла JSON | Что происходит |
|---|---|
| **Всё или ничего при чтении** | Чтобы найти одного студента из миллиона, вы загружаете **весь** файл в RAM. |
| **Всё или ничего при записи** | Чтобы изменить одно поле, вы снова записываете **весь** файл. |
| **Два пользователя одновременно** | Два запроса читают файл, каждый что-то меняет, и каждый записывает. **Изменение первого стирается.** |
| **Сбой посреди записи** | Файл остаётся наполовину записанным и испорченным. |
| **Нет правил** | Ничто не мешает двум студентам с одинаковым email или отсутствующему полю. |
| **Запросы** | «Все студенты из Хайфы старше 20» = цикл в нашем коде по всем данным. |

**База данных** решает всё это: чтение и запись **ровно** того, что нужно, работа многих пользователей одновременно, защита от сбоев, правила, которые обеспечивает **сама база**, и язык (SQL) для запросов.

### Объяснение с основ

#### Что такое база данных?

**База данных** (Database) = система **упорядоченного** хранения данных, которая позволяет эффективно и надёжно их сохранять, находить, обновлять и удалять. Программа, которая ею управляет, называется **СУБД** (DBMS, Database Management System): MySQL, PostgreSQL, SQLite...

#### Реляционная база данных — таблицы

Самый распространённый вид — **реляционная база данных**: данные организованы в **таблицы**, как лист Excel со строгими правилами.

```text
Table: students
┌────┬────────┬─────┬────────────────┐
│ id │ name   │ age │ email          │   ← columns (the structure)
├────┼────────┼─────┼────────────────┤
│  1 │ Dana   │  24 │ dana@mail.com  │   ← row = one student
│  2 │ Avi    │  31 │ avi@mail.com   │   ← row
│  3 │ Noa    │  22 │ noa@mail.com   │   ← row
└────┴────────┴─────┴────────────────┘
  ↑
  primary key
```

| Термин | Что это | В Python (то, что мы уже знаем) |
|---|---|---|
| **Таблица** (Table) | Коллекция записей одного вида | Класс (шаблон) + список всех объектов |
| **Столбец** (Column) | Одно поле с фиксированным **именем** и **типом** | Атрибут |
| **Строка** (Row, запись) | Один элемент | Один объект / один словарь |
| **Первичный ключ** (Primary Key) | Столбец, который **однозначно** идентифицирует каждую строку | `student_id` |

{{< box "analogy" "Аналогия: картотека в офисе" >}}
**База данных** = шкаф. **Таблица** = один ящик («студенты»). **Столбцы** = печатная анкета, по которой построена каждая папка (имя, возраст, email), одинаковая для всех папок. **Строка** = одна папка, одного студента. **Первичный ключ** = номер на корешке папки: он никогда не повторяется, и по нему достают папку, не перебирая все.
{{< /box >}}

#### Первичный ключ

**Первичный ключ** (Primary Key) = столбец (обычно `id`), значение которого:

1. **Уникально** — нет двух строк с одинаковым значением.
2. **Не пусто** — у каждой строки должно быть значение.
3. **Не меняется** — идентификатор — на всю жизнь строки.

**Почему не использовать имя?** Потому что есть две «Даны». **А email?** Потому что люди меняют email. Числовой идентификатор, который база выдаёт сама, — безопасный выбор. (Помните `Shop`, который выдавал идентификаторы через `max + 1`? База делает это за нас, и надёжнее.)

И это ровно `{student_id}` в пути API: `GET /students/7` = «принеси строку, первичный ключ которой 7».

#### Типы данных

У каждого столбца есть **тип**. В **MySQL** (базе, которую мы будем использовать) типов много, но нам понадобятся такие:

| Тип SQL | Что хранится | Python | Пример |
|---|---|---|---|
| **INT** | Целое число | `int` | `id`, `age`, `quantity` |
| **DECIMAL(10, 2)** | Точное десятичное число — 2 знака после точки | `Decimal` (как `float`, но точное) | `price` |
| **VARCHAR(100)** | Текст — до 100 символов | `str` | `name`, `email` |
| (настоящего BOOLEAN нет) | Хранят 0/1 в INT | `bool` | `is_active` |
| **NULL** | «Нет значения» | `None` | Пустое необязательное поле |

#### Базовые ограничения

Правила, которые обеспечивает **база данных**. Даже если в коде есть баг, база откажет:

| Ограничение (Constraint) | Значение |
|---|---|
| `PRIMARY KEY` | Уникальный идентификатор строки. |
| `AUTO_INCREMENT` | База сама выдаёт id по возрастанию. |
| `NOT NULL` | Должно быть заполнено. |
| `UNIQUE` | Нет двух строк с одинаковым значением (например, email). |

Это как проверка в `__init__` (тема ООП), только её обеспечивает **сама база** — ещё один слой защиты.

#### SQL — язык

**SQL** (Structured Query Language, произносится «сиквел» или «эс-кью-эл») = язык, на котором общаются с реляционной базой данных. **Запрос** (Query) = одна команда на SQL. Мы встретим только четыре-пять команд:

```text
CREATE TABLE   → create the structure      (day 3)
INSERT         → C - add a row             (day 3)
SELECT         → R - read rows             (day 3)
UPDATE         → U - change rows           (day 4)
DELETE         → D - remove rows           (day 4)
```

### Синтаксис

**Проект таблицы** (результат этапа проектирования, до всякого кода). Так выглядит проект таблицы `students`:

```text
Table: students
| column | type         | constraints                 |
|--------|--------------|-----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY  |
| name   | VARCHAR(100) | NOT NULL                    |
| age    | INT          | NOT NULL                    |
| email  | VARCHAR(100) | NOT NULL UNIQUE             |
```

А вот команда SQL, которая её создаёт (мы напишем и выполним её в следующих уроках; здесь — только чтобы увидеть, куда мы идём):

```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

### Первый пример кода

**От класса Python к проекту таблицы.** Класс `Product` из темы ООП:

```python
class Product:
    def __init__(self, sku, name, price, stock=0):
        ...
```

**Перевод:**

| Атрибут в Python | Столбец | Тип | Ограничения | Почему |
|---|---|---|---|---|
| — | `id` | INT | AUTO_INCREMENT PRIMARY KEY | Стабильный внутренний идентификатор |
| `sku` | `sku` | VARCHAR(20) | NOT NULL UNIQUE | Уникальный, обязательный |
| `name` | `name` | VARCHAR(100) | NOT NULL | Обязательный |
| `price` | `price` | DECIMAL(10, 2) | NOT NULL | Точное десятичное число |
| `stock` | `stock` | INT | NOT NULL DEFAULT 0 | Целое число, по умолчанию 0 |

### Разбор кода построчно

| Решение | Обоснование |
|---|---|
| Добавить `id`, хотя есть `sku` | `sku` — **бизнес**-идентификатор, который может измениться (ребрендинг). `id` — **технический** идентификатор, который никогда не меняется. Принято хранить оба. |
| `sku` с `UNIQUE` | Правило «артикул уникален» обеспечивается в базе, даже если другой код забудет проверить. |
| `price` как DECIMAL(10, 2) | Цена с агорот (копейками). `DECIMAL` хранит число **точно** — без проблем округления `float` (`0.1 + 0.2`). |
| Что **не** входит в таблицу | Методы (`take`, `restock`) — это **поведение**, и они живут в коде. База хранит только **данные**. |

### Ещё один пример

**От словарей к таблице и «грязные» типы.** Данные, которые пришли из файла JSON:

```python
students = [
    {"name": "Dana", "age": 24, "email": "dana@mail.com", "phone": "050-1234567"},
    {"name": "Avi", "age": "31", "email": "avi@mail.com"},
    {"name": "Noa", "age": 22, "email": "dana@mail.com", "phone": None},
]
```

**Вопросы проектирования:**

1. **`phone` есть только в части строк.** → столбец **без** `NOT NULL`. Когда значения нет, хранится **NULL** (`None` в Python).
2. **У Ави `age` — `"31"` (строка).** → столбец `INT`. **В коде** мы преобразуем `int(...)` перед сохранением (или это сделает Pydantic в теме API студентов).
3. **У Ноа и Даны одинаковый email.** → с `UNIQUE` на `email` **база откажется** сохранять Ноа. Именно этого мы и хотим (и обработаем эту ошибку в следующих уроках).
4. **Нет `id`.** → `AUTO_INCREMENT`: база выдаст 1, 2, 3.

```text
| column | type         | constraints                |
|--------|--------------|----------------------------|
| id     | INT          | AUTO_INCREMENT PRIMARY KEY |
| name   | VARCHAR(100) | NOT NULL                   |
| age    | INT          | NOT NULL                   |
| email  | VARCHAR(100) | NOT NULL UNIQUE            |
| phone  | VARCHAR(20)  | (nullable)                 |
```

**Почему `phone` — VARCHAR, а не INT?** Потому что `050-1234567` содержит дефис, а номер телефона, начинающийся с 0, потерял бы ноль в INT. **Правило:** «число», с которым не делают арифметики (телефон, индекс, номер удостоверения), → VARCHAR.

### Типичная ошибка

**Ошибка 1: «естественный» первичный ключ, который может измениться или повториться.** Имя, email, номер телефона. В день, когда Дана сменит email, все, кто указывал на неё по email, её «потеряют». **Числовой id** от базы.

**Ошибка 2: хранить список внутри одного столбца.**

```text
| id | name | grades     |
|  1 | Dana | "90,85,70" |
```

Нельзя посчитать среднее в SQL, нельзя найти «всех, кто получил 90», и трудно обновить одну оценку. В реляционном мире оценки — **отдельная таблица** (`grades` со `student_id`). Это вы глубоко изучите позже в треке (связи между таблицами и JOIN). Здесь мы будем работать с **одной таблицей** и простыми полями.

**Ошибка 3: отказаться от `NOT NULL` и `UNIQUE», «потому что код проверяет».** Код проверяет **сегодня**. Завтра кто-то напишет скрипт импорта, который обходит код, или появится баг. База — последняя линия обороны.

**Ошибка 4: думать, что SQL — это «программа».** SQL — **декларативный** язык: вы говорите, **что** хотите («все студенты из Хайфы»), а не **как** это найти (никаких циклов). База сама решает, как выполнить это эффективно.

### Итоги

- **База данных** решает проблемы файла JSON: точечное чтение и запись, много пользователей одновременно, устойчивость к сбоям, правила и запросы.
- **Реляционная база** = таблицы. **Таблица** ≈ класс + все объекты. **Столбец** ≈ атрибут (с типом). **Строка** ≈ один объект.
- **Первичный ключ** = уникальный идентификатор, не пустой и неизменный. Обычно `id INT AUTO_INCREMENT PRIMARY KEY`.
- Типы в MySQL: **INT**, **DECIMAL**, **VARCHAR** и **NULL** (= `None`). bool хранится как 0/1. Телефон и индекс хранятся как VARCHAR.
- Ограничения: **NOT NULL**, **UNIQUE**. База обеспечивает правила, даже когда код ошибается.
- **SQL** = декларативный язык. Здесь: CREATE TABLE, INSERT, SELECT, UPDATE, DELETE — и всё.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### Официальная документация

- [MySQL — Creating and Using a Database](https://dev.mysql.com/doc/refman/8.4/en/database-use.html)
- [MySQL — Data Types](https://dev.mysql.com/doc/refman/8.4/en/data-types.html)
- [MySQL — CREATE TABLE](https://dev.mysql.com/doc/refman/8.4/en/create-table.html)

<!-- docs:end -->

## Глава 2: Установка MySQL — Docker, Database Client и драйвер

### Что мы изучим?

- Что такое **сервер базы данных** и почему мы будем работать с **MySQL**.
- Что такое **контейнер** и как запустить MySQL в **Docker** — одной командой.
- Как видеть базу из VS Code с помощью расширения **Database Client**.
- Что такое **драйвер** (Driver) и как Python подключается к MySQL.
- Где хранить **данные для подключения** — и почему не в коде.

### Зачем это нужно?

Прежде чем написать хоть один `INSERT`, нужно ответить на «сантехнические» вопросы: где работает база? Как наш код до неё добирается? Каковы данные для подключения? В реальной работе первое, что вы получаете в новом проекте, — именно это: «вот данные для подключения к базе». Нужно знать, что с ними делать.

### Объяснение с основ

#### База данных — это сервер

**MySQL** — **сервер базы данных**: отдельная программа (процесс — вспомните тему операционной системы!), которая работает всё время и **слушает порт** — в MySQL это порт **3306**. Наш код не открывает файл — он **подключается по сети** к серверу, точно как браузер, который подключается к сайту (тема сетей: IP + порт).

| | **MySQL** (сервер базы данных) |
|---|---|
| Где | Отдельный процесс, который слушает порт **3306** |
| Как подключаться | По сети: **хост, порт, пользователь, пароль** и имя базы |
| Кто подключается | Несколько программ и серверов — **одновременно** |
| Где это в мире | Сайты и системы любого размера. PostgreSQL и SQL Server работают так же |

#### Почему Docker?

Установка MySQL напрямую на компьютер — долгий процесс: мастер установки, пароли, служба, работающая в фоне, и настройки, которые различаются между Windows и Mac. И то, что работает у одного, не всегда работает у другого.

**Docker** (который мы установили в первую неделю) решает это: он запускает готовые программы внутри **контейнера**.

- **Образ** (Image) — шаблон: «MySQL версии 8.4, готовый к работе». Его скачивают один раз.
- **Контейнер** (Container) — **работающая** копия образа. Изолирован от остального компьютера и содержит всё, что нужно программе.

{{< box "analogy" "Аналогия: контейнер в порту" >}}
Грузовой контейнер выглядит одинаково на каждом корабле и в каждом порту — неважно, что внутри. Так и контейнер Docker: тот же MySQL с теми же настройками работает абсолютно одинаково у всех студентов, в любой операционной системе.
{{< /box >}}

#### Database Client — база с интерфейсом

MySQL можно видеть и использовать без единой строки кода — с помощью расширения VS Code с именем **Database Client** (автор Weijan Chen). Оно показывает **дерево** баз и таблиц, открывает таблицу как электронную таблицу, выполняет SQL и показывает результат **в виде таблицы**. Мы будем использовать его во всех этих темах, чтобы **видеть**, что наш код сделал в базе.

#### Драйвер — переводчик

Наш код написан на Python. MySQL говорит на собственном «протоколе» (тема сетей: протокол = правила общения). Нужен **посредник**, который переводит: **драйвер** (Driver, его также называют Database Connector) = библиотека, которая умеет:

1. **Подключаться** к серверу базы данных определённого вида.
2. **Отправлять** ему команды SQL.
3. **Возвращать** результаты в виде данных Python (tuple, int, str).

Для MySQL мы будем использовать официальный драйвер: **`mysql-connector-python`**. Его устанавливают через `pip`, а в коде пишут `import mysql.connector`.

{{< box "analogy" "Аналогия: переходник для розетки за границей" >}}
У вашего устройства (кода Python) израильская вилка, а розетка в стене (база данных) — американская. **Драйвер** = переходник. Для каждого вида розетки свой переходник, но само устройство не меняется.
{{< /box >}}

#### Данные для подключения — и не в коде!

Чтобы подключиться к MySQL, нужны **пять данных**:

| Данные | У нас | Что это |
|---|---|---|
| `host` | `localhost` | Где сервер — наш компьютер |
| `port` | `3306` | Порт MySQL |
| `user` | `root` | Имя пользователя |
| `password` | `kodkod` | Пароль, который мы задали в контейнере |
| `database` | `school` | Какая база внутри сервера |

В настоящем проекте здесь **настоящий пароль**. Если он написан в коде, а код попал на GitHub, **он утёк** (вспомните секреты из темы Git!). Профессиональная практика:

- Данные для подключения хранятся в **переменных окружения** (Environment Variables) или в файле `.env`, который находится в **`.gitignore`**.
- Код **читает** их: `os.environ.get("DB_PASSWORD")`.

### Синтаксис

**Запуск MySQL в контейнере** — одна команда, один раз (в PowerShell; на Mac замените `` ` `` на `\`):

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
```

| Часть | Значение |
|---|---|
| `docker run` | Создаёт и запускает новый **контейнер** |
| `--name kodkod-mysql` | Имя контейнера, чтобы к нему обращаться |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | Переменная окружения: пароль пользователя `root` |
| `-e MYSQL_DATABASE=school` | Переменная окружения: база с именем `school`, которая создаётся автоматически |
| `-p 3306:3306` | Порт 3306 на нашем компьютере ведёт к MySQL внутри контейнера |
| `-d` | Работает в фоне (detached), терминал остаётся свободным |
| `mysql:8.4` | **Образ**: MySQL версии 8.4 |

**Полезные команды:**

```powershell
docker ps                     # which containers are running now
docker start kodkod-mysql     # start it again (after a restart of the computer)
docker stop kodkod-mysql      # stop it (the data is kept)
```

**Подключение из Python:**

```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
connection.close()
```

- `mysql.connector.connect(...)` — открывает сетевое соединение с сервером. Если сервер не запущен — ошибка.
- `port=3306` — значение по умолчанию, его можно не указывать.

### Первый пример кода

**Настройка базы для этих тем**, шаг за шагом.

**1. Запустите MySQL** (Docker Desktop должен быть открыт):

```powershell
docker run --name kodkod-mysql `
  -e MYSQL_ROOT_PASSWORD=kodkod `
  -e MYSQL_DATABASE=school `
  -p 3306:3306 -d mysql:8.4
docker ps
```

При первом запуске Docker скачивает образ (несколько минут). `docker ps` показывает, что контейнер работает:

```text
CONTAINER ID   IMAGE       STATUS         PORTS                    NAMES
ffaa661f5047   mysql:8.4   Up 10 seconds  0.0.0.0:3306->3306/tcp   kodkod-mysql
```

**2. Подключитесь из VS Code:**

1. Вкладка **Extensions** → найдите **Database Client** (автор Weijan Chen) → **Install**.
2. В боковой панели появится значок базы данных. Щёлкните по нему → **+** (Create Connection) → выберите **MySQL**.
3. Заполните: Host `127.0.0.1`, Port `3306`, Username `root`, Password `kodkod`, Database `school` → **Connect**.
4. Подключение появится в дереве, а внутри — база `school`, пока без таблиц.

**3. Проект на Python:**

```powershell
mkdir ~\dev\kodkod6\week-4\school-db
cd ~\dev\kodkod6\week-4\school-db
git init
python -m venv .venv
.venv\Scripts\Activate.ps1
pip install mysql-connector-python
```

`.gitignore`:

```text
__pycache__/
.venv/
.env
```

`setup_db.py`:

```python
import os

import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password=os.environ.get("DB_PASSWORD", "kodkod"),
    database="school",
)
print("Connected:", connection.is_connected())
print("Server version:", connection.server_info)
connection.close()
print("Connected after close:", connection.is_connected())
```

**Вывод:**

```text
Connected: True
Server version: 8.4.11
Connected after close: False
```

### Разбор кода построчно

| Код | Объяснение |
|---|---|
| `import mysql.connector` | **Драйвер**. Устанавливается через `pip install mysql-connector-python`. |
| `host="localhost"`, `port=3306` | **Где** сервер: наш компьютер, на порту, который мы открыли через `-p 3306:3306`. |
| `user="root"` | Главный пользователь MySQL. |
| `os.environ.get("DB_PASSWORD", "kodkod")` | Пароль — **из переменной окружения**, если она задана, иначе значение по умолчанию учебной среды. |
| `database="school"` | База, созданная через `-e MYSQL_DATABASE=school`. |
| `connection.is_connected()` | `True` — соединение открыто. |
| `connection.server_info` | Версия сервера MySQL. У вас номер может отличаться. |
| `connection.close()` | Закрывает соединение (в следующем уроке — почему это важно). |
| Нет `*.db` в `.gitignore` | Данные находятся **в контейнере**, а не в папке проекта. В Git попадает только **код**, который создаёт структуру. |

### Ещё один пример

#### Контейнер после перезагрузки компьютера

После перезагрузки (Restart) Docker Desktop запускается — но контейнер **не** работает. Снова выполнить `docker run`? **Нет** — имя `kodkod-mysql` уже занято:

```text
docker: Error response from daemon: Conflict. The container name "/kodkod-mysql" is already in use by container ...
```

Контейнер существует, он просто остановлен. Запустите его:

```powershell
docker start kodkod-mysql
```

**Все данные сохранились** — таблицы и строки остаются в контейнере, пока вы его не удалите (`docker rm`).

#### Когда сервер не запущен

Если Docker Desktop закрыт или контейнер остановлен, подключение из Python не удаётся:

```text
mysql.connector.errors.InterfaceError: 2003: Can't connect to MySQL server on 'localhost:3306' (Errno 10061: No connection could be made because the target machine actively refused it)
```

**`Can't connect`** = порт 3306 никто не слушает. Проверьте через `docker ps` и запустите через `docker start kodkod-mysql`.

### Типичная ошибка

**Ошибка 1: Docker Desktop не открыт.**

```text
failed to connect to the docker API ... The system cannot find the file specified.
```

Команды `docker` общаются с Docker Desktop. Откройте его, дождитесь окончания запуска и попробуйте снова.

**Ошибка 2: порт занят.**

```text
Bind for 0.0.0.0:3306 failed: port is already allocated
```

На порту 3306 уже что-то работает — обычно MySQL, когда-то установленный прямо на компьютер. Можно использовать другой порт на нашем компьютере: `-p 3307:3306`, а затем подключаться с `port=3307`.

**Ошибка 3: пароль от базы в коде, который попадает на GitHub.**

```python
connection = mysql.connector.connect(host="prod-db.company.com", user="admin", password="MyRealPassword123")
```

Даже если удалить его в следующем Commit, пароль остаётся в истории (вспомните тему Git!). **Переменные окружения / `.env` в `.gitignore`.** (Наш пароль `kodkod` — только для локальной учебной среды.)

### Итоги

- **MySQL** = сервер базы данных: процесс, который слушает порт **3306**, и к нему подключаются по сети.
- **Docker** запускает MySQL внутри **контейнера** — одной командой и одинаково у всех. `docker run` — один раз, а затем `docker start kodkod-mysql`.
- **Database Client** (расширение VS Code) = интерфейс к базе: дерево таблиц, выполнение SQL и результаты в виде таблицы.
- **Драйвер** = библиотека, которая переводит между Python и базой. Для MySQL: **`mysql-connector-python`** (`import mysql.connector`).
- Пять данных для подключения: `host`, `port`, `user`, `password`, `database`.
- Пароли не пишут в коде: переменные окружения или `.env` в `.gitignore`.

<!-- docs:start — generated by scripts/intro6/site_docs.py; edit the links there -->

### Официальная документация

- [Docker Hub — MySQL official image](https://hub.docker.com/_/mysql)
- [Database Client for VS Code](https://marketplace.visualstudio.com/items?itemName=cweijan.vscode-database-client2)
- [Connector/Python — Installation](https://dev.mysql.com/doc/connector-python/en/connector-python-installation.html)

<!-- docs:end -->

<!-- exercises:start — generated by scripts/intro6/sync_site_exercises.py; edit scripts/intro6/exercises/ -->
## Практика

Упражнения опираются только на то, что пройдено в этом уроке и в предыдущих. Сначала попробуйте сами, и только потом открывайте решение.

### Упражнение 1: Таблица, строка, столбец

```text
id | name | age | email
---+------+-----+---------------
 1 | Dana |  24 | dana@mail.com
 2 | Avi  |  31 | avi@mail.com
```

1. Сколько в таблице строк и сколько столбцов?
2. Что описывает каждая строка и что описывает каждый столбец?
3. Какой столбец подходит на роль первичного ключа и почему не `name`?

{{< solution >}}
1. **2 строки** и **4 столбца**.
2. Каждая **строка** (Row) — **один студент**, запись. Каждый **столбец** (Column) — одно **поле** с фиксированным типом, которое есть у всех студентов.
3. **`id`** — он уникален, никогда не меняется и создаётся автоматически. `name` не подходит: может быть два студента по имени Дана, а имя может измениться.
{{< /solution >}}

### Упражнение 2: Типы и ограничения

Напишите команду SQL, которая создаёт таблицу `students` с:

- `id` — целое число, первичный ключ, создаётся автоматически;
- `name` — текст, обязательный;
- `age` — целое число, обязательное;
- `email` — текст, обязательный и уникальный.

{{< solution >}}
```sql
CREATE TABLE students (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    age INT NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE
);
```

- `INT` / `VARCHAR(100)` — тип каждого столбца: целое число и текст до 100 символов.
- `NOT NULL` — нельзя оставить пустым.
- `UNIQUE` — нет двух студентов с одинаковым email.
- `AUTO_INCREMENT` — база сама выбирает `id`.
{{< /solution >}}

### Упражнение 3: Что поймала бы база?

```text
students = [
    {"name": "Dana", "age": 24, "email": "d@x.com"},
    {"name": "Avi", "age": 24, "email": None},
    {"name": "Noa", "age": 22, "email": "d@x.com"},
]
```

Какие из этих данных отклонила бы таблица `students` из предыдущего упражнения и из-за какого ограничения? И почему их никто не остановил в списке Python?

{{< solution >}}
- **Ави** — email равен `None` → отклонено из-за **`NOT NULL`**.
- **Ноа** — тот же email, что у Даны → отклонено из-за **`UNIQUE`**.
- **Дана** — допустимо.

У списка Python **нет правил**: туда попадает любое значение. В базе данных правила определяются **один раз**, в таблице, и действуют для всех, кто в неё пишет, — из любой программы.
{{< /solution >}}

### Упражнение 4: Запуск MySQL

1. Что делает каждая часть следующей команды?
2. Как проверить, что контейнер работает?
3. Как запустить его снова после перезагрузки компьютера — не создавая новый?

```text
docker run --name kodkod-mysql -e MYSQL_ROOT_PASSWORD=kodkod -e MYSQL_DATABASE=school -p 3306:3306 -d mysql:8.4
```

{{< solution >}}
| Часть | Значение |
|---|---|
| `docker run` | Создаёт и запускает новый **контейнер** |
| `--name kodkod-mysql` | Имя контейнера, чтобы к нему обращаться |
| `-e MYSQL_ROOT_PASSWORD=kodkod` | Пароль пользователя `root` |
| `-e MYSQL_DATABASE=school` | База с именем `school`, которая создаётся автоматически |
| `-p 3306:3306` | Порт 3306 на нашем компьютере ведёт к MySQL в контейнере |
| `-d` | Работает в фоне, терминал остаётся свободным |
| `mysql:8.4` | **Образ** (Image): MySQL версии 8.4 |

2. `docker ps` — показывает контейнеры, которые работают прямо сейчас.
3. `docker start kodkod-mysql` — запускает существующий контейнер. Его данные сохраняются. Повторный `docker run` не сработал бы, потому что имя уже занято.
{{< /solution >}}

### Упражнение 5: Первое подключение

Напишите код, который подключается к базе `school` в контейнере, печатает, активно ли соединение, закрывает его и печатает снова.

**Ожидаемый вывод:**

```text
Connected: True
Connected after close: False
```

{{< solution >}}
```python
import mysql.connector

connection = mysql.connector.connect(
    host="localhost",
    port=3306,
    user="root",
    password="kodkod",
    database="school",
)
print("Connected:", connection.is_connected())
connection.close()
print("Connected after close:", connection.is_connected())
```

```text
Connected: True
Connected after close: False
```

`mysql.connector.connect` открывает сетевое соединение с сервером MySQL в контейнере. Если контейнер не запущен — вы получите ошибку: `Can't connect to MySQL server`.
{{< /solution >}}

<!-- exercises:end -->
