Создание поиска по товарам в каталоге: от простого запроса до обработки результатов

Раздел: Веб-разработка -> Реализация поиска

Реализация поиска в каталоге на PHP

Полнотекстовый поиск MySQL (FULLTEXT) - эффективное решение для средних каталогов

Основной рекомендуемый способ организации поиска в каталоге товаров или статей - использование полнотекстового индекса MySQL с оператором MATCH AGAINST. Этот подход позволяет выполнять быстрый поиск по текстовым полям, учитывая релевантность результатов, игнорируя стоп-слова и поддерживая естественный язык.

Как реализовать полнотекстовый поиск в MySQL для каталога PHP?

Для начала необходимо создать полнотекстовый индекс на колонке (или нескольких колонках) таблицы. Например, таблица products с полями name и description:

CREATE TABLE products (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    description TEXT,
    price DECIMAL(10,2),
    FULLTEXT (name, description)
) ENGINE=InnoDB;

Search tags php tag (поиск по тегам в php)

Далее в PHP-скрипте выполняется запрос с подготовленным выражением:

$search = $_GET['q'] ?? '';
$stmt = $pdo->prepare("SELECT *, MATCH(name, description) AGAINST(:query IN NATURAL LANGUAGE MODE) AS relevance 
                       FROM products 
                       WHERE MATCH(name, description) AGAINST(:query IN NATURAL LANGUAGE MODE) 
                       ORDER BY relevance DESC 
                       LIMIT 20");
$stmt->execute(['query' => $search]);
$results = $stmt->fetchAll();

Search topic php (поиск по теме в php)

При выводе результатов можно подсветить совпадения функцией highlight (реализация приведена ниже).

Типичная ошибка: забыть создать индекс или использовать неправильный движок (MyISAM по умолчанию поддерживает FULLTEXT, InnoDB с MySQL 5.6+ тоже). Если индекс отсутствует, запрос будет выполняться как обычное сканирование таблицы.

Проблема с кириллицей: для корректной работы с русским языком необходимо установить правильную кодировку сортировки (например, utf8mb4_unicode_ci) и задать минимальную длину слова (по умолчанию 4 символа). Короткие слова игнорируются. Для изменения минимальной длины нужно настроить параметр ft_min_word_len в MySQL.

Вариант 1: Оператор LIKE для простого поиска

Как выполнить поиск по частичному совпадению с помощью LIKE в PHP?

Оператор LIKE подходит для маленьких каталогов (до нескольких тысяч записей) и когда не требуется высокая производительность. Пример:

$search = $_GET['q'] ?? '';
$stmt = $pdo->prepare("SELECT * FROM products WHERE name LIKE :query OR description LIKE :query2");
$likeQuery = '%' . $search . '%';
$stmt->execute(['query' => $likeQuery, 'query2' => $likeQuery]);

Search type php id type (тип поиска по id в php)

Недостатки: медленный на больших таблицах, не учитывает релевантность, не работает со стоп-словами, подвержен SQL-инъекциям при неправильном использовании (но подготовленные запросы решают эту проблему).

Результат: список товаров, где в названии или описании встречается искомая строка.

Search php view (вид поиска в php)

Вариант 2: Регулярные выражения REGEXP

Когда и как применять REGEXP для гибкого поиска в MySQL?

REGEXP позволяет искать по сложным шаблонам, например, товары, название которых начинается с определенной буквы или содержит цифры. Пример поиска названий, начинающихся на "Ноут":

$stmt = $pdo->prepare("SELECT * FROM products WHERE name REGEXP :pattern");
$stmt->execute(['pattern' => '^Ноут']);

Catalog php search (поиск в каталоге php)

Но REGEXP не использует индексы, поэтому подходит только для маленьких таблиц или фильтрации уже выбранного набора.

Вариант 3: Самостоятельная разбивка запроса и несколько условий

Как реализовать поиск по каждому слову запроса отдельно на стороне PHP?

Пользователь может ввести несколько слов, и нужно найти товары, содержащие все эти слова (или любое из них). Пример для случая "все слова":

$search = $_GET['q'] ?? '';
$words = preg_split('/\s+/', $search);
$conditions = [];
$params = [];
foreach ($words as $i => $word) {
    $conditions[] = "(name LIKE :word{$i} OR description LIKE :desc{$i})";
    $params["word{$i}"] = '%' . $word . '%';
    $params["desc{$i}"] = '%' . $word . '%';
}
$where = implode(' AND ', $conditions);
$stmt = $pdo->prepare("SELECT * FROM products WHERE {$where}");
$stmt->execute($params);

Index php act search (действие поиска в php)

Этот вариант гибкий, но запрос может стать длинным, а производительность падает с ростом числа слов.

Вариант 4: Интеграция внешнего поискового движка (Elasticsearch, Sphinx)

В каких случаях стоит использовать Elasticsearch вместо встроенного поиска MySQL?

Для очень больших каталогов (сотни тысяч и миллионы записей) или при необходимости сложной фильтрации, фасетного поиска, автодополнения - подключается отдельный сервис. Пример отправки запроса через HTTP-клиент Guzzle:

use GuzzleHttp\Client;
$client = new Client(['base_uri' => 'http://localhost:9200']);
$response = $client->post('/products/_search', [
    'json' => [
        'query' => [
            'multi_match' => [
                'query' => $search,
                'fields' => ['name', 'description']
            ]
        ]
    ]
]);
$body = json_decode($response->getBody(), true);
$results = array_map(function($hit) { return $hit['_source']; }, $body['hits']['hits']);

Такой подход требует настройки индексации данных из MySQL в Elasticsearch (Logstash, Beats или вручную).

Общие проблемы и способы их решения

SQL-инъекции: всегда использовать подготовленные запросы или PDO. Никогда не конкатенировать пользовательский ввод напрямую.

Производительность: для таблиц более 100 тысяч записей LIKE и REGEXP становятся неприемлемо медленными. Необходимо либо использовать FULLTEXT индекс, либо внешние инструменты. Также важно иметь правильные индексы на поля, участвующие в поиске.

Релевантность: в FULLTEXT поиске по умолчанию используется бинарный режим, учитываются только слова длиннее 3-4 символов. Для русского языка может потребоваться настройка стемминга или использование внешнего анализатора.

Пагинация: при больших результатах используйте LIMIT и OFFSET, но для полнотекстового поиска лучше применять курсорную пагинацию (по последнему ID), если важна производительность на больших страницах.

Кодировка: убедитесь, что таблицы и соединение используют utf8mb4, иначе кириллица может не распознаваться.

- Search php search name (поиск по имени в php)
- Search index php subaction (поддействие поиска в php)
- Php search form (форма поиска в php)

Расширенные примеры реализации поиска

Пример 1. Полнотекстовый поиск с пагинацией и подсчетом общего количества

PHP-код с использованием PDO и передачей параметров LIMIT, OFFSET:

Пример
$page = max(1, (int)($_GET['page'] ?? 1));
$perPage = 10;
$offset = ($page - 1) * $perPage;
$search = $_GET['q'] ?? '';

// Подсчет результатов
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM products WHERE MATCH(name, description) AGAINST(:query IN NATURAL LANGUAGE MODE)");
$countStmt->execute(['query' => $search]);
$total = $countStmt->fetchColumn();
$totalPages = ceil($total / $perPage);

// Выборка с пагинацией
$stmt = $pdo->prepare("SELECT *, MATCH(name, description) AGAINST(:query IN NATURAL LANGUAGE MODE) AS rel 
                       FROM products 
                       WHERE MATCH(name, description) AGAINST(:query IN NATURAL LANGUAGE MODE) 
                       ORDER BY rel DESC 
                       LIMIT :limit OFFSET :offset");
$stmt->bindValue(':query', $search, PDO::PARAM_STR);
$stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
$stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
$stmt->execute();
$results = $stmt->fetchAll();

// Вывод
foreach ($results as $row) {
    echo "

" . highlight($row['name'], $search) . " - " . $row['price'] . " руб.

"; } echo "
Страница $page из $totalPages
";

Результат (вывод):

Товар: Ноутбук HP - 45000 руб.
Товар: Ноутбук Lenovo - 38000 руб.
Страница 1 из 3

Пример 2. Функция подсветки совпадений

Пример
function highlight($text, $search) {
    $words = preg_split('/\s+/', $search);
    foreach ($words as $word) {
        $text = preg_replace('/(' . preg_quote($word, '/') . ')/iu', '$1', $text);
    }
    return $text;
}

Использование: echo highlight($row['name'], $search);

Пример 3. Автодополнение (автокомплит) с помощью AJAX и FULLTEXT

PHP endpoint (autocomplete.php):

Пример
$search = $_GET['term'] ?? '';
$stmt = $pdo->prepare("SELECT DISTINCT name FROM products WHERE MATCH(name) AGAINST(:query IN BOOLEAN MODE) LIMIT 10");
$stmt->execute(['query' => $search . '*']);
$names = $stmt->fetchAll(PDO::FETCH_COLUMN);
echo json_encode($names);

JavaScript обрабатывает ответ и выводит подсказки. Результат:

["Ноутбук HP", "Ноутбук Lenovo", "Ноутбук Acer"]

Пример 4. Использование Sphinx Search для больших каталогов

Конфигурация Sphinx (sphinx.conf) может выглядеть так:

Пример
source products_source
{
    type = mysql
    sql_host = localhost
    sql_user = user
    sql_pass = pass
    sql_db = catalog
    sql_query = SELECT id, name, description FROM products
    sql_attr_uint = id
}
index products_index
{
    source = products_source
    path = /var/data/sphinx/products
    charset_table = 0..9, A..Z->a..z, a..z, U+410..U+44F->U+430..U+44F, U+430..U+44F
    min_word_len = 2
}

В PHP через API Sphinx (используя pecl/sphinx):

Пример
$s = new SphinxClient();
$s->SetServer('localhost', 9312);
$s->SetMatchMode(SPH_MATCH_EXTENDED2);
$result = $s->Query($search, 'products_index');
if ($result && $result['matches']) {
    $ids = array_keys($result['matches']);
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id IN (" . implode(',', $ids) . ")");
    $stmt->execute();
    $products = $stmt->fetchAll();
}

Это позволяет обрабатывать миллионы записей с высокой скоростью.

Поиск в каталоге PHP - comments

En
Catalog php search (php)