Как вывести магазины, привязанные к категории товаров, в Telegram боте на aiogram
При разработке Telegram-бота на библиотеке aiogram с базой данных SQLite часто возникает задача фильтрации данных. Например, у вас есть три таблицы: categories (кепки, футболки, кроссовки), places (магазины города) и items (товары с привязкой к магазинам). Пользователь выбирает категорию, и нужно показать только те магазины, в которых есть товары из этой категории. В этой статье разберём, как правильно организовать запрос к базе данных и изменить код бота.
Проблема: все магазины вместо привязанных
В исходном коде функция place() получает все записи из таблицы places без учёта выбранной категории. Это приводит к тому, что после выбора категории (например, «Кепки») пользователь видит все 6 магазинов, хотя товары этой категории есть только в двух. Решение - изменить SQL-запрос так, чтобы он выбирал только те магазины, которые связаны с конкретной категорией через таблицу items.
Структура базы данных для связи
Предположим, у вас есть три таблицы:
categories- id (PRIMARY KEY), name (текст)places- id (PRIMARY KEY), name (текст)items- id (PRIMARY KEY), name (текст), description (текст), price (число), category_id (FOREIGN KEY на categories.id), place_id (FOREIGN KEY на places.id)
Таблица items выступает связующим звеном: каждый товар принадлежит одной категории и одному магазину. Чтобы получить магазины для выбранной категории, нужно выполнить JOIN между items и places с фильтром по category_id.
Реализация: изменяем функцию получения магазинов
Вместо функции get_cat_place(), которая выбирает все магазины, создайте новую функцию, принимающую category_id. Пример на Python с использованием aiosqlite:
async def get_places_by_category(category_id: int):
query = '''
SELECT DISTINCT p.id, p.name
FROM places p
JOIN items i ON p.id = i.place_id
WHERE i.category_id = ?
'''
async with db.execute(query, (category_id,)) as cursor:
rows = await cursor.fetchall()
return [Place(id=row[0], name=row[1]) for row in rows]Теперь изменим функцию формирования клавиатуры place(category_id):
async def place(category_id: int):
all_places = await get_places_by_category(category_id)
keyboard = InlineKeyboardBuilder()
for place in all_places:
keyboard.add(InlineKeyboardButton(text=place.name, callback_data=f'place_{place.id}'))
return keyboard.adjust(1).as_markup()Обратите внимание: мы добавили параметр category_id. Этот идентификатор нужно передавать из обработчика, который вызывается при нажатии на кнопку категории. Например, в callback-обработчике category_{id} вы сохраняете выбранную категорию (в FSM или data) и затем вызываете place(category_id).
Пример полного цикла в боте
Допустим, у вас есть обработчик выбора категории:
@dp.callback_query(lambda c: c.data.startswith('category_'))
async def process_category(callback: CallbackQuery):
category_id = int(callback.data.split('_')[1])
# Сохраняем category_id, например, в FSMContext
await state.update_data(category_id=category_id)
# Получаем клавиатуру с магазинами для этой категории
markup = await place(category_id)
await callback.message.answer('Выберите магазин:', reply_markup=markup)Теперь после выбора категории «Кепки» пользователь увидит только те магазины, в которых есть товары из этой категории. Если в базе данных кепки продаются в двух магазинах - клавиатура покажет только эти два.
Проверка и отладка
Убедитесь, что в таблице items у каждого товара корректно заполнены поля category_id и place_id. Выполните тестовый SQL-запрос:
SELECT DISTINCT p.name
FROM places p
JOIN items i ON p.id = i.place_id
WHERE i.category_id = 1; -- 1 - ID категории 'Кепки'Запрос должен вернуть только два магазина. Если возвращаются все - проверьте связи в таблице items.
Заключение
Мы разобрали, как реализовать фильтрацию магазинов по категории товаров в Telegram-боте на aiogram с SQLite. Ключевой момент - использование JOIN в SQL-запросе и передача category_id в функцию формирования клавиатуры. Это решение легко масштабируется: вы можете добавить дополнительные фильтры (по цене, рейтингу) простым расширением запроса. Надеемся, эта статья помогла вам разобраться с задачей.