SQLite в Python: основы работы с реляционными базами данных

Большинство production-приложений хранят данные не в файлах, а в реляционных базах данных: PostgreSQL, MySQL, SQLite. Это значит — рано или поздно вы будете писать SQL-запросы из Python и читать результаты обратно в код.

В этой статье разберём базовый паттерн на примере SQLite. Это полноценная реляционная БД, встроенная в Python из коробки: ничего устанавливать не нужно, БД хранится в одном файле, и тот же подход работает с любой другой СУБД.

Эта статья даст общее представление о работе с SQL из Python. Для глубокого изучения самого SQL рекомендуем бесплатный курс SQL Academy с практическими заданиями.

Подключение и cursor

Для работы с SQLite в стандартной библиотеке есть модуль sqlite3. Базовый паттерн: создаём соединение, получаем cursor (через него выполняем запросы), закрываем соединение.

Python 3.13
import sqlite3

# Подключаемся к БД (файл создастся автоматически)
connection = sqlite3.connect('tasks.db')
cursor = connection.cursor()

# ... здесь будут запросы

connection.close()
print("Готово")
Готово

Иллюстрация: Python-код слева, sqlite3 в середине, файл tasks.db с маленькой таблицей справа; стрелки показывают что в БД летит SQL-запрос, а обратно возвращаются строки

Удобнее использовать соединение как контекстный менеджер (with) — он сам закроет соединение и сохранит изменения:

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    # ... запросы

print("Готово")
Готово

Создание таблицы

В реляционной БД данные лежат в таблицах. Каждая таблица описывается схемой: какие столбцы, какого типа, какие ограничения. Создаём через SQL-команду CREATE TABLE:

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            completed BOOLEAN DEFAULT FALSE
        )
    ''')

print("Таблица tasks готова")
Таблица tasks готова

Что значат части SQL:

  • CREATE TABLE IF NOT EXISTS tasks — создать таблицу tasks, если ещё нет
  • id INTEGER PRIMARY KEY AUTOINCREMENT — целочисленный первичный ключ с автоинкрементом
  • title TEXT NOT NULL — текстовое поле, обязательное
  • completed BOOLEAN DEFAULT FALSE — логическое поле, по умолчанию False

CRUD: четыре базовые операции

CRUD это акроним от Create / Read / Update / Delete. Это четыре операции, которые покрывают почти всю работу с данными. Дальше предполагаем, что таблица tasks уже создана.

CREATE: добавление данных

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute(
        "INSERT INTO tasks (title) VALUES (?)",
        ("Изучить SQLite",)
    )
    cursor.execute(
        "INSERT INTO tasks (title) VALUES (?)",
        ("Сделать покупки",)
    )

print("Задачи добавлены")
Задачи добавлены

Здесь принципиально важно: значения не вставляются прямо в строку SQL через f-string или конкатенацию. Вместо этого используется параметр ?, а значение передаётся вторым аргументом в execute. Это защищает от SQL-инъекций:

Python 3.13
# ОПАСНО: пользовательский ввод склеивается с SQL
user_input = "'; DROP TABLE tasks; --"
cursor.execute(f"INSERT INTO tasks (title) VALUES ('{user_input}')")
# выполнится: INSERT ... VALUES (''); DROP TABLE tasks; --')

# БЕЗОПАСНО: значение передаётся отдельно
cursor.execute("INSERT INTO tasks (title) VALUES (?)", (user_input,))
# выполнится: INSERT ... VALUES ('\'; DROP TABLE tasks; --')

Правило: никогда не склеивайте пользовательский ввод в SQL-строку, всегда используйте параметры через ?.

READ: чтение данных

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute("SELECT id, title, completed FROM tasks")
    rows = cursor.fetchall()

for row in rows:
    print(row)
(1, 'Изучить SQLite', 0)
(2, 'Сделать покупки', 0)

cursor.fetchall() возвращает все строки результата как список кортежей. Доступ к полям по индексу: row[0] это id, row[1] это title и т.д.

Если нужно достать только одну строку (например по id), используется fetchone():

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute("SELECT title FROM tasks WHERE id = ?", (1,))
    row = cursor.fetchone()

print(row)
('Изучить SQLite',)

UPDATE: обновление данных

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute(
        "UPDATE tasks SET completed = ? WHERE id = ?",
        (True, 1)
    )

print("Задача 1 помечена выполненной")
Задача 1 помечена выполненной

WHERE id = ? обязательно: без условия UPDATE обновит все строки в таблице.

DELETE: удаление данных

Python 3.13
import sqlite3

with sqlite3.connect('tasks.db') as connection:
    cursor = connection.cursor()
    cursor.execute("DELETE FROM tasks WHERE id = ?", (2,))

print("Задача 2 удалена")
Задача 2 удалена

Та же история: без WHERE DELETE удалит всю таблицу строк.

Что осталось за кадром

В реальном production-коде есть несколько важных тем, которые мы здесь не разбираем подробно, но о которых стоит знать:

  • Транзакции (BEGIN/COMMIT/ROLLBACK): группа изменений выполняется атомарно — либо все, либо ни одной. with sqlite3.connect(...) коммитит автоматически при выходе из блока.
  • Связи между таблицами (FOREIGN KEY, JOIN): большинство реальных схем содержат несколько связанных таблиц (пользователи и их задачи, заказы и товары).
  • Индексы: ускоряют поиск по часто используемым колонкам.

Эти темы покрывает курс SQL Academy.

Проверка понимания

Почему нужно передавать значения в execute() через параметр ?, а не вставлять напрямую в SQL-строку?


В следующей статье возьмём SQLAlchemy Core: это библиотека, которая позволяет строить SQL-запросы из Python-выражений вместо строк. SQL-инъекции там защищены автоматически, а один и тот же код работает с PostgreSQL, MySQL и SQLite.