как реализовать поиск по 2 разным табличным данным?


Использование mysql и PHP

Я уже использую предложения "СОПОСТАВЛЕНИЕ С".

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

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

Например, если я наберу "шоколадная одежда"

Я могу получить 4 следующих результата:

Результат Shop1

Результат shopitem1

Результат shopitem2

Магазин 2 результат

И, конечно, наиболее релевантные результаты должны быть ранжированы первыми.

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

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

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

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

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

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

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

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

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;

Я намерен выполнить поиск по столбцам "Описание" и "название" таблицы "Товары для покупок".

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

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

Author: Kim Stacks, 2009-09-26

7 answers

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

  • Все индексы в MySQL могут ссылаться только на столбцы в одной базовой таблице. Вы не можете создать полнотекстовый индекс, который индексирует несколько таблиц.
  • Вы не можете определять индексы для представлений, только базовые таблицы.
  • Запрос 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 должна совпадать только одна таблица, либо магазины, либо shopitems, и эти таблицы имеют разные столбцы. Таким образом, либо sh.*, либо si.* в результате будет равно нулю. Форматирование выходных данных в вашем приложении зависит от вас.


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

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


Ваш комментарий: Вот выдержка из Документации по поиску Sphinx о текущих обновлениях индекса:

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

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

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

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

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

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

 5
Author: Bill Karwin, 2009-10-04 17:10:59

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

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

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

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

Если бы вы могли как-нибудь измените названия ваших столбцов для 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: Последующая обработка

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

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 оптимизировать двойной вызов для СОПОСТАВЛЕНИЯ/СОПОСТАВЛЕНИЯ в каждом из выбранных (я надеюсь, что это так).

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

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

 1
Author: Matthieu M., 2009-10-04 15:42:48

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

Поэтому я бы сделал такую таблицу:

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
Author: Ifju, 2009-09-26 11:47:26

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

  1. Не меняйте дизайн. Все в полном порядке. Вот как это должно быть.
  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
Author: Slawa, 2009-09-30 16:02:13

Я бы пошел за СОЮЗ. В этом и заключается цель данного заявления.

 0
Author: Teo, 2009-10-03 14:51:16

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

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

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

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

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

  • более медленное время сохранения данных, так как они должны быть записаны в двух местах
  • дополнительное пространство, используемое для хранения данных
 0
Author: Shiraz Bhaiji, 2009-10-03 20:47:40

Хм, может быть, ты можешь использовать союз? как

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

ОБНОВЛЕНИЕ 1:

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

1) должен ли я изменить свой дизайн? я подумываю о том, чтобы создать отдельную таблицу под названием результаты поиска, которая будет содержать данные как из МАГАЗИНОВ, так и Таблица ТОВАРОВ ДЛЯ ПОКУПОК. однако это означает, что у меня есть некоторое дублирование данных.

Я думаю, что вместо этого вы должны использовать представление, чтобы содержать данные из обеих таблиц, поскольку представление автоматически "обновляется" при изменении данных 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/Представление выше вы можете. в основном, разместив

...
ORDER BY relavency DESC 
LIMIT 10

Мне любопытно. это означает, что мне нужно запускать этот запрос КАЖДЫЙ раз для любого поиска вход. потому что разные входные данные будут иметь разные оценки релевантности.

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

Я также добавил ПОРЯДОК и ОГРАНИЧЕНИЕ в каждый ВЫБОР, чтобы ускорить процесс, получив меньше записей. затем ЗАКАЖИТЕ и ОГРАНИЧЬТЕ еще раз в целом.

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

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

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

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

 0
Author: iceangel89, 2015-06-20 18:22:23