Пишем запросы через модуль sqlite в Python 3


01 июня 2021


Работа с модулем sqite3 и Python для создания запросов

Для работы с базой SQLite в Python уже есть модуль с аналогичным названием. Модуль относится к базовым и устанавливается вместе с интерпретатором. Вам может понадобится установка через pip, если вы используете какую-то другую сборку Python. Рассмотрим модуль на примерах подключения к базе и создания запросов.

 

Особенности SQLite

У самой базы SQLite есть несколько особенностей, которые отличают ее от остальных баз. В первую очередь - эта база данных не нуждается в установке, а работает через модули языков. У нее так же нет клиент-серверной архитектуры. Приложение и база, обычно, располагаются рядом на одном хосте. Из-за этого эта база популярна у мобильных приложений, которые не нуждаются в подключении к интернету, но это не единственное место где ее используют.

 

Создание и подключение к базе данных

Хоть SQLite не является клиент-серверной базой мы все равное должны подключаться к ней. Это можно сделать так:

import sqlite3

connection = sqlite3.connect('database.db')

Если этот файл не будет найден по указанному пути, то создастся новый. Если у вас указан полный путь до файла, а папки не существует - произойдет ошибка. Вы можете использовать следующую проверку что бы убедится, что файл существует и данные не будут записаны в новый файл:

import sqlite3
import os

database = 'database.db'

def check_db(filename):
    return os.path.exists(filename)

if check_db(database):
   connection = sqlite3.connect(database)

Еще один способ создать базу - поместить ее в оперативную память. Учитывайте, что в этом случае она временная и будет жить до перезагрузки компьютера или смерти процесса:

import sqlite3

connection = sqlite3.connect(':memory:')

Так как база - это все тот же файл и в SQL существует понятие транзакций, которые должны корректно завершаться, мы должны закрывать каждое подключение к базе. Это можно сделать через метод '.close()' или использовать менеджер контекста:

database = 'database.db'

connection = sqlite3.connect(database)
connection.close()

# или

from contextlib import closing
with closing(sqlite3.connect(database)) as connection:
    pass

Использовать библиотеку contextlib и метод closing - совет со stackoverflow, где объясняется что иначе соединение не будет закрыто.

Любые операции изменения базы должны подтверждаться методом commit() или откатываться методом rollback(). Если вы этого не сделаете, то может выполнится автоматический rollback().

 

Курсор и execute

Для выполнения SQL запросов в библиотеке есть несколько методов. Обычный способ - это создание курсора, который будет перемещать по полученным значениям:

cursor = connection.cursor()

После его создания мы сможем выполнять запрос:

cursor.execute(query1)

Или можно использовать метод .scriptexecute(), который учитывает что вы передаете несколько запросов разделенных ';'. В этом случае коммиты выполняются автоматически:

cursor.executescript(query1)

Есть еще один способ создания курсора, в этом случае он будет временным:

connection.execute()
connection.scriptexecute()
 

 

Выполнение запросов

Что бы создать таблицу в реляционных базах нам нужно определиться с типами данных, которые будут в ней хранится. Мы создадим таблицу, которая будет хранить название файла, размер и дату его создания. 

name size date
pic1.jpg 50KB 2021-12-19
pic2.jpg 60KB 2021-12-20

Создание такой таблицы выполняется в несколько шагов. 

Создание таблицы и вставка значений с CREATE и INSERT

Любая таблица состоит из колонок. Эти колонки определяют типы данных. В нашем случае, чистый SQL запрос, который создаст таблицу, будет следующим:

CREATE TABLE images(
    name text primary key,
    size text,
    date date
);

Еще один запрос, который вставит эти данные в таблицу:

INSERT INTO images(name, size, date)
VALUES ('pic1.jpg', '50KB', '2021-12-19'),('pic2.jpg', '50KB', '2021-12-20')

Что бы эти запросы были выполнены нужно использовать курсор одним из методов, которые были описаны выше. Это может выглядеть так:

import sqlite3
 
# запрос на создание таблицы
query1 = '''
CREATE TABLE images(
    name text primary key,
    size text,
    date date
);'''

