Добавление и обновление данных в SQLite на Python
Работа с SQLite в Python через модуль sqlite3 чаще всего начинается с двух операций: добавления новых строк командой INSERT и изменения существующих командой UPDATE. Здесь показано, как правильно передавать значения в запрос, получать id новой записи, вставлять данные пачкой, проверять количество измененных строк и не терять данные из-за незакрытой транзакции.

Содержание статьи
Подготовка базы для примеров
Все примеры используют небольшую базу с двумя таблицами: проекты и задачи, привязанные к проекту через внешний ключ. Как создавать базу и таблицы, подробно разобрано в уроке про подключение к SQLite из Python, здесь только минимальная схема, чтобы код можно было запустить сразу.
import sqlite3
conn = sqlite3.connect('projects.db')
conn.executescript('''
CREATE TABLE IF NOT EXISTS projects (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
begin_date TEXT,
end_date TEXT
);
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
priority INTEGER DEFAULT 1,
project_id INTEGER NOT NULL REFERENCES projects(id),
begin_date TEXT,
end_date TEXT
);
''')Если файла projects.db нет, connect() создаст его сам. Даты хранятся строками в формате ГГГГ-ММ-ДД: в SQLite нет отдельного типа для дат, а такой формат корректно сортируется и сравнивается. Про то, как SQLite обращается с типами столбцов, рассказано в статье о типах данных SQLite.
Вставка одной записи и получение ее id
Запрос INSERT выполняется методом execute(). Значения никогда не подставляют в строку SQL через f-строку или конкатенацию: вместо них ставят знаки вопроса, а данные передают вторым аргументом в виде кортежа. Модуль сам экранирует кавычки и спецсимволы, поэтому запрос защищен от SQL-инъекций и не ломается на названиях вроде "Кафе O'Brien".
def add_project(conn, name, begin, end):
sql = 'INSERT INTO projects (name, begin_date, end_date) VALUES (?, ?, ?)'
cur = conn.execute(sql, (name, begin, end))
conn.commit()
return cur.lastrowid
project_id = add_project(conn, 'Мобильное приложение', '2026-01-10', '2026-06-30')
print(project_id) # 1Атрибут cursor.lastrowid содержит rowid последней вставленной строки. Для столбца INTEGER PRIMARY KEY rowid совпадает с первичным ключом, поэтому так удобно получать id нового проекта, чтобы сразу привязать к нему задачи. Метод conn.execute() - сокращение: он создает курсор, выполняет запрос и возвращает этот курсор.
Именованные параметры
Когда полей много, позиционные знаки вопроса легко перепутать. В этом случае используют именованные метки с двоеточием и передают словарь. Порядок ключей в словаре значения не имеет.
task = {
'name': 'Собрать требования',
'priority': 2,
'project_id': project_id,
'begin': '2026-01-10',
'end': '2026-01-15',
}
conn.execute(
'INSERT INTO tasks (name, priority, project_id, begin_date, end_date) '
'VALUES (:name, :priority, :project_id, :begin, :end)',
task,
)
conn.commit()Такой вариант удобен, когда данные уже приходят словарем, например из JSON или из формы веб-приложения.
Массовая вставка через executemany
Если нужно добавить десятки или тысячи строк, вызывать execute() в цикле и делать commit() после каждой строки медленно: каждая фиксация транзакции означает запись на диск. Метод executemany() принимает один запрос и последовательность кортежей, а фиксацию делают один раз в конце.
tasks = [
('Прототип экранов', 1, project_id, '2026-01-16', '2026-02-01'),
('Настроить сервер', 3, project_id, '2026-02-02', '2026-02-10'),
('Тестирование', 2, project_id, '2026-02-11', '2026-03-01'),
]
cur = conn.executemany(
'INSERT INTO tasks (name, priority, project_id, begin_date, end_date) '
'VALUES (?, ?, ?, ?, ?)',
tasks,
)
conn.commit()
print(cur.rowcount) # 3После executemany() атрибут rowcount показывает общее число вставленных строк, а вот на lastrowid полагаться нельзя: по документации он обновляется только после execute(). Если id каждой новой строки важен, вставляйте строки по одной внутри одной транзакции и читайте lastrowid после каждой.
Обновление данных командой UPDATE
UPDATE изменяет значения в уже существующих строках. Главное правило - всегда указывать условие WHERE. Запрос без него молча перепишет все строки таблицы, и отменить это после commit() будет нельзя.
def update_task(conn, task_id, priority, begin, end):
sql = '''UPDATE tasks
SET priority = ?, begin_date = ?, end_date = ?
WHERE id = ?'''
cur = conn.execute(sql, (priority, begin, end, task_id))
conn.commit()
return cur.rowcount
changed = update_task(conn, 2, 3, '2026-01-17', '2026-02-03')
print(changed) # 1, если задача с id 2 существует, иначе 0Значения параметров передаются в том же порядке, в каком стоят знаки вопроса, включая id в условии WHERE. Атрибут rowcount помогает понять, нашлась ли строка: 0 означает, что условию ничего не соответствует, и это повод вывести сообщение пользователю, а не считать операцию успешной.
Обновление по выражению и для нескольких строк
В SET можно использовать не только готовые значения, но и выражения от текущих данных столбца. Например, поднять приоритет всем задачам проекта, срок которых уже прошел:
cur = conn.execute(
'''UPDATE tasks SET priority = priority + 1
WHERE project_id = ? AND end_date < date('now')''',
(project_id,),
)
conn.commit()
print('Обновлено задач:', cur.rowcount)Обратите внимание на запятую в (project_id,): без нее скобки не создают кортеж, и sqlite3 выдаст ошибку о неверном количестве параметров.
Транзакции, commit и контекстный менеджер
По умолчанию модуль sqlite3 открывает транзакцию перед INSERT и UPDATE неявно, а изменения становятся видны другим подключениям и сохраняются в файл только после conn.commit(). Если программа завершится раньше, данные пропадут. Это самая частая причина вопроса "запрос выполнился, а в базе пусто".
Удобнее использовать подключение как контекстный менеджер. Блок with conn: сам вызовет commit(), если внутри не было исключения, и rollback(), если оно возникло.
try:
with conn:
pid = conn.execute(
'INSERT INTO projects (name) VALUES (?)', ('Сайт',)
).lastrowid
conn.execute(
'INSERT INTO tasks (name, project_id) VALUES (?, ?)', ('Дизайн', pid)
)
except sqlite3.Error as e:
print('Изменения отменены:', e)
finally:
conn.close()Здесь проект и задача добавляются атомарно: либо обе строки, либо ни одной. Важная деталь - with conn не закрывает соединение, поэтому close() вызывается отдельно. Подробнее про перехват ошибок можно прочитать в статье об обработке исключений.
Вставить или обновить: upsert
Иногда заранее неизвестно, есть ли запись в таблице: если нет, ее нужно добавить, если есть - обновить. Для этого SQLite (начиная с версии 3.24) поддерживает конструкцию INSERT ... ON CONFLICT ... DO UPDATE. Конфликт определяется по столбцу с ограничением UNIQUE или PRIMARY KEY.
conn.execute('CREATE TABLE IF NOT EXISTS stock (sku TEXT PRIMARY KEY, qty INTEGER)')
sql = '''INSERT INTO stock (sku, qty) VALUES (?, ?)
ON CONFLICT(sku) DO UPDATE SET qty = qty + excluded.qty'''
with conn:
conn.execute(sql, ('A-100', 5))
conn.execute(sql, ('A-100', 3))
print(conn.execute('SELECT qty FROM stock WHERE sku = ?', ('A-100',)).fetchone())
# (8,)Псевдотаблица excluded содержит значения, которые пытались вставить. Есть и более старая форма INSERT OR REPLACE, но она удаляет старую строку и вставляет новую, из-за чего теряются значения столбцов, не указанных в запросе.
| Задача | Метод или конструкция | Что проверить после |
|---|---|---|
| Добавить одну строку | execute() с INSERT | lastrowid |
| Добавить много строк | executemany() | rowcount |
| Изменить строки | execute() с UPDATE и WHERE | rowcount |
| Добавить или обновить | INSERT ... ON CONFLICT DO UPDATE | SELECT по ключу |
Чтение, фильтрацию и удаление строк разбирает следующий урок: выборка и удаление данных в SQLite. Полное описание модуля есть в документации sqlite3.
Частые вопросы
Короткие ответы на проблемы, с которыми сталкиваются при записи данных в SQLite.
Почему после INSERT данные не появились в базе?
Скорее всего, не был вызван conn.commit() или программа упала до него. Используйте блок with conn:, он фиксирует транзакцию автоматически.
Что означает ошибка database is locked?
Другое подключение (вторая программа, DB Browser с несохраненными изменениями) держит открытую транзакцию на запись. Завершите ее и увеличьте ожидание: sqlite3.connect('projects.db', timeout=10).
Можно ли подставить имя таблицы через знак вопроса?
Нет, параметры работают только для значений. Имена таблиц и столбцов вставляют в строку SQL, предварительно проверив их по белому списку допустимых названий.
Как узнать, сколько строк изменил UPDATE?
Прочитайте cursor.rowcount сразу после выполнения запроса. Для UPDATE и DELETE он равен числу затронутых строк.