Я кодирую веб-сайт в PHP / MySQL, и я бы хотел реализовать аналогичный механизм тегов stackoverflow. У меня есть 3 релевантные таблицы в БД: 1. Пункты 2. Метки 3. ItemTagMap (сопоставляет теги с элементами, n: n mapping)
Теперь на странице поиска я хотел бы показать отличный список всех тегов для всего результата поиска (а не только для текущей страницы), чтобы пользователи могли «уточнить» их поиск, добавив / удалив теги из этого списка тегов.
Вопрос в том, что это довольно тяжелый запрос в БД, и может быть множество запросов на поиск, которые приводят к различным наборам результатов и, следовательно, к различным наборам тегов.
Кто-нибудь знает, как эффективно реализовать это?
Прежде чем перейти в режим преждевременной оптимизации , может оказаться полезным изучить следующий шаблон запроса. Если ничто иное не может быть использовано в качестве базовой линии, по которой можно измерить эффективность возможных оптимизаций.
SELECT T.Tagid, TagInfo.TagName, COUNT(*) FROM Items I JOIN Tags TagInfo ON TagInfo.TagId = T.TagId JOIN ItemTagMap T ON I.ItemId = T.ItemId --JOIN ItemTagMap T1 ON I.ItemId = T1.ItemId WHERE I.ItemId IN ( SELECT ItemId FROM Items WHERE -- Some typical initial search criteria Title LIKE 'Bug Report%' -- Or some fulltext filter instead... AND ItemDate > '02/22/2008' AND Status = 'C' ) --AND T1.TagId = 'MySql' GROUP BY T.TagId, TagInfo.TagName ORDER BY COUNT(*) DESC
Подзапрос – это «управляющий запрос», то есть тот, который соответствует исходным критериям конечного пользователя. (см. ниже подробную информацию о том, как этот запрос, требуемый несколько раз, может вписываться в общий оптимизированный поток). Комментируется JOIN на T1 (и, возможно, T2, T3, когда выбрано несколько тегов), и в соответствии с предложением WHERE связанный критерии. Они необходимы, когда пользователь выбирает конкретный тег, независимо от того, является ли он частью первоначального поиска или уточнением. (Возможно, более эффективно разместить эти объединения и где предложения в подзапросе, более подробно об этом ниже)
Обсуждение … «Водительский запрос» или его вариация необходимы для двух различных целей:
Обратите внимание, что полный список не нужно сортировать (или это может быть полезно для сортировки в другом порядке), в соответствии с которым второй список нужно сортировать по выбору пользователя (например, по дате, по убыванию или по заголовку, по алфавиту по возрастанию ). Также обратите внимание, что если требуется какой-либо порядок сортировки, стоимость запроса будет подразумевать полный список (застенчивая нечетная оптимизация самим SQL и / или некоторая денормализация, SQL должен «видеть» последние записи в этом списке , если они принадлежат к вершине, сортируются).
Этот последний факт заключается в том, что он имеет одинаковый запрос для обеих целей, соответствующий список может храниться во временной таблице. Общий поток будет состоять в том, чтобы быстро найти верхние записи N элементов с их деталями и сразу вернуть их в приложение. Затем приложение может получить ajax-fashion список тегов для уточнений. Этот список будет производить с запросом, похожим на предыдущий, где подзапрос заменяется на «select * from temporTable». Скорее всего, оптимизатор SQL решит отсортировать этот список (в некоторых случаях), давайте сделаем это, а не второй, угадаем его и не будем сортировать его явно.
Еще один момент, который следует учитывать, – это, возможно, привести соединение (и) в таблицу ItemTagMap внутри «вождения запроса», а не как показано выше. Вероятно, лучше всего это сделать, как для производительности, так и потому, что он будет создавать правильный список для цели №2 (отображение страницы элементов).
Вышеупомянутый запрос / поток, скорее всего, будет достаточно хорошо масштабироваться даже на относительно скромном оборудовании; ориентировочно в 1/2 миллиона + предметов, с постоянным поиском пользователей может быть до 10 в секунду. Одним из ключевых факторов будет избирательность исходных критериев поиска.
Идеи оптимизации
– сказал он! –
Соответствующая архитектура и оптимизация должны выбираться с учетом фактических требований и эффективного статистического профиля данных …
Вы хотите попытаться свести к минимуму количество вызовов БД, положив тяжелую работу на PHP.
Сначала выберите все элементы из базы данных:
select * from items where (conditions);
Затем создайте массив всех идентификаторов из набора результатов.
$ids = array(); foreach ($items as $item) { $ids[] = $item['id']; } $ids = implode(',' $ids);
Затем выберите все ItemTagMaps и связанные данные тега для ранее найденного идентификатора элемента.
select map.item_id, t.id, t.name from tags t, item_tag_maps map where t.id = map.tag_id and map.item_id in ($ids);
Теперь, когда вы зацикливаете свой массив $ items, вы можете найти все соответствующие теги из второго SQL-запроса, который вы выполнили, если у него есть соответствующее значение item_id.
Предполагая, что:
тогда:
SELECT t.name FROM Tag t WHERE EXISTS (SELECT 1 FROM ItemTag WHERE item_id = 1234) ORDER BY t.name
Ничего особенного в этом нет. Это похоже, но я предполагаю, что это будет медленнее:
SELECT t.name FROM Tag t WHERE t.id IN (SELECT tag_id FROM ItemTag WHERE item_id = 1234) ORDER BY t.name
Это может быть сделано как соединение:
SELECT DISTINCT t.name FROM Tag t JOIN ItemTag i WHERE i.tag_id = t.id WHERE i.item_id = 1234 ORDER BY t.name
Я думаю, что первый будет быстрее, но, как всегда, с SQL, стоит проверить (на достаточно большом наборе данных).
Вышеуказанные были сделаны для перечисления тегов для одного элемента. Вы хотите создать комбинированный набор тегов для результатов поиска. Это не сложно из вышеизложенного, но это зависит от того, как вы получаете результаты поиска.