Python: уроки, справочник и задачи

Добавление и обновление данных в SQLite на Python

Раздел: SQL и SQLite · Обновлено 31.10.2026

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

Вставка данных в SQLite
Содержание статьи
  1. Подготовка базы для примеров
  2. Вставка одной записи и получение ее id
    1. Именованные параметры
  3. Массовая вставка через executemany
  4. Обновление данных командой UPDATE
    1. Обновление по выражению и для нескольких строк
  5. Транзакции, commit и контекстный менеджер
  6. Вставить или обновить: upsert
  7. Частые вопросы
    1. Почему после INSERT данные не появились в базе?
    2. Что означает ошибка database is locked?
    3. Можно ли подставить имя таблицы через знак вопроса?
    4. Как узнать, сколько строк изменил UPDATE?

Подготовка базы для примеров

Все примеры используют небольшую базу с двумя таблицами: проекты и задачи, привязанные к проекту через внешний ключ. Как создавать базу и таблицы, подробно разобрано в уроке про подключение к 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() с INSERTlastrowid
Добавить много строкexecutemany()rowcount
Изменить строкиexecute() с UPDATE и WHERErowcount
Добавить или обновитьINSERT ... ON CONFLICT DO UPDATESELECT по ключу

Чтение, фильтрацию и удаление строк разбирает следующий урок: выборка и удаление данных в 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 он равен числу затронутых строк.