2009-09-26 4 views
2

Использование mysql и PHPКак реализовать поиск двух разных табличных данных?

Я уже использую предложения MATCH AGAINST.

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

Я хочу, чтобы иметь возможность искать и отображать результаты из разных таблиц на одной странице результатов.

Например, если я типа "Шоколадная одежда"

я может получить 4 результаты следующим образом:

Shop1 результат

ShopItem1 результат

ShopItem2 результат

Shop2 результат

и, конечно же, mos t соответствующие результаты должны быть ранжированы первыми.

У меня есть несколько вопросов. дизайн мудрый, а также реализация мудрый

1) должен ли я изменить свой дизайн? я думаю о наличии отдельной таблицы, называемой результатами поиска, которая будет содержать данные из обеих таблиц SHOPS и SHOPPRODUCTS. однако это означает, что у меня есть дублирование данных.

2) Должен ли я сохранить свой текущий дизайн? если да, то как я могу получить результаты поиска, отсортированные по релевантности в двух разных таблицах?

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

http://www.rottentomatoes.com/search/full_search.php?search=girl

ИЛИ, что на самом деле лучший выход?

Я надеюсь, что кто-то может дать мне руководство по этому виду esp, если у вас есть опыт в генерации результатов поиска через то, что может показаться несколькими таблицами.

, так как по требованию, я помещу структуры таблицы здесь