# запрос на вставку данных
query2 = '''
INSERT INTO images(name, size, date)
VALUES ("pic1.jpg", "50KB", "2021-12-19"),("pic2.jpg", "50KB", "2021-12-20")
'''

database = 'database.db'

connection = sqlite3.connect(database)
# выполняем 1-ый запрос
connection.execute(query1)
# выполняем 2-ый запрос
connection.execute(query2)
connection.commit()
connection.close()

Узнать сколько строк было изменено через запрос можно следующим образом:

connection.execute(query1).rowcount()

Выполнение запроса к sqlite в Python и получение количества измененных строк

Еще один метод total_changes(), который показывает сколько строк было изменено во всей базе с момента подключения:

connection.total_changes()

Каждое изменение базы должно сопровождаться методом commit, которое подтверждает, что результат успешен и должен быть сохранен в базе.

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

В языке SQL, обновить одно из значений в базе, можно с инструкцией UPDATE. Так мы, за счет чистого SQL, изменим название одной из строк созданных раннее:

UPDATE images
SET name = 'newpic.jpg'
WHERE name = 'pic1.jpg'

С помощью sqlite и python этот запрос выполняется так же как и с INSERT:

import sqlite3
 
# запрос на изменение строки
query1 = '''
UPDATE images
SET name = 'newpic.jpg'
WHERE name = 'pic1.jpg';
'''

database = 'database.db'

connection = sqlite3.connect(database)
connection.execute(query1)
connection.commit()
connection.close()

Обновление значений с UPDATE в sqlite в Python

Если commit не будет выполнен, то результат не будет сохранен в базе.

Чтение данных через SELECT

Чтение данных из базы выполняется через перемещение курсора. Этот процесс выполняется через методы fetchone или fetchall, которые возвращают кортеж:

import sqlite3
from contextlib import closing

database = 'database.db'
 
with closing(sqlite3.connect(database)) as connection:
    cursor = connection.cursor()
    cursor.execute("""
                   select * from images
                   """)
    # получаем все значения
    print(cursor.fetchall())

Выполнение SELECT запроса в sqlite в Python

fetchone() будет возвращать по одной строке после каждого вызова:

import sqlite3
from contextlib import closing
 
database = 'database.db'
 
with closing(sqlite3.connect(database)) as connection:
    cursor = connection.cursor()
    cursor.execute("""
                   select * from images
                   """)

    print('1: ', cursor.fetchone())
    print('2: ', cursor.fetchone())
    print('3: ', cursor.fetchone())

Выполнение SELECT запроса fetchone в sqlite в Python

Удаление данных с DELETE

SQL запрос, который удалит строку используя колонку 'name', выглядит так:

DELETE FROM images
WHERE name='newpic.jpg'

Через код Python это будет выглядеть так:

import sqlite3
 
# запрос на изменение строки
query1 = '''
DELETE FROM images
WHERE name='newpic.jpg';
'''

database = 'database.db'

connection = sqlite3.connect(database)
connection.execute(query1)
connection.commit()
connection.close()

Выполнение commit в sqlite3 Python

Параметризация запроса

У вас может быть необходимость передать в SQL дополнительные параметры. Вы можете использовать форматирования строк Python, но это будет ошибкой:

value = 'pic2.jpg'
cursor.execute(f'select * from images where name = "{value}"')

Проблема этого способа в том, что пользователь может передать вместо 'value' другой SQL запрос. Такой действие называется SQL инъекцией. Каждый передаваемый параметр в SQL нуждается в дополнительных проверках и они уже реализованы в методе execute:

values = ('pic2.jpg', 'pic1.jpg')
cursor.execute('select * from images where name = ? or name = ?', values)

Параметризация запроса в sqlite в Python

То есть вместо знаков '?' у нас будут подставляться значения из кортежа.

Мы можем создать несколько запросов используя .executemany(). В примере ниже будет выполнено 2 разных 'INSERT' для каждого из кортежей (с точки зрения SQL такой подход считается плохой практикой):

values = [('pic4.jpg', '52KB', '2021-12-19'), ('pic5.jpg', '54KB', '2021-12-19')]
cursor.executemany('INSERT INTO images(name, size, date) VALUES (?, ?, ?)', values)

 

