SQLite в Python: основы работы с реляционными базами данных
Сообщения в вашем телефоне, история браузера, настройки половины приложений на ноутбуке — огромная их часть лежит в SQLite: полноценной реляционной БД без сервера, встроенной прямо в Python. Ставить ничего не нужно, вся база — один файл, а тот же подход потом работает с любой другой СУБД.
На SQLite и разберём базовый приём работы с реляционной БД: как из Python отправить SQL-запрос и прочитать строки обратно в код.
Эта статья даст общее представление о работе с SQL из Python. Для глубокого изучения самого SQL рекомендуем бесплатный курс SQL Academy с практическими заданиями.
Подключение и cursor
Для работы с SQLite в стандартной библиотеке есть модуль sqlite3. Базовый паттерн: создаём соединение, получаем cursor (через него выполняем запросы), закрываем соединение.
Python 3.13import sqlite3 # Подключаемся к БД (файл создастся автоматически) connection = sqlite3.connect('tasks.db') cursor = connection.cursor() # ... здесь будут запросы connection.close() print("Готово")Готово

Удобнее обернуть соединение в with: на выходе из блока изменения сохранятся сами, а если внутри случится ошибка — откатятся. Одна тонкость: у sqlite3 контекстный менеджер не закрывает соединение, поэтому close() в конце всё равно нужен.
Python 3.13import sqlite3 connection = sqlite3.connect('tasks.db') with connection: cursor = connection.cursor() # ... запросы connection.close() # with не закрывает — закрываем сами print("Готово")Готово
Создание таблицы
В реляционной БД данные лежат в таблицах. Каждая таблица описывается схемой: какие столбцы, какого типа, какие ограничения. Создаём через SQL-команду CREATE TABLE:
Python 3.13import 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.13import 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 search = "' OR '1'='1" cursor.execute(f"SELECT * FROM tasks WHERE title = '{search}'") # SQL превращается в: SELECT * FROM tasks WHERE title = '' OR '1'='1' # условие '1'='1' истинно всегда → вернутся ВСЕ задачи, а не только нужная # БЕЗОПАСНО: значение передаётся отдельно cursor.execute("SELECT * FROM tasks WHERE title = ?", (search,)) # ищется задача с буквальным названием "' OR '1'='1" — лишнего не вернётся
Здесь злоумышленник подставил кусок SQL в обычное поле поиска и получил все строки таблицы. Тем же приёмом обходят проверку пароля или удаляют данные. Правило: никогда не склеивайте пользовательский ввод в SQL-строку, всегда используйте параметры через ?.
READ: чтение данных
Python 3.13import 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.13import 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.13import 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.13import 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(...) коммитит автоматически при выходе из блока.
- JOIN и выборки по нескольким таблицам сразу: большинство реальных схем содержат несколько связанных таблиц (пользователи и их задачи, заказы и товары), и данные из них достают одним запросом.
- Индексы: ускоряют поиск по часто используемым колонкам.
Эти темы покрывает курс SQL Academy.
Проверка понимания
Почему нужно передавать значения в execute() через параметр ?, а не вставлять напрямую в SQL-строку?
В следующей статье возьмём SQLAlchemy Core: это библиотека, которая позволяет строить SQL-запросы из Python-выражений вместо строк. SQL-инъекции там защищены автоматически, а один и тот же код работает с PostgreSQL, MySQL и SQLite.
