Урок 81. Практикум 20: Данные о сотрудниках. Часть 2

📁 Блок: Базы данных ⏱️ Время изучения: ~90 мин 🎯 Сложность: Практикум
#for #in #with #as #enumerate #from #print

⚡ Кратко

Повторяем основы работы с MySQL из Python: соединение через with, соединение таблиц JOIN, параметризованные запросы и проверка ввода.

  • JOIN объединяет строки из нескольких таблиц по общему полю.
  • %s отделяет данные от текста SQL и защищает от инъекций.
  • Операторы сравнения нельзя передавать как параметры, поэтому проверяем их по белому списку.
  • Ввод номера отдела проверяем через try ... except ValueError и диапазон.

🔌 PyMySQL: соединение и курсор

Для работы с MySQL из Python используем библиотеку pymysql. Соединение удобно открывать через контекстный менеджер with: он автоматически закроет ресурсы, даже если произошла ошибка.

import pymysql

config = {
    "host": "ich-db.edu.itcareerhub.de",
    "user": "ich1",
    "password": "password",
    "database": "hr",
    "charset": "utf8mb4",
}

После pymysql.connect(**config) получаем объект соединения. Через него создаём курсор и выполняем SQL-запросы методом cursor.execute(sql, params).

🔗 Соединение таблиц

Информация о сотрудниках разбросана по трём таблицам:

  • employees — имя, фамилия, зарплата, department_id, job_id;
  • departments — название отдела, department_id;
  • jobs — название должности, job_id.

Чтобы получить имя, фамилию, должность и зарплату в одной выборке, соединяем таблицы по внешним ключам:

SELECT first_name, last_name, job_title, salary
    FROM employees
    JOIN departments ON employees.department_id = departments.department_id
    JOIN jobs ON employees.job_id = jobs.job_id
    WHERE departments.department_name = %s
    ORDER BY salary DESC

JOIN по умолчанию — это INNER JOIN: в результат попадают только те строки, для которых найдено совпадение во всех таблицах.

🛡️ Параметризованные запросы

Название отдела, которое вводит пользователь, нельзя вставлять в SQL-строку через f-string. Вместо этого используем плейсхолдер %s и передаём значение отдельно:

cursor.execute("SELECT * FROM employees WHERE department_id = %s", (department_id,))

Если параметр один, кортеж всё равно нужен: не забудьте запятую (value,).

⚠️ Проверка оператора сравнения

Знак сравнения (>, <, =, >=, <=) — это часть языка SQL, его нельзя передать как параметр %s. Безопасный способ: проверить ввод по белому списку и только потом подставить в строку запроса:

valid_operators = {'>', '<', '=', '>=', '<='}
condition = input("Enter condition: ").strip()
if condition not in valid_operators:
    print("Invalid condition.")
else:
    query = f"SELECT * FROM t WHERE salary {condition} %s"
    cursor.execute(query, (value,))

✅ Валидация пользовательского ввода

Перед использованием ввода проверяем два условия:

  • Это целое число — int() может выбросить ValueError.
  • Число находится в допустимом диапазоне — от 1 до количества отделов.

Если проверка не проходит, программа просит ввести номер снова, а не падает.

🧯 Обработка ошибок

При работе с базой могут возникать ошибки подключения, SQL или ввода. Рекомендуется отлавливать конкретные исключения:

try:
    selected_index = int(input("Enter department number: "))
except ValueError:
    print("Invalid input. Please enter a number.")

try:
    with pymysql.connect(**config) as connection:
        ...
except pymysql.MySQLError as e:
    print("Database error:", e)

✨ Современные практики

Проверить по документации: рекомендации о хранении паролей в переменных окружения, использовании python-dotenv и ORM (например, SQLAlchemy) выходят за рамки исходной практики. В реальных проектах секреты не хранят в коде.
  • Храните параметры подключения в переменных окружения или файле .env.
  • Никогда не собирайте SQL через f-string или конкатенацию с пользовательскими данными.
  • Выносите повторяющиеся операции в функции: подключение, печать результата, ввод с проверкой.
  • Используйте with для автоматического закрытия ресурсов.