Text-to-SQL: как задавать вопросы базе данных обычным языком
Практика4 минРедакция Innovialabs
Коротко
LLM может превращать вопрос «сколько заказов было в марте по филиалам» в SQL, но работает это надёжно только с описанной схемой, отдельным read-only пользователем, проверкой запроса перед запуском и показом самого SQL рядом с ответом.
Как это работает
Text-to-SQL — это схема, в которой пользователь пишет вопрос обычным языком, языковая модель составляет SQL-запрос, система выполняет его в базе и возвращает таблицу или короткий ответ.
Цепочка выглядит так:
- Пользователь спрашивает: «Какие пять товаров продавались лучше всего в прошлом месяце?»
- Бэкенд добавляет к вопросу описание схемы базы и правила.
- LLM возвращает SQL.
- Код проверяет запрос и запускает его от пользователя с правами только на чтение.
- Результат показывается вместе с текстом запроса, чтобы его можно было перепроверить.
Модель не «знает» ваши данные. Она знает только то, что вы ей передали в контексте, поэтому качество почти целиком зависит от подготовки.
Контекст схемы: главное, что влияет на точность
Если отдать модели голый CREATE TABLE, она будет угадывать смысл колонок. Угадывает она часто неверно: status = 3 для неё ничего не значит.
Что стоит передавать:
- Только нужные таблицы. Для вопросов о продажах не нужны таблицы логов и настроек. Лишние таблицы путают модель и тратят токены.
- Описания колонок человеческим языком. «
amount— сумма заказа в сумах с НДС», «deleted_atне пустой — заказ удалён». - Значения справочников. Какие статусы бывают и что они означают.
- Связи между таблицами — какие ключи с чем соединяются.
- Бизнес-определения. Что считается «выручкой», «активным клиентом», «прошлым месяцем» — по дате создания или по дате оплаты.
- Несколько примеров «вопрос → правильный SQL» для типичных запросов.
Хороший приём — сделать для аналитики представления (views) с понятными именами и уже применёнными фильтрами: v_paid_orders вместо сырых таблиц с десятком условий. Модели проще, ошибок меньше.
Безопасность: только чтение и ограничения
Сгенерированный SQL — это код от непредсказуемого источника. Относитесь к нему соответственно.
- Отдельный пользователь БД с правами только
SELECTи только на разрешённые таблицы или представления. Это главная защита, а не промпт «не пиши DELETE». - Реплика вместо основной базы, если она есть, чтобы тяжёлый запрос не замедлил рабочую систему.
- Таймаут на выполнение и лимит строк в результате.
- Скрытие персональных данных. Если менеджерам не нужны телефоны клиентов, их не должно быть в доступных представлениях.
- Журнал запросов: кто спросил, какой SQL получился, сколько он выполнялся.
Проверка запроса перед запуском
Перед выполнением запрос стоит пропустить через автоматические проверки:
- Разобрать SQL парсером и убедиться, что это ровно один оператор
SELECT(илиWITH ... SELECT). - Проверить, что используются только разрешённые таблицы.
- Добавить
LIMIT, если его нет. - Выполнить
EXPLAINи отклонить запрос, если план слишком тяжёлый.
import sqlglot
def validate(sql: str, allowed: set[str]) -> str:
statements = sqlglot.parse(sql, read="postgres")
if len(statements) != 1:
raise ValueError("Ожидается один запрос")
tree = statements[0]
if tree.key not in ("select", "union"):
raise ValueError("Разрешён только SELECT")
tables = {t.name for t in tree.find_all(sqlglot.exp.Table)}
if not tables <= allowed:
raise ValueError(f"Недоступные таблицы: {tables - allowed}")
return tree.sql(dialect="postgres")
Если запрос упал с ошибкой, можно один-два раза вернуть модели текст ошибки и попросить исправить. Бесконечные повторы не нужны.
Как ловить неправильные ответы
Опаснее всего не ошибка выполнения, а правдоподобный неверный ответ: запрос отработал, число выглядит нормально, но посчитано не то.
Что помогает:
- Показывать SQL рядом с ответом и коротко пересказывать его смысл: «посчитаны оплаченные заказы с 1 по 31 марта».
- Набор контрольных вопросов с заранее известными ответами. Прогоняйте его после каждого изменения промпта, схемы или модели.
- Просить уточнение, если вопрос неоднозначен: «выручка с НДС или без?» лучше, чем молчаливый выбор.
- Сверка с отчётами. Ключевые метрики сравнивайте с тем, что уже считает BI или бухгалтерия.
- Кнопка «ответ неверный», чтобы собирать сложные случаи и добавлять их в примеры.
Типичные ошибки
- Дать модели доступ к основной базе с правами записи.
- Передать всю схему из сотен таблиц без описаний.
- Не определить бизнес-термины, из-за чего «клиент» и «выручка» считаются по-разному в разных ответах.
- Показывать только число без запроса — проверить его потом невозможно.
- Ожидать, что Text-to-SQL заменит аналитика на сложных вопросах с многоэтапной логикой.