SQLite: первая база данных в Python

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

На SQLite и разберём базовый приём работы с реляционной БД: как из Python отправить SQL-запрос и прочитать строки обратно в код.

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

Для работы с SQLite в стандартной библиотеке есть модуль sqlite3. Базовый паттерн — три шага:

  • открыть соединение — открытую базу, как файл после open();
  • получить курсор — он отправляет в базу запросы и держит результат последнего;
  • в конце закрыть соединение.
Python 3.13
import sqlite3

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

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

connection.close()
print("Готово")
Готово
Python-код
SQL-запрос
строки-кортежи
tasks.db
id
title
1
Изучить SQLite
2
Сделать покупки

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

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

Дальше во всех примерах соединение обёрнуто в with: SQLite записывает изменения не мгновенно, их нужно подтверждать (по-английски commit), и with делает это сам при выходе из блока, а при ошибке откатывает. Соединение он, правда, не закрывает, но в коротких скриптах, как ниже, оно закроется вместе с программой; в долгоживущем коде зовите close(), как в первом примере.

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,
            title TEXT NOT NULL,
            completed BOOLEAN DEFAULT FALSE
        )
    ''')

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

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

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

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

CRUD — акроним от Create / Read / Update / Delete: четыре операции, которые покрывают почти всю работу с данными.

Тренироваться будем на базе в памяти: :memory: вместо имени файла даёт чистую базу при каждом запуске. Первые строки внутри with — подготовка: таблица и две задачи.

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

Python 3.13
import sqlite3

with sqlite3.connect(':memory:') as connection:
    cursor = connection.cursor()
    cursor.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT)")

    cursor.execute(
        "INSERT INTO tasks (title) VALUES (?)",
        ("Изучить SQLite",)
    )
    cursor.execute(
        "INSERT INTO tasks (title) VALUES (?)",
        ("Сделать покупки",)
    )

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

Значения передаются кортежем вторым аргументом execute, а в самом SQL вместо них стоит параметр ?. Запятая в ("Изучить SQLite",) обязательна: именно она делает скобки кортежем из одного элемента. Почему значения не вклеивают прямо в строку запроса — покажем сразу после того, как научимся читать данные.

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

Python 3.13
import sqlite3

with sqlite3.connect(':memory:') as connection:
    cursor = connection.cursor()
    cursor.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT)")
    cursor.execute("INSERT INTO tasks (title) VALUES ('Изучить SQLite'), ('Сделать покупки')")

    cursor.execute("SELECT id, title FROM tasks")
    rows = cursor.fetchall()

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

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

Когда нужна только одна строка, вместо fetchall() используют fetchone(): он возвращает первую строку результата. Увидим его в деле чуть ниже.

Теперь можно показать, зачем параметр ? нужен на самом деле. Представьте поле поиска, куда пользователь вводит название задачи, — а злоумышленник вводит кусок 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" — лишнего не вернётся

Звёздочка в SELECT * — «все колонки разом». Злоумышленник подставил кусок SQL в обычное поле поиска и получил все строки таблицы; тем же приёмом обходят проверку пароля или удаляют данные. Правило: никогда не склеивайте пользовательский ввод в SQL-строку, всегда используйте параметры через ?.

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

Python 3.13
import sqlite3

with sqlite3.connect(':memory:') as connection:
    cursor = connection.cursor()
    cursor.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, completed BOOLEAN DEFAULT FALSE)")
    cursor.execute("INSERT INTO tasks (title) VALUES ('Изучить SQLite'), ('Сделать покупки')")

    cursor.execute(
        "UPDATE tasks SET completed = ? WHERE id = ?",
        (True, 1)
    )

    cursor.execute("SELECT id, title, completed FROM tasks WHERE id = ?", (1,))
    print(cursor.fetchone())
(1, 'Изучить SQLite', 1)

Первая задача теперь выполнена: логические значения SQLite хранит как 0 и 1, и completed сменился с нуля на единицу. А WHERE id = ? обязательно: без условия UPDATE обновит все строки таблицы.

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

Python 3.13
import sqlite3

with sqlite3.connect(':memory:') as connection:
    cursor = connection.cursor()
    cursor.execute("CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT)")
    cursor.execute("INSERT INTO tasks (title) VALUES ('Изучить SQLite'), ('Сделать покупки')")

    cursor.execute("DELETE FROM tasks WHERE id = ?", (2,))

    cursor.execute("SELECT id, title FROM tasks")
    print(cursor.fetchall())
[(1, 'Изучить SQLite')]

Вторая задача исчезла — осталась одна строка. Та же история, что с UPDATE: без WHERE команда DELETE удалит все строки таблицы.

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

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

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

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

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

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

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