2009-09-02 6 views
5

Я раньше не делал поиск с MYSQL, но мне нужно реализовать поиск. У меня есть три таблицы: «статьи», «articles_tags» и «теги».MySQL Полный текстовый логический поиск с тегами

«статьи» содержит первое, что я хотел бы найти, поле «название».

«articles_tags» - сводная таблица, которая связывает «статьи» и «теги» вместе. «articles_tags» имеет два поля: «articles_id» и «tag_id».

«теги» содержит вторую вещь, которую я хотел бы найти, поле «имя».

Моя проблема: мне нужен способ поиска в поле «название» и каждый из тегов, относящихся к этой статье («tags.name»), и возвращать релевантность (или сортировать по релевантности) для статьи.

Что было бы хорошим способом реализовать это? Я уверен, что это не может быть сделано только из одного запроса, поэтому два запроса, а затем «смешение» релевантности вместе, будут в порядке.

Спасибо.

Редактировать: Забыл сказать, если бы я мог дать больше веса для соответствия тегу, чем соответствовать слову в названии, это было бы замечательно. Я не прошу кого-нибудь написать это, но дайте мне какое-то направление. Я немного новичок в PHP и MySQL.

ответ

0

Смешной это третий вопрос о практически той же проблемы я вижу в 2-х дней, проверьте эти два сообщения: 1, 2

+0

Я смотрел на этих двоих, но не видеть, как они соотносятся с моим проблема. – 2009-09-02 03:08:58

+2

Самое забавное, что это действительно комментарий, а не ответ. – TheCarver

0

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

SELECT * FROM 
(SELECT a.id, a.title, 
    MATCH (a.title) AGAINST ('$s_search_term') AS title_score, 
    SUM(MATCH (t.name) AGAINST ('$s_search_term') 
) AS tag_score 
FROM articles AS a 
LEFT JOIN articles_tags AS at 
    ON a.id = at.article_id 
LEFT JOIN tags AS t 
    ON t.id = at.tag_id 
WHERE MATCH (a.title) AGAINST ('$s_search_term') 
    OR MATCH (t.name) AGAINST ('$s_search_term') 
GROUP BY a.id) AS table1 
ORDER BY 2*tag_score + title_score DESC 

Возможно, вы захотите нормализовать tag_score, разделив его на COUNT (t.id). Извините, но легче дать запрос, чем объяснить, как это сделать.

2

Начиная с ответа, данного @ james.c.funk, но внося некоторые изменения.

SELECT a.id, a.title, 
    MATCH (a.title) AGAINST (?) AS relevance 
FROM articles AS a 
LEFT OUTER JOIN (articles_tags AS at 
    JOIN tags AS t ON (t.id = at.tag_id AND t.name = ?)) 
    ON (a.id = at.article_id) 
WHERE MATCH (a.title) AGAINST (? IN BOOLEAN MODE) 
ORDER BY IF(t.name IS NOT NULL, 1.0, relevance) DESC; 

Я предполагаю, что вы хотите, чтобы совпадения тегов совпадали с полной строкой, а не с использованием полнотекстового поиска.

Также используется одно левое внешнее соединение вместо двух, потому что если соединение с articles_tags выполнено, то, несомненно, есть тег. Поместите сравнение имен тегов в условие соединения, а не в предложении WHERE.

Логический режим MATCH() возвращает 1.0 на матч, что делает его бесполезным как показатель значимости. Так что сделайте дополнительное сравнение в списке выбора, чтобы рассчитать релевантность. Это значение находится между 0.0 и 1.0. Теперь мы можем сделать сортировку тегов выше, рассматривая ее как имеющую значение 1.0.

+0

Привет, Билл. Я читал в нескольких местах, что использование JOIN с FULLTEXT является плохим, поскольку он заставляет MySQL запускать полное сканирование таблицы и потерять ценную производительность. Я сейчас проведу тест, чтобы узнать, правда ли то, что я прочитал. – TheCarver

+0

@PaparazzoKid, зависит от того, к какой таблице обращаются в первую очередь. MySQL соединяется с использованием алгоритма вложенных циклов, поэтому, если вы используете FULLTEXT для ограничения количества строк, сопоставленных в первой таблице, используйте это для поиска строк в объединенной таблице, это не должно быть проблемой. Но если вы сначала сканируете другую таблицу таблицы, используйте FULLTEXT в условии соединения или, что еще хуже, используйте столбец * первой таблицы в качестве шаблона для поиска в поиске FULLTEXT (я не знаю, если это даже возможно), тогда это было бы дорого. Возможно, вам придется использовать STRAIGHT_JOIN. –

1

Вот как я сделал это в прошлом. Он выглядит медленным, но я думаю, вы найдете его нет.

Я добавил немного сложности, чтобы показать, что еще можно легко сделать.В этом примере статья получит 1 очко для частичного совпадения, 2 балла для частичного совпадения тегов, 3 балла за точный совпадение тегов и 4 балла за точный совпадение. Затем он добавляет их вверх и сортирует по счету.

SELECT 
    a.*, 
    SUM(
    CASE WHEN a.title LIKE '%keyword%' THEN 1 ELSE 0 END 
    + 
    CASE WHEN t.name LIKE '%keyword%' THEN 2 ELSE 0 END 
    + 
    CASE WHEN t.name = 'keyword' THEN 3 ELSE 0 END 
    + 
    CASE WHEN a.title = 'keyword' THEN 4 ELSE END 
) AS score 
FROM article a, articles_tags at, tags t 
WHERE a.id = at.article_id 
AND at.tag_id=t.id 
AND (a.title LIKE '%keyword%' OR t.name LIKE '%keyword%') 
GROUP BY a.id 
ORDER BY score; 

ПРИМЕЧАНИЯ: Это не вернет статьи без тегов. Я использовал простые объединения, чтобы уменьшить шум в запросе и выделить только то, что делает подсчет очков. Чтобы включить статьи без тэгов, просто добавьте соединения слева.

2

Стоит ли в это время рекомендовать, чтобы вы посмотрели на разгрузку задания поиска на то, что на самом деле написано именно для этой цели?

В наших продуктах мы используем MySQL для хранения данных, но индексируем все наши данные с помощью Lucene (через Solr - но это не имеет значения).

Это стоит взглянуть на него, потому что его относительно просто настроить, он очень мощный, и это намного проще, чем пытаться манипулировать базой данных, делая то, что вы хотите.

К сожалению, это не прямой ответ на этот вопрос, я просто чувствую, что такого рода вещи всегда стоит упомянуть в этом сценарии :)

+1

как вы держите mysql и lucene в синхронизации? спасибо –

Смежные вопросы