CREATE TABLE `shopitems` (
    `id` int(10) unsigned NOT NULL auto_increment, 
    `ShopID` int(10) unsigned NOT NULL, 
    `ImageID` int(10) unsigned NOT NULL, 
    `name` varchar(100) NOT NULL, 
    `description` varchar(255) NOT NULL, 
    `pricing` varchar(45) NOT NULL, 
    `datetime_created` datetime NOT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=MyISAM AUTO_INCREMENT=31 DEFAULT CHARSET=utf8; 

/*Table structure for table `shops` */ 

DROP TABLE IF EXISTS `shops`; 

CREATE TABLE `shops` (
    `id` int(11) NOT NULL auto_increment, 
    `title` varchar(100) default NULL, 
    `description` text, 
    `keywords` text, 
    `url` varchar(255) default '', 

    `owner_id` varchar(255) default NULL, 
    `datetime_created` datetime default NULL, 
    `created_by` varchar(255) default NULL, 
    `datetime_modified` datetime default NULL, 
    `modified_by` varchar(255) default NULL, 

    `overall_rating_avg` decimal(4,2) default '0.00', 


    PRIMARY KEY (`id`), 
    FULLTEXT KEY `url` (`url`), 
    FULLTEXT KEY `TitleDescFullText` (`keywords`,`title`,`description`,`url`) 
) ENGINE=MyISAM AUTO_INCREMENT=3051 DEFAULT CHARSET=utf8; 

я намерен искать через описание и название столбцы таблицы shopproducts.

, но, как вы можете видеть, оно еще не реализовано.

хотя поиск в магазинах уже запущен.

+2

Добавление структур таблицы поможет получить хороший ответ –

+0

привет, что вы подразумеваете под этим?вы имеете в виду, что у меня должна быть отдельная таблица с именем search_results, которая содержит все существующие данные и сопоставляет их только на основе этой таблицы? –

+0

Не было бы легче полагаться на полнотекстовый поиск на Sphinx или Xapian? Создание индекса с заданным интервалом и только поиск в нем значительно улучшат скорость поиска. – unexist

ответ

5

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

  • Все индексы в MySQL могут ссылаться только на столбцы в одной базовой таблице. Вы не можете сделать полнотекстовый индекс, который индексирует несколько таблиц.
  • Вы не можете определить индексы для представлений, а только базовые таблицы.
  • A MATCH() запрос к полнотекстовому индексу должен соответствовать всем столбцам в полнотекстовом индексе в порядке, указанном в индексе.

Я бы создал третью таблицу для хранения содержимого, которое вы хотите индексировать. Нет необходимости хранить этот контент избыточно - храните его исключительно в третьей таблице. Это заимствует концепцию «общего суперкласса» от объектно-ориентированного дизайна (поскольку мы можем применить его к дизайну РСУБД).

CREATE TABLE Searchable (
    `id` SERIAL PRIMARY KEY, 
    `title` varchar(100) default NULL, 
    `description` text, 
    `keywords` text, 
    `url` varchar(255) default '', 
    FULLTEXT KEY `TitleDescFullText` (`keywords`,`title`,`description`,`url`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `shopitems` (
    `id` INT UNSIGNED NOT NULL, 
    `ShopID` INT UNSIGNED NOT NULL, 
    `ImageID` INT UNSIGNED NOT NULL, 
    `pricing` varchar(45) NOT NULL, 
    `datetime_created` datetime NOT NULL, 
    PRIMARY KEY (`id`), 
    FOREIGN KEY (`id`) REFERENCES Searchable (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

CREATE TABLE `shops` (
    `id` INT UNSIGNED NOT NULL, 
    `owner_id` varchar(255) default NULL, 
    `datetime_created` datetime default NULL, 
    `created_by` varchar(255) default NULL, 
    `datetime_modified` datetime default NULL, 
    `modified_by` varchar(255) default NULL, 
    `overall_rating_avg` decimal(4,2) default '0.00', 
    PRIMARY KEY (`id`), 
    FOREIGN KEY (`id`) REFERENCES Searchable (`id`) 
) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

Обратите внимание только таблица с ключом автоинкрементного теперь Searchable. В таблицах shops и shopitems используйте ключ с совместимым типом данных, но не с автоматическим приращением. Поэтому вы должны создать строку в Searchable, чтобы сгенерировать значение id, прежде чем вы сможете создать соответствующую строку в shops или shopitems.

Я добавил FOREIGN KEY объявления для иллюстрации, хотя MyISAM молча игнорирует эти ограничения (и вы уже знаете, что вы должны использовать MyISAM для поддержки полнотекстовой индексации).

Теперь вы можете искать текстовое содержание как shops и shopitems в одном запросе, используя один полнотекстовый индекс:

SELECT S.*, sh.*, si.*, 
    MATCH(keywords, title, description, url) AGAINST('dummy') As score 
FROM Searchable S 
LEFT OUTER JOIN shops sh ON (S.id = sh.id) 
LEFT OUTER JOIN shopitems si ON (S.id = si.id) 
WHERE MATCH(keywords, title, description, url) AGAINST('dummy') 
ORDER BY score DESC; 

Конечно, для данной строки в Searchable только одна таблица должна соответствовать, либо магазины или магазины, и эти таблицы имеют разные столбцы. Таким образом, либо sh.*, либо si.* будет NULL в результате. Это зависит от вас, чтобы форматировать вывод в приложении.


Несколько других ответов предложили использовать Sphinx Search. Это еще одна технология, которая дополняет MySQL и добавляет более сложные возможности полнотекстового поиска. Он имеет отличную производительность для запросов, поэтому некоторые люди получили от него довольно зрелище.

Но создание индексов и особенно добавление к индексу поэтапно дорого. Фактически, обновление индекса поиска Sphinx настолько дорогостоящим, что рекомендуемым решением является создание одного индекса для более старых, архивных данных и другого меньшего индекса для последних данных, которые, скорее всего, будут обновляться. Тогда каждый поиск должен запускать два запроса, против двух отдельных индексов. И если ваши данные естественным образом не поддаются изменению прежних данных, то вы, возможно, не сможете воспользоваться этим трюком.


Re ваш комментарий: Вот отрывок из Sphinx Search documentation о живых изменениях индекса:

Там очень частая ситуация, когда общий набор данных слишком велик, чтобы быть переиндексирован с нуля часто, но сумма новых записей довольно мала. Пример: a forum with 1 000 000 архивные сообщения, но только 1000 новых сообщений в день.

В этом случае, «живые» (почти реальные времени) обновление индекс может быть реализовано с использованием так называемой «основная + дельтой» схемы.

Идея состоит в том, что, поскольку обновление индекса поиска Sphinx является дорогостоящим, их решение заключается в том, чтобы сделать индекс, который вы обновляете как можно меньше. Так что только самые последние сообщения на форуме (в их примере), тогда как большая история архивных сообщений форума никогда не изменяется, поэтому вы создаете второй, более крупный индекс для этой коллекции один раз. Конечно, если вы хотите выполнить поиск, вам нужно запросить оба индекса.

Периодически, говорят один раз в неделю, «последние» сообщения форума станут считаться «заархивированными», и вам придется объединить текущий индекс для последних сообщений в архивированный индекс и начать меньший индекс. Они делают вывод о том, что слияние двух индексов Sphinx Search более эффективно, чем переиндексация после обновления данных.

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

Возьмите свою базу данных, например: у вас есть магазины и магазины. Как вы можете разделить их на строки, которые никогда не меняются, по сравнению с новыми строками? Любым магазинам или продуктам в каталоге должно быть разрешено обновить их описание. Но так как это потребовало бы восстановления индекса индекса Sphinx Search каждый раз, когда вы вносили изменения, это становится очень дорогостоящей операцией. Возможно, вы ставите в очередь изменения и применяете их в пакете, перестраивая индекс один раз в неделю. Но попробуйте объяснить продавцам магазина, почему незначительное изменение их описания магазина не вступит в силу до воскресенья.

+0

Обычно я не рекомендую использовать селектор * в результате запроса. Это может показаться хорошей идеей на волне момента, но обычно препятствует обеспечению совместимости с программным обеспечением, которое должно иметь дело с результатом. –

+0

@Matthieu M: Да, я согласен, я использую шаблон только в специальных запросах и примерах для StackOverflow. Я не использую шаблон для производственного кода. Но этот вопрос ортогонален вопросу полнотекстового поиска. –

+0

Привет, Билл, спасибо за ваш ответ. Это очень ясно и освещает. У меня есть некоторые вопросы о поиске Сфинкса. «n факт, обновление индекса поиска Sphinx настолько дорогостоящим, что рекомендуемым решением является создание одного индекса для более старых, архивных данных и другого меньшего индекса для последних данных, которые, скорее всего, будут обновлены. Тогда каждый поиск должен запускать два запроса , против двух отдельных индексов. И если ваши данные, естественно, не поддаются изменению прежних данных, то вы, возможно, не сможете воспользоваться этим трюком ». можете ли вы уточнить эту часть? –

0

Предлагаю вам первый вариант. Избыточность не всегда зла.

Так что я бы таблицу, как это:

CREATE TABLE search_results 
(
    ... 
    `searchable_shop_info` VARCHAR(32), 
    `searchable_shopitem_info` TEXT 
    FULLTEXT KEY `searchable` (`searchable_shop_info`, `searchable_shopitem_info`) 
) Engine=MyISAM; 

Тогда вы все еще можете использовать SELECT * FROM search_results WHERE MATCH ( searchable_shop_info , searchable_shopitime_info ) AGAINST ('search query string');

+0

Могу ли я спросить, почему вы рекомендуете это по другим параметрам? –

0

СММ может быть, вы можете использовать союз? подобный

 
create table search1 (
    title varchar(12), 
    relavency tinyint unsigned 
); 

create table search2 (
    title varchar(12), 
    relavency tinyint unsigned 
); 

insert into search1 values (substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)); 

insert into search2 values (substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)), 
(substring(md5(rand()), 1, 12), (rand()*100)); 

(select *, 'search1' as source from search1) 
union (select *, 'search2' as source from search2) 
order by relevancy desc;

выберите ваши строки и вычислите релевантность в соответствии с нормалью, затем соедините результаты. я не знаю, понял ли я, как неправильно, потому что никто не думает о союзе?

alt text

UPDATE 1:

нормально я перечитываю ур вопрос и комментарий уже ... я думаю

1) я должен изменить свой дизайн? i am мышление наличия отдельной таблицы названные результаты поиска, которые будут содержат данные обоих МАГАЗИНОВ и Таблица SHOPPRODUCTS. однако это означает, что есть У меня есть дублирование данных.

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

CREATE VIEW viewSearch (Title, Relavency, SourceTable) AS 
(SELECT title, relavency, 'search1' as source FROM search1 
ORDER BY relavency DESC 
LIMIT 10) 
UNION 
(SELECT title, relavency, 'search2' as source FROM search2 
ORDER BY relavency DESC 
LIMIT 10) 
ORDER BY relavency DESC 
LIMIT 10;

alt text

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

по запросу SQL/View. в основном путем размещения

... 
ORDER BY relavency DESC 
LIMIT 10

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

Я действительно не понимаю, что означают? если бы вы теперь искали между двумя таблицами, не делайте 2 отдельных SQL-запроса (1 для каждой таблицы)? или если u должен был выбрать результаты в 1 таблицу, то все равно ... на самом деле 3 запроса (2 для выбора в таблицу результатов, затем 1 для запроса).

Я также добавил ORDER BY & LIMIT в каждый SELECT, чтобы ускорить процесс, получив меньше записей. затем ORDER BY & LIMIT еще раз в целом.

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

Возможно Я немного не понимаю. i Является подозрительным, является ли ваш метод ресурсоемким. Прошу просветить меня. Я готов рассмотреть все возможности .

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

ой, и я тоже не очень знаком с полнотекстовым поиском, так что я не знаю, если этот метод будет влиять на что-либо

+0

Мне любопытно. это означает, что мне нужно запустить этот запрос EVERYTIME для любого ввода поиска. потому что разные входные данные будут иметь разные оценки релевантности. Возможно, мне немного не хватает понимания. Я подозрюю, является ли ваш метод ресурсоемким. Пожалуйста, просветите меня. Я желаю рассмотреть все возможности. –

+0

спасибо за ваши усилия. но вы не выполнили полнотекстовый поиск, поэтому я не думаю, что вы видите проблему. Я уверен, что вы не можете выполнять полнотекстовый поиск в VIEW. –

+0

hmm, хорошо, я не знаю, как u будет поддерживать таблицу результатов. но я думаю, что триггеры будут вариантом – iceangel89

0

Если я понимаю ваши вопросы правильно, ответ очень прост:

  1. Дон» t изменить дизайн. Это прекрасно. Вот как это должно быть.
  2. ли объединенном запрос так:
 
SELECT * FROM shops 
LEFT OUTER JOIN shopitems ON (shopitems.shopid = shops.id) 
WHERE 
    MATCH (shops.title, shops.description, shops.keywords, 
      shopitems.name, shopitems.description) 
    AGAINST ('whatever text') 
+0

1) Вы понимаете неправильно. 2) запрос даже не работает вообще, не говоря уже о целях моего вопроса. –

