Раздел 42 · Аналитика данных с нуля

SQL из Python: sqlite3, read_sql и параметры запроса

middle~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

Мира · маркетолог магазина настолок «Кубик»

Каждую неделю прошу у вас выручку по промокодам и каждый раз жду. Можно я сама буду вводить промокод и даты, а отчёт соберётся сам? Разработчики говорят, что прошлый такой скрипт упал, когда кто-то ввёл промокод с кавычкой. Это правда опасно?

В прошлом уроке данные приезжали в 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 ₽.

Оплата пока не подключена: премиум и курсы открываются промокодом.