Получение таблицы в виде словаря

Мы можем преобразовать таблицу и вернуть ее не в виде кортежей с последовательными значениями, а в виде словаря. Для этого нам нужно использовать другой класс в подключении. Это можно сделать так:

import sqlite3
from contextlib import closing
 
database = 'database.db'
 
with closing(sqlite3.connect(database)) as connection:
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("""
                   select * from images
                   """)

    row = cursor.fetchone()
    print('Объект "Row": ', row)
    print('Словарь: ', dict(row))

Преобразование запроса в словарь с SQLite и Python

Вам не обязательно использовать преобразование в словарь, что бы получить значение по ключу. Такая возможно уже есть у класса Row:

with closing(sqlite3.connect(database)) as connection:
    connection.row_factory = sqlite3.Row
    cursor = connection.cursor()
    cursor.execute("""
                   select * from images
                   """)

    for i in cursor.fetchall():
       # получаем значения по ключу
       print(i['name'])
    

Получение результата запроса по ключу в SQLite и Python

 

 

Транзакции

В реляционных базах данные есть принцип ACID. Если не вдаваться в технические подробности, то он объясняет как должны работать транзакции. Сама транзакция - это один или несколько запросов, которые выполняются вместе. Эти запросы могут быть успешно выполнены или все вместе отменены. Простой пример - это 2 таблицы, в которые вы вставляете данные. Если в 1 таблице произойдет ошибка, то и операция во 2-ой таблице должна отмениться.

Условия для успешного выполнения транзакций могут быть заданы самим разработчиком. Подтверждаются транзакции через метод commit(), а отменяются через rollback().

Если вы закрываете подключение к базе корректно (используя менеджер контекста with или метод .close()), то все не завершенные запросы выполнят commit. Если у вас произойдет сбой (аварийное выключение компьютера например), то должен будет сработать один из принципов ACID для корректного завершения транзакций, но может произойти и rollback.

В следующем примере мы создадим 2 подключения к базе и в одном из них данные не будут записаны в базу из-за отсутствия commit():

import sqlite3
from contextlib import closing
 
database = 'database.db'
 
# функция для вывода данных из таблицы
def show_table(db_con):
    cursor = db_con.cursor()
    cursor.execute('select name, size, date from images;')
    for name, size, date in cursor.fetchall():
        print(name, size, date)
 
 
with closing(sqlite3.connect(database)) as db_con:
    print('Таблица до добавления данных:')
    show_table(db_con)
    cursor1 = db_con.cursor()

    # создание транзакции
    cursor1.execute("""
    insert into images (name, size, date)
    values ('pic3.jph', '10KB', '2021-03-10');
    """)
 
    print('Данные при текущем подключении и без завершения транзакции:')
    show_table(db_con)
 
    print('Данные при новом подключении и без завершения транзакции:')
    with sqlite3.connect(database) as db_con2:
        show_table(db_con2)
 
    # Выполняем коммит
    db_con.commit()
    print('Данные при новом подключении и с завершённой транзакцией:')
    with sqlite3.connect(database) as db_con3:
        show_table(db_con3)

Транзакции в SQLite и Python

rollback работает противоположным образом. Т.е. данные, которые были созданы в первом insert запросе будут удалены из базы вовсе. Транзакция может либо завершиться, либо откатится. Выполнить первое, после второго, как и наоборот, нельзя.

Лучше практикой является явное закрытие каждого  подключения и транзакции:

timeout транзакции

Если транзакция не будет закрыта, то таблица будет заблокирована (зависит так же от типа используемой транзакции). Сколько эта транзакция останется в таком виде зависит от параметра 'timeout'. Он устанавливается при подключении:

sqlite3.connect(database, timeout=5)

По умолчанию это значение равное 5. 

...

Теги: #sqlite


Каналы
Telegram FixMyPc Telegram Лента FixMyPC RSS Rss
Популярные тэги
О блоге
Этот блог представляет собой конспекты выученного материала, преобретенного опыта и лучшие практики в системном администрировании и программировании.