Подключение к реляционным базам данных в скриптах на Python
Способы подключения к базе данных из Python
Встроенный модуль sqlite3 для локальной базы данных
Для быстрого доступа к базе данных без установки дополнительных пакетов Python предлагает модуль sqlite3. Он удобен в небольших приложениях, тестовых сценариях и аналитических скриптах, работающих с файлом на диске.
Пример создания таблицы и добавления записи:
import sqlite3
conn = sqlite3.connect('shop.db')
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE
)
''')
cursor.execute(
'INSERT INTO categories (name) VALUES (?)',
('Напитки',)
)
conn.commit()
cursor.execute('SELECT * FROM categories')
print(cursor.fetchall())
conn.close()
Python работа с базой (работа с базой данных в python)
Строка подключения передаётся в connect, затем создаётся курсор. Знак вопроса в SQL-запросе обозначает параметр, а кортеж подставляет значение. Метод commit делает вставку постоянной.
Потеря данных без commit
Если не вызвать commit, данные будут видны только внутри текущего соединения. После закрытия соединения они исчезнут. Способ устранения: использовать соединение как контекстный менеджер с with sqlite3.connect(...) as conn, тогда commit выполняется автоматически.
Основной сценарий: локальная база в одном файле, однопользовательское приложение или прототип.
Помимо этого основного способа, в Python есть несколько альтернатив для разных типов баз данных.
Как подключиться к серверной СУБД PostgreSQL?
Для работы с общим сервером применяется драйвер psycopg2. Установка выполняется командой pip install psycopg2-binary, чтобы не компилировать код на целевой машине.
pip install psycopg2-binary
import psycopg2
conn = psycopg2.connect(
host='127.0.0.1',
port='5432',
dbname='shop',
user='shop_user',
password='secret'
)
cursor = conn.cursor()
cursor.execute(
'INSERT INTO orders (customer_id, total) VALUES (%s, %s) RETURNING id',
(42, 1500.00)
)
order_id = cursor.fetchone()[0]
conn.commit()
cursor.close()
conn.close()
print(order_id)
В psycopg2 параметры передаются через %s, что защищает от SQL-инъекций. Метод RETURNING id позволяет сразу получить сгенерированный идентификатор записи.
Ошибка аутентификации
При неверном пароле возникает psycopg2.OperationalError: password authentication failed. Проверяются имя пользователя, пароль и наличие прав на указанную базу данных.
Основное назначение такого подключения: общая база для нескольких приложений или пользователей.
Как использовать SQLAlchemy для унификации работы с разными базами?
SQLAlchemy является обёрткой над драйверами баз данных. Она умеет подключаться к SQLite, PostgreSQL, MySQL, Oracle и другим системам через единую строку подключения.
from sqlalchemy import create_engine, text
engine = create_engine('postgresql+psycopg2://shop_user:secret@127.0.0.1/shop')
with engine.connect() as conn:
conn.execute(
text('UPDATE products SET price = price * 1.1 WHERE category_id = :cat'),
{'cat': 2}
)
conn.commit()
Строка подключения содержит диалект, драйвер, логин, пароль и адрес сервера. В тексте запроса имена параметров начинаются с двоеточия. Этот способ удобен при переносе проекта между СУБД.
Проблема с типами данных
При переключении между базами некоторые типы могут отличаться. SQLAlchemy Core решает эту задачу своими типами, например Integer, String и DateTime.
Как выполнять простые запросы к MySQL через PyMySQL?
PyMySQL предоставляет минимальный интерфейс для MySQL. Он часто применяется в скриптах, где нужно быстро подключиться к существующей базе и выполнить несколько команд.
import pymysql
conn = pymysql.connect(
host='127.0.0.1',
user='root',
password='pass',
database='mydb',
charset='utf8mb4'
)
with conn.cursor() as cursor:
cursor.execute('SELECT id, name FROM cities ORDER BY name')
for row in cursor.fetchall():
print(f'{row[0]:3d} {row[1]}')
conn.close()
Курсор используется внутри контекстного менеджера, чтобы гарантировать закрытие ресурсов. Метод charset снижает риск проблем с кириллицей.
Несовпадение типов при сравнении
MySQL часто требует явное приведение типов. Например, при сравнении строки и числа возникает ошибка Truncated incorrect DOUBLE value. Необходимо применять правильные типы данных в схеме.
Как работать с базой данных без написания SQL?
Объектно-реляционное отображение в SQLAlchemy позволяет описывать таблицы классами Python. Тогда запросы строятся через методы, а не через текстовые команды.
from sqlalchemy import Column, Integer, String, create_engine
from sqlalchemy.orm import declarative_base, Session
Base = declarative_base()
class Publisher(Base):
__tablename__ = 'publishers'
id = Column(Integer, primary_key=True)
name = Column(String(100), nullable=False)
engine = create_engine('sqlite:///publishers.db')
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(Publisher(name='Питер'))
session.commit()
Класс Publisher соответствует таблице, экземпляр класса соответствует строке. Сессия добавляет изменения и сохраняет их в базе.
Расхождение между классом и таблицей
Если таблица уже существует, но отличается от класса, возникают ошибки запросов. Помогает alter или удаление старой таблицы.
Цель варианта: уменьшение рутинного SQL-кода и ускорение разработки сложных моделей данных.
Расширенные примеры для разных сценариев
В этом блоке собраны примеры, которые выходят за рамки простого выполнения SELECT и INSERT. Они показывают возможности модуля sqlite3, SQLAlchemy и драйвера psycopg2.
Словарь строк через row_factory
Объект sqlite3.Row позволяет получать значения по имени колонки. Это удобно при большом количестве полей и уменьшает вероятность ошибки с индексами.
import sqlite3
conn = sqlite3.connect(':memory:')
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
cursor.execute('CREATE TABLE employees (id INTEGER PRIMARY KEY, name TEXT, salary INTEGER)')
cursor.executemany(
'INSERT INTO employees (name, salary) VALUES (?, ?)',
[('Анна', 60000), ('Борис', 85000), ('Виктор', 95000)]
)
conn.commit()
cursor.execute('SELECT name, salary FROM employees ORDER BY salary DESC')
rows = cursor.fetchmany(2)
for row in rows:
print(row['name'], row['salary'])
conn.close()
Виктор 95000 Борис 85000
Метод fetchmany возвращает ограниченное число строк, что помогает при постраничном выводе.
Пользовательская функция SQLite
SQLite позволяет создавать функции на Python и использовать их прямо в SQL. Следующий пример считает цену со скидкой.
import sqlite3
def discount(price, percent):
return round(price * (100 - percent) / 100, 2)
conn = sqlite3.connect(':memory:')
conn.create_function('discount', 2, discount)
cursor = conn.cursor()
cursor.execute('CREATE TABLE products (name TEXT, price REAL)')
cursor.executemany(
'INSERT INTO products VALUES (?, ?)',
[('Монитор', 25000.00), ('Клавиатура', 5000.00)]
)
conn.commit()
cursor.execute('SELECT name, discount(price, 10) FROM products')
for name, new_price in cursor.fetchall():
print(name, new_price)
conn.close()
Монитор 22500.0 Клавиатура 4500.0
Функция create_function принимает имя функции, число аргументов и ссылку на функцию Python.
Явное управление транзакцией в SQLite
При isolation_level=None SQLite не открывает транзакцию автоматически. Это позволяет явно управлять началом и завершением блока обновлений.
import sqlite3
conn = sqlite3.connect(':memory:', isolation_level=None)
cursor = conn.cursor()
cursor.execute('CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)')
cursor.execute('INSERT INTO accounts VALUES (1, 1000)')
cursor.execute('INSERT INTO accounts VALUES (2, 500)')
try:
cursor.execute('BEGIN')
cursor.execute('UPDATE accounts SET balance = balance - 200 WHERE id = 1')
cursor.execute('UPDATE accounts SET balance = balance + 200 WHERE id = 2')
cursor.execute('COMMIT')
except Exception:
cursor.execute('ROLLBACK')
raise
cursor.execute('SELECT * FROM accounts')
print(cursor.fetchall())
conn.close()
[(1, 800), (2, 700)]
Если вторая UPDATE приведёт к ошибке, ROLLBACK отменит первую. Такая схема защищает от частичного изменения данных.
Отражение таблицы в SQLAlchemy Core
Отражение позволяет загрузить структуру существующей таблицы в объект Table без описания колонок вручную.
from sqlalchemy import create_engine, MetaData, Table, select
engine = create_engine('sqlite:///reflect.db')
with engine.begin() as conn:
conn.exec_driver_sql('DROP TABLE IF EXISTS authors')
conn.exec_driver_sql('CREATE TABLE authors (id INTEGER PRIMARY KEY, name TEXT)')
conn.exec_driver_sql("INSERT INTO authors (id, name) VALUES (1, 'Лев Толстой')")
metadata = MetaData()
authors = Table('authors', metadata, autoload_with=engine)
with engine.connect() as conn:
result = conn.execute(select(authors.c.name).where(authors.c.id == 1))
print(result.scalar())
Лев Толстой
Благодаря autoload_with загружаются названия колонок и их типы. Указание where строится через сравнение объектов колонок.
Связь многие-к-одному в SQLAlchemy ORM
ORM позволяет работать с графом объектов. При обращении к author.books загружаются связанные строки из таблицы books.
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, Session, relationship
Base = declarative_base()
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
name = Column(String(50))
books = relationship('Book', back_populates='author')
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
title = Column(String(100))
author_id = Column(Integer, ForeignKey('authors.id'))
author = relationship('Author', back_populates='books')
engine = create_engine('sqlite:///demo_orm.db')
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
with Session(engine) as session:
author = Author(name='Анна Ахматова')
author.books.append(Book(title='Вечер'))
author.books.append(Book(title='Чётки'))
session.add(author)
session.commit()
with Session(engine) as session:
author = session.query(Author).filter_by(name='Анна Ахматова').first()
print(author.name)
for book in author.books:
print(f' - {book.title}')
Анна Ахматова - Вечер - Чётки
Метод drop_all перед create_all очищает таблицы, поэтому повторный запуск не создаёт дубликатов.
Массовая загрузка данных в PostgreSQL через COPY
Оператор COPY увеличивает скорость вставки большого числа строк по сравнению с отдельными INSERT. Данные передаются через специальный буфер.
from psycopg2 import connect
from io import StringIO
conn = connect(
host='127.0.0.1',
dbname='shop',
user='shop_user',
password='secret'
)
buffer = StringIO()
buffer.write('1\tКружка\t250\n')
buffer.write('2\tТарелка\t400\n')
buffer.seek(0)
with conn.cursor() as cursor:
cursor.execute('CREATE TEMP TABLE products (id INTEGER PRIMARY KEY, name TEXT, price NUMERIC)')
cursor.copy_expert(
'COPY products (id, name, price) FROM STDIN WITH (FORMAT text)',
buffer
)
cursor.execute('SELECT count(*) FROM products')
print(cursor.fetchone()[0])
conn.commit()
conn.close()
2
Временная таблица исчезнет после закрытия соединения, поэтому пример безопасен для реальной базы данных.