Pandas: чтение базы данных SQL
Pandas предоставляет набор функций для прямого взаимодействия с базами данных SQL. Результаты запросов можно напрямую считывать в DataFrame, а DataFrame также можно записывать обратно в базу данных. Это позволяет аналитикам данных избегать ручной обработки подключений к базе данных и разбора результатов, значительно упрощая взаимодействие между базами данных и Python.
Обзор основных функций
| Функция | Назначение | Тип возвращаемого значения |
|---|---|---|
pd.read_sql() |
Выполняет SQL-запросы или считывает всю таблицу (универсальная функция, работающая в обоих сценариях) | DataFrame |
pd.read_sql_query() |
Выполняет SQL-запросы, подходит для сложных запросов | DataFrame |
pd.read_sql_table() |
Напрямую читает всю таблицу, поддерживает только подключения SQLAlchemy | DataFrame |
DataFrame.to_sql() |
Записывает DataFrame в таблицу базы данных | None / int |
В практической работе рекомендуется использовать
pd.read_sql()потому что он автоматически определяет, выполнить запрос или прочитать всю таблицу, в зависимости от переданных параметров, обеспечивая наилучшую совместимость.pd.read_sql_table()Поддерживает только подключения к движку SQLAlchemy; не поддерживает нативныеsqlite3и другие подключения DB-API.
Установление подключения к базе данных
Сам Pandas не подключается напрямую к базам данных. Для установления подключения требуется сторонняя библиотека, которая затем передаётся. Есть два основных подхода:движок SQLAlchemy(рекомендуется) инативное подключение DB-API(легковесное и простое).
Способ 1: SQLAlchemy (рекомендуется)
SQLAlchemy — самый популярный инструментарий для работы с базами данных в Python. Он поддерживает все основные базы данных и обладает наилучшей совместимостью с Pandas:
pip install sqlalchemy
Пример
# Формат строки подключения SQLAlchemy: тип базы данных + driver://username:password@host:port/database name
# SQLite (файловая база данных, имя пользователя и пароль не требуются)
engine = create_engine("sqlite:///mydata.db")
# MySQL
engine = create_engine("mysql+pymysql://root:password@localhost:3306/mydb")
# PostgreSQL
engine = create_engine("postgresql+psycopg2://user:password@localhost:5432/mydb")
# SQL Server
engine = create_engine("mssql+pyodbc://user:password@server/mydb?driver=ODBC+Driver+17+for+SQL+Server")
# Проверить, успешно ли установлено подключение
with engine.connect() as conn:
print("Подключение успешно")
Способ 2: нативное подключение sqlite3 (только для SQLite)
Встроенный в Pythonsqlite3модуль не требует дополнительной установки и подходит для легковесных локальных баз данных SQLite:
Пример
# Подключение к файловой базе данных SQLite (автоматически создаётся, если файл не существует)
conn = sqlite3.connect("mydata.db")
# Подключение к базе данных в памяти (данные исчезают после завершения программы; подходит для тестирования)
conn_memory = sqlite3.connect(":memory:")
# Подключение необходимо закрыть вручную после использования
# conn.close()
pd.read_sql() Чтение данных
Базовый синтаксис
pd.read_sql(sql, con, index_col=None, coerce_float=True, params=None, parse_dates=None, columns=None, chunksize=None)
Описание основных параметров:
| Параметр | Type | Описание |
|---|---|---|
sql |
str | SQL-запрос или имя таблицы (связано сconхранения) |
con |
Объект подключения | Объект engine SQLAlchemy или объект соединения DB-API |
index_col |
str или list | Устанавливает указанный столбец как индекс строк DataFrame |
params |
list или dict | Значения параметров для параметризованных SQL-запросов, предотвращающие SQL-инъекции |
parse_dates |
list или dict | Разбирает указанный столбец как тип datetime |
chunksize |
int | Читает фрагментами, возвращая итератор объектов DataFrame с указанным количеством строк в каждом фрагменте |
1. Чтение всей таблицы
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Прочитать всю таблицу employees
df = pd.read_sql("employees", con=engine)
print(df.head())
print(f"Всего {len(df)} строк, {len(df.columns)} столбцов")
2. Выполнение SQL-запросов
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Запрос с условиями
df = pd.read_sql("SELECT * FROM employees WHERE department = 'IT'", con=engine)
# Запрос соединения нескольких таблиц
sql = """
SELECT e.name, e.salary, d.department_name
FROM employees e
JOIN departments d ON e.dept_id = d.id
WHERE e.salary > 10000
ORDER BY e.salary DESC
"""
df = pd.read_sql(sql, con=engine)
print(df.head(10))
3. Параметризованные запросы (предотвращение SQL-инъекций)
Когда условия запроса поступают от пользовательского ввода,всегда используйте параметризованные запросыи не используйте конкатенацию строк для SQL:
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# ❌ Опасный подход: конкатенация строк SQL, риск SQL-инъекции
dept = "IT"
# df = pd.read_sql(f"SELECT * FROM employees WHERE department = '{dept}'", engine)
# ✅ Безопасный подход: используйте ? placeholders (sqlite3) или именованные параметры :name (SQLAlchemy)
# Стиль SQLite / DB-API (с использованием ?)
df = pd.read_sql(
"SELECT * FROM employees WHERE department = ? AND salary > ?",
con=engine,
params=["IT", 8000] # params — это список, подставляющий ? по позиции
)
# Стиль именованных параметров SQLAlchemy (с использованием :param_name)
from sqlalchemy import text
with engine.connect() as conn:
df = pd.read_sql(
text("SELECT * FROM employees WHERE department = :dept AND salary > :min_salary"),
con=conn,
params={"dept": "IT", "min_salary": 8000}
)
print(df)
4. Установка столбца индекса
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Установить столбец id из базы данных в качестве индекса строк DataFrame
df = pd.read_sql("SELECT * FROM employees", con=engine, index_col="id")
print(df.head())
# Использовать несколько столбцов в качестве составного индекса
df = pd.read_sql(
"SELECT * FROM orders",
con=engine,
index_col=["year", "month"] # Составной индекс
)
5. Парсинг столбцов с датами
Поля с датами, хранящиеся в базе данных, по умолчанию считываются как строки. С помощьюparse_datesпараметра их можно напрямую преобразовать вdatetime:
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Преобразовать столбцы created_at и updated_at в тип datetime
df = pd.read_sql(
"SELECT * FROM orders",
con=engine,
parse_dates=["created_at", "updated_at"]
)
print(df.dtypes)
# created_at datetime64[ns]
# updated_at datetime64[ns]
# Также можно указать формат разбора (для нестандартных форматов даты)
df = pd.read_sql(
"SELECT * FROM orders",
con=engine,
parse_dates={"created_at": "%Y%m%d"} # Разобрать формат вроде "20240115" как дату
)
Чтение больших данных по частям (chunksize)
Когда таблица базы данных содержит очень большой объём данных, чтение всего сразу в память может вызвать OOM (out of memory). Используяchunksizeпараметр, данные можно читать пакетами, обрабатывая только часть за раз:
Пример
from sqlalchemy import create_engine
engine = create_engine("mysql+pymysql://root:password@localhost/bigdata")
# chunksize=10000 означает чтение 10000 строк за раз и возврат итератора
chunks = pd.read_sql("SELECT * FROM large_table", con=engine, chunksize=10000)
# Обрабатывать порцию за порцией (в памяти одновременно только 10000 строк)
result_list = []
for i, chunk in enumerate(chunks):
# Применить логику обработки к каждому чанку (например, фильтрация, агрегация и т. д.)
processed = chunk[chunk["status"] == "active"]
result_list.append(processed)
print(f"Обработан чанк {i+1}, корректных строк в текущем чанке: {len(processed)}")
# Объединить все обработанные чанки в один DataFrame
final_df = pd.concat(result_list, ignore_index=True)
print(f"Итоговое количество корректных строк данных: {len(final_df)}")
pd.read_sql_query() и pd.read_sql_table()
pd.read_sql_query(): Выполняет только запросы
иpd.read_sql()Функциональность в основном такая же, но он принимает только SQL-запросы, а не имена таблиц. Параметры точно такие же, что подходит для сценариев, где нужно чётко различать операции "запрос" и "чтение таблицы":
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
df = pd.read_sql_query(
"SELECT name, salary FROM employees WHERE salary > 5000",
con=engine
)
print(df)
pd.read_sql_table(): Читает всю таблицу (только SQLAlchemy)
pd.read_sql_table()Специально используется для чтения всей таблицы, с поддержкой фильтрации столбцов и строк через параметры, ноподдерживает только подключения через движок SQLAlchemyи не поддерживает нативные подключения, такие как sqlite3:
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Читать указанные столбцы из таблицы employees
df = pd.read_sql_table(
"employees",
con=engine,
columns=["id", "name", "salary", "department"] # Читать только эти столбцы
)
# Также поддерживает указание schema (режим в базе данных)
df = pd.read_sql_table(
"employees",
con=engine,
schema="hr" # Читать таблицу employees в схеме hr
)
print(df.head())
Запись DataFrame в базу данных (to_sql)
ИспользованиеDataFrame.to_sql()Вы можете записать DataFrame в базу данных, с поддержкой создания новой таблицы, добавления данных или перезаписи исходной таблицы:
Базовый синтаксис
DataFrame.to_sql(name, con, schema=None, if_exists='fail', index=True, index_label=None, chunksize=None, dtype=None, method=None)
if_existsПараметр определяет поведение, когда целевая таблица уже существует:
| Значение if_exists | Поведение | Сценарий использования |
|---|---|---|
'fail'(По умолчанию) |
Вызывает ошибку, если таблица уже существует | Предотвращает случайную перезапись существующих данных |
'replace' |
Сначала удаляет исходную таблицу, затем заново создаёт таблицу и записывает в неё | Полное обновление данных |
'append' |
Добавляет данные в существующую таблицу, не изменяя структуру таблицы | Инкрементально записывает новые данные |
Пример
from sqlalchemy import create_engine
engine = create_engine("sqlite:///mydata.db")
# Подготовка примера данных
df = pd.DataFrame({
"name": ["Чжан Сань", "Ли Сы", "Ван У"],
"department": ["IT", "HR", "IT"],
"salary": [12000, 8000, 15000]
})
# Записать DataFrame в таблицу employees; заменить её, если она уже существует
df.to_sql(
"employees",
con=engine,
if_exists="replace", # Перезаписать исходные данные
index=False # Не записывать индекс строк DataFrame в базу данных (обычно не требуется)
)
# Добавить новые данные в существующую таблицу (примечание: имена столбцов и типы данных должны совпадать)
new_employees = pd.DataFrame({
"name": ["Zhao Liu"],
"department": ["Finance"],
"salary": [11000]
})
new_employees.to_sql("employees", con=engine, if_exists="append", index=False)
# Проверить результат записи
result = pd.read_sql("SELECT * FROM employees", con=engine)
print(result)
Указание типов данных столбцов
При записи можно использоватьdtypeпараметр, чтобы явно указать тип каждого столбца в базе данных:
Пример
from sqlalchemy import create_engine, Integer, String, Float, DateTime
engine = create_engine("sqlite:///mydata.db")
df = pd.DataFrame({
"id": [1, 2, 3],
"name": ["Alice", "Bob", "Charlie"],
"score": [92.5, 88.0, 95.3],
})
df.to_sql(
"students",
con=engine,
if_exists="replace",
index=False,
dtype={
"id": Integer(), # Тип Integer
"name": String(50), # Строка переменной длины, максимум 50 символов
"score": Float() # Число с плавающей точкой
}
)
Полный пример использования
Ниже приведён полный пример чтения данных о продажах из базы данных SQLite и их анализа:
Пример
import sqlite3
from sqlalchemy import create_engine
# Шаг 1: Подготовка тестовой базы данных
engine = create_engine("sqlite:///sales.db")
conn = sqlite3.connect("sales.db")
# Создать образец данных и записать его в базу данных
sales_data = pd.DataFrame({
"order_id": [1001, 1002, 1003, 1004, 1005, 1006],
"product": ["Laptop", "Phone", "Tablet", "Laptop", "Phone", "Tablet"],
"amount": [6999, 3999, 2999, 7299, 4299, 3199],
"quantity": [2, 5, 3, 1, 4, 2],
"order_date": ["2024-01-10", "2024-01-15", "2024-01-20",
"2024-02-05", "2024-02-10", "2024-02-18"]
})
sales_data.to_sql("sales", con=engine, if_exists="replace", index=False)
# Шаг 2: Прочитать данные и разобрать даты
df = pd.read_sql(
"SELECT * FROM sales",
con=engine,
parse_dates=["order_date"]
)
print("Исходные данные:")
print(df)
print()
# Шаг 3: Рассчитать продажи по продуктам
sql_agg = """
SELECT product,
COUNT(*) AS order_count,
SUM(amount * quantity) AS total_sales,
AVG(amount) AS avg_unit_price
FROM sales
GROUP BY product
ORDER BY total_sales DESC
"""
summary = pd.read_sql(sql_agg, con=engine)
print("Сводка продаж по продуктам:")
print(summary)
print()
# Шаг 4: Записать сводные результаты обратно в базу данных
summary.to_sql("sales_summary", con=engine, if_exists="replace", index=False)
print("Сводные данные записаны в таблицу sales_summary")
conn.close()
Результат выполнения приведённого выше кода:
原始数据: order_id product amount quantity order_date 0 1001 笔记本 6999 2 2024-01-10 1 1002 手机 3999 5 2024-01-15 2 1003 平板 2999 3 2024-01-20 3 1004 笔记本 7299 1 2024-02-05 4 1005 手机 4299 4 2024-02-10 5 1006 平板 3199 2 2024-02-18 各产品销售汇总: product 订单数 总销售额 平均单价 0 手机 2 36991 4149.0 1 笔记本 2 21297 7149.0 2 平板 2 15395 3099.0 汇总数据已写入 sales_summary 表
Типичные проблемы и примечания
1. Соединение необходимо закрывать после использования
При использовании нативного соединения DB-API (например, sqlite3) необходимо вручную закрыть соединение после операции. Рекомендуется использоватьwithоператор для автоматического управления им:
Пример
import pandas as pd
# Используйте оператор with; соединение автоматически закрывается при выходе, и не будет утечки, даже если возникнет исключение
with sqlite3.connect("mydata.db") as conn:
df = pd.read_sql("SELECT * FROM employees", con=conn)
print(df) # Соединение было автоматически закрыто, но данные df всё ещё доступны
2. Не читайте все данные из большой таблицы напрямую
Для больших таблиц с миллионами строк или более прямоеSELECT *чтение всех данных исчерпает память. Следует в первую очередь фильтровать данные на уровне SQL (WHERE, LIMIT) или использоватьchunksizeчтение по частям.
3. read_sql_table поддерживает только SQLAlchemy
Если вы используете собственное соединение DB-API, например sqlite3, чтобы вызвать егоpd.read_sql_table(), будет вызвана ошибкаNotImplementedError. Пожалуйста, используйтеpd.read_sql()или переключитесь на движок SQLAlchemy.
4. Параметр index у to_sql по умолчанию имеет значение True
По умолчаниюto_sqlиндекс строк DataFrame (0, 1, 2...) также записывается в базу данных, создавая столбец с именемindexcolumn, который обычно избыточен. Рекомендуется явно передаватьindex=False。
5. Драйверы баз данных нужно устанавливать отдельно
Когда SQLAlchemy подключается к разным базам данных, также необходимо установить соответствующий пакет драйвера:
| Базы данных | Пакет драйвера | Команда установки |
|---|---|---|
| MySQL | PyMySQL | pip install pymysql |
| PostgreSQL | psycopg2 | pip install psycopg2-binary |
| SQL Server | pyodbc | pip install pyodbc |
| Oracle | cx_Oracle | pip install cx_Oracle |
| SQLite | Встроенный | Установка не требуется |