Как вывести магазины, привязанные к категории товаров, в 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 в функцию формирования клавиатуры. Это решение легко масштабируется: вы можете добавить дополнительные фильтры (по цене, рейтингу) простым расширением запроса. Надеемся, эта статья помогла вам разобраться с задачей.

    Часто задаваемые вопросы