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

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

⚡ Различия в двух словах

Старый: сразу преобразуем ввод в int и используем как индекс; оператор подставляем в SQL через f-string без проверки.

Новый: валидируем ввод в цикле; оператор сравнения проверяем по белому списку, а значение зарплаты передаём через %s.

Вывод: современный код устойчив к ошибкам и SQL-инъекциям.

📜 Старый подход: наивный ввод

selected_index = int(input("Enter department number: "))
selected_department = departments[selected_index - 1]
print("You selected:", selected_department)

Что здесь происходит: программа ожидает корректный ввод. Если пользователь введёт буквы или номер вне диапазона, программа упадёт с ValueError или IndexError.

❌ Почему так не надо

  • Любой текст вместо числа вызывает ValueError и аварийное завершение.
  • Номер 0 или 999 приводит к IndexError.
  • Пользователь не получает шанс исправить ошибку.

✅ Современный подход: цикл с валидацией

def read_department_number(departments):
    while True:
        try:
            selected_index = int(input("Enter department number: "))
            if 1 <= selected_index <= len(departments):
                return selected_index
            print("Invalid department number. Please try again.")
        except ValueError:
            print("Invalid input. Please enter a number.")

selected_index = read_department_number(departments)
selected_department = departments[selected_index - 1]

Что улучшилось: функция read_department_number отвечает только за ввод. Она проверяет и тип, и диапазон, и повторяет запрос при ошибке.

Преимущества:
  • Программа не падает на некорректном вводе.
  • Логика ввода вынесена в отдельную функцию и легко тестируется.
  • Главная функция остаётся короткой и читаемой.

📜 Старый подход: f-string SQL

condition = input("Enter condition: ").strip()
value = float(input("Enter salary: "))
query = f"""
    SELECT first_name, last_name, job_title, salary
    FROM employees
    WHERE departments.department_name = %s AND salary {condition} %s
"""
cursor.execute(query, (selected_department, value))

Что здесь происходит: значение condition вставляется прямо в текст SQL. Если condition пришёл от пользователя, злоумышленник может подменить оператор на произвольный SQL-код.

✅ Современный подход: белый список операторов

valid_operators = {'>', '<', '=', '>=', '<='}
condition = input("Enter condition: ").strip()
if condition not in valid_operators:
    print("Invalid condition.")
else:
    value = float(input("Enter salary: "))
    query = f"""
        SELECT first_name, last_name, job_title, salary
        FROM employees
        WHERE departments.department_name = %s AND salary {condition} %s
    """
    cursor.execute(query, (selected_department, value))

Что улучшилось: оператор проверяется на допустимость, а само число зарплаты передаётся через параметр %s. Это сочетает гибкость запроса и безопасность.

Преимущества:
  • SQL-инъекция через оператор невозможна.
  • Поддерживаются только известные операторы сравнения.
  • Значение зарплаты экранируется библиотекой PyMySQL.
Проверить по документации: вопросы параметризации операторов и защиты от SQL-инъекций подробно рассматриваются в документации PyMySQL и PEP 249 (Python Database API).