1

Я не уверен, что я правильно понял, но вот мои 2 цента.

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

  • для магазинов: название, описание и ключевые слова
  • для shopitems: название и описание

Решение 1: Схема консистенции - лань s не использовать индекс ...

Если бы вы могли как-то изменить название своих колонок для shopitems, это сразу стало бы намного проще.

Select id From 
(Select id, text1, text2, text3 From table1 
UNION 
Select id, text1, text2, text3 From table2) 
Where MATCH(id, text1, text2, text3) AGAINST('keyword1 keyword2 keyword3') 

Однако я могу понять, что было бы нецелесообразно менять все, что уже существует. Обратите внимание, что при сглаживании, добавив третий (фиктивный) текстовый столбец к , shopitems мог бы сделать трюк.

Раствор 2: Последующая обработка

следует отметить, что значение, вычисленное на самом деле может быть возвращен (и, таким образом, используется). Поэтому вы можете создать временную таблицу с этим значением! Обратите внимание, что если вы хотите, чтобы вернуть «название» и «описание» оба столбца должны иметь один и тот же тип, которые будут рассматриваться в unifrom манере ...

Select id, title, description From 
(
Select id, title, description, MATCH(id, title, description, keywords) AGAINST('dummy') As score 
     From shops 
     Where MATCH(id, title, description, keywords) AGAINST('dummy') 
UNION 
Select id, name As title, description, MATCH(id, name, description) AGAINST('dummy') As score 
     From shopitems 
     Where MATCH(id, name, description) AGAINST('dummy') 
) 
ORDER BY score DESC 

