Мира · маркетолог магазина настолок «Кубик»
Каждую неделю прошу у вас выручку по промокодам и каждый раз жду. Можно я сама буду вводить промокод и даты, а отчёт соберётся сам? Разработчики говорят, что прошлый такой скрипт упал, когда кто-то ввёл промокод с кавычкой. Это правда опасно?
В прошлом уроке данные приезжали в Python CSV-файлом: кто-то выгрузил, кто-то прислал. Так работает разовый анализ, а для отчёта каждую неделю это лишний шаг. Python умеет сам отправить SQL в базу и получить ответ сразу в DataFrame. Сегодня подключаемся к базе, пишем запрос из кода, передаём в него значения из формы Миры и сохраняем результат в файл.
Python звонит базе
Представь телефон. Чтобы спросить склад, сколько коробок на полке, нужен аппарат, который знает его номер и язык. В Python таким аппаратом служит драйвер, а открытый звонок называется соединением. Ты набираешь номер один раз, задаёшь по нему сколько угодно запросов и кладёшь трубку.
На работе аналитик звонит в PostgreSQL через psycopg, в ClickHouse через clickhouse-connect или в любую базу через SQLAlchemy:
# на работе, не в раннере курса
from sqlalchemy import create_engine
import pandas as pd
engine = create_engine("postgresql+psycopg://analyst@db.kubik.local/shop")
pd.read_sql_query("SELECT count(*) FROM orders", engine)
pd.read_sql_query одинаков для всех баз: отдаёшь запрос и соединение, получаешь DataFrame. Пароль в код не пишут: блокнот уедет в Git, а пароль вместе с ним. Его берут из переменной окружения или из настроек драйвера. У ClickHouse есть свой короткий путь, client.query_df(...) из clickhouse-connect, но результат тот же.
В раннере курса нет ни этих библиотек, ни сети. Зато в стандартную библиотеку Python встроен sqlite3, драйвер SQLite, базы в одном файле или вовсе в памяти. На ней и потренируемся: соединение, запрос и параметры работают так же, как с PostgreSQL.
База в памяти из CSV
Возьмём выгрузку «Кубика» за первое полугодие 2026 года: 2699 заказов, 4820 позиций и 11 промокодов. Сложим её в базу:
import sqlite3
import pandas as pd
conn = sqlite3.connect(":memory:")
orders = pd.read_csv("orders.csv", parse_dates=["created_at"])
orders.to_sql("orders", conn, index=False)
pd.read_csv("order_items.csv").to_sql("order_items", conn, index=False)
pd.read_csv("promo_codes.csv").to_sql("promo_codes", conn, index=False)
":memory:" вместо имени файла значит, что база живёт в памяти, пока открыто соединение. to_sql создаёт таблицу и вставляет в неё строки DataFrame, а index=False не даёт индексу pandas стать лишним столбцом. Типы столбцов pandas подберёт сам: числа станут числами, а даты текстом. Своего типа для дат у SQLite нет.
read_sql_query: ответ сразу в DataFrame
Теперь заказы по месяцам, тем же SQL, что в курсе:
pd.read_sql_query("""
SELECT strftime('%Y-%m', created_at) AS month,
count(*) AS orders,
count(*) FILTER (WHERE status = 'cancelled') AS cancelled
FROM orders
GROUP BY month
ORDER BY month
""", conn)
month orders cancelled
0 2026-01 443 16
1 2026-02 424 26
2 2026-03 473 30
3 2026-04 456 29
4 2026-05 483 29
5 2026-06 420 23
Те же числа даёт PostgreSQL на базе boardshop. Считает база, а в Python приезжают шесть строк вместо 2699. Это разделение труда из урока 65: сворачиваем в базе, крутим в pandas.
Рядом живёт pd.read_sql: он смотрит, что ему дали. Запрос передаёт в read_sql_query, а имя таблицы в read_sql_table, которому нужен SQLAlchemy. С sqlite3 проще сразу писать read_sql_query. Без pandas запрос тоже работает: conn.execute(query).fetchall() вернёт список кортежей.
урок из курса
Урок из курса «Аналитика данных с нуля». Курс покупается один раз и остаётся навсегда: 500 ₽.
Оплата пока не подключена: премиум и курсы открываются промокодом.