Я понятия не имею, о выполнении этого запроса хотя, интересно, будет ли mysql оптимизировать двойной вызов MATCH/AGAINST в каждом из Selects (я надеюсь, что это так).

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

В любом случае, я надеюсь, это вам помогло.

+0

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

+0

В обоих решениях есть проблема с столкновением идентификатора, но это можно решить, добавив другое поле в каждую таблицу и введя имя таблицы в это поле для всех его строк. Однако это также означает, что, когда я показываю свои результаты на веб-странице, мне нужно снова вернуть всю связанную информацию для всех, так как у меня есть только идентификатор. –

+0

Да проблема двойного извлечения раздражает, поэтому я предложил попытаться иметь более похожие макеты таблицы, если это возможно. Обратите внимание, что во втором решении вы можете попросить получить дополнительную информацию (название, описание) и сгладить различия с помощью псевдонимов. Я могу попытаться придумать более полное решение, если вы скажете мне, какие строки вам нужны для каждой из ваших таблиц и какие изменения вы готовы внести в свои структуры таблиц. –

0

Я бы пошел на СОЮЗ. Это и есть цель заявления.

0

Я бы пошел с вашей первой альтернативой, создав отдельную таблицу поиска.

Мы сделали это один раз, когда нам нужно было искать данные по нескольким SOA-системам.

Преимущества такого подхода являются:

  • быстрый ответ на поисковые запросы
  • больше контроля над организации поиска результатов

Недостатками являются:

  • медленное время для сохранения данных, так как он должен быть написан двумя местами
  • дополнительное пространство, используемое для хранения данных