如何实现搜索2个不同的表数据?

时间:2009-09-26 10:32:02

标签: php mysql search full-text-search

使用mysql和PHP

我已经在使用MATCH AGAINST条款了。

它可以很好地对付各个表格。就像我想在商店桌上搜索一样。没问题。

我想要的是能够在单个结果页面中搜索和显示来自不同表格的结果。

例如,如果我输入“巧克力衣服”

我可能会得到以下4个结果:

Shop1结果

ShopItem1结果

ShopItem2结果

Shop2结果

当然,最相关的结果应排在第一位。

我有很多问题。设计明智以及实施明智

1)我应该改变我的设计吗?我正在考虑有一个名为搜索结果的单独表格,其中包含来自SHOPS和SHOPPRODUCTS表的数据。但这意味着我有一些数据重复。

2)我应该保留目前的设计吗?如果是这样,那么我怎么能在两个不同的表中按相关性排序搜索结果呢?

我看到rottentomatoes将他们的搜索结果组织在不同的组中。但是,我们更喜欢搜索结果不受不同类型的限制,特别是当我们的分页更难以在UI方面进行导航时。

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;

我打算搜索shopproducts表的描述和名称列。

但是你可以看到它还没有实现。

虽然搜寻商店已经开始运作。

7 个答案:

答案 0 :(得分:5)

以下是一些“游戏规则”,您必须牢记解决此问题。您可能已经知道这些,但明确说明这些可能有助于确认其他读者。

  • MySQL中的所有索引只能引用单个基表中的列。您无法创建跨多个表索引的全文索引。
  • 您无法为视图定义索引,只能定义基表。
  • 针对全文索引的MATCH()查询必须与全文索引中的所有列匹配,并按索引中声明的顺序进行匹配。

我会创建第三个表来存储您要编制索引的内容。无需冗余存储此内容 - 仅将其存储在第三个表中。这借用了面向对象设计的“通用超类”概念(只要我们可以将它应用于RDBMS设计)。

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。表shopsshopitems使用具有兼容数据类型的键,但不使用自动增量。因此,您必须在Searchable中创建一行才能生成id值,然后才能在shopsshopitems中创建相应的行。

我添加了FOREIGN KEY声明用于说明目的,即使MyISAM会默默地忽略这些约束(并且您已经知道必须使用MyISAM来支持全文索引)。

现在,您可以使用单个全文索引在单个查询中搜索shopsshopitems的文本内容:

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搜索索引的成本非常高,建议的解决方案是为较旧的归档数据创建一个索引,为更新的数据创建另一个较小的索引,以便更新。然后,每个搜索都必须针对两个单独的索引运行两个查询。如果你的数据自然不适合旧数据不变的模式,那么你可能无法利用这个技巧。


重新评论:以下是Sphinx Search documentation关于索引实时更新的摘录:

  

经常出现这种情况   总数据集太大了   经常从头开始重新索引,但是   新记录的数量相当小。   示例:一个拥有1,000,000的论坛   存档的帖子,但只有1,000新   每天的帖子。

     

在这种情况下,“现场”(几乎是真实的   时间)索引更新可能是   使用所谓的实现   “主要+三角洲”计划。

这个想法是,由于更新Sphinx搜索索引的成本很高,因此他们的解决方案是让您更新的索引尽可能小。因此,只有最新的论坛帖子(在他们的示例中),而存档的论坛帖子的较大历史永远不会更改,因此您只为该集合构建一个第二个更大的索引。当然,如果你想进行搜索,你必须查询两个索引。

定期地说,每周一次,“最近的”论坛消息将被视为“存档”,您必须将最近帖子的当前索引合并到存档索引,并启动较小的索引。他们确实指出,在更新数据后,合并两个Sphinx搜索索引比重新索引更有效。

但我的观点是,与最近频繁更新的数据相比,并非所有数据集都自然地属于拥有永不更改的归档数据集的模式。

以您的数据库为例:您有商店和商店。与新行相比,如何将这些行分成永不改变的行?应允许目录中的任何商店或产品更新其描述。但是,由于每次进行更改都需要重建整个Sphinx搜索索引,因此这将成为一项非常昂贵的操作。也许你会排队更改并批量应用它们,每周重建一次索引。但是,试着向商店供应商解释为什么在周日晚上之前对商店描述的微小改动不会生效。

答案 1 :(得分:1)

我不确定我是否理解正确,但这是我的2美分。

从我所看到的,问题是你有2个表格有不同的布局,所以我假设你想要在这些字段上进行全文搜索:

  • 用于商店:标题,说明和关键字
  • for shopitems :名称和描述

解决方案1:布局一致性 - 不使用索引......

如果你能以某种方式改变商店项目列的名称,它会立即变得更加简单。

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是否会在每个选择中优化掉对MATCH / AGAINST的双重调用(我希望如此)。

问题在于我的查询仅仅是一个演示。使用别名的缺点是,现在你不知道它们来自哪个表。

无论如何,我希望它对你有帮助。

答案 2 :(得分: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');

答案 3 :(得分: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

更新1:

好的,我已经重新阅读了你的问题和评论......我想

  

1)我应该改变我的设计吗?我是   想着有一张单独的桌子   称为搜索结果   包含来自SHOPS和。的数据   SHOPPRODUCTS表。但这意味着   我有一些数据重复。

我认为你应该使用一个视图来包含来自两个表的数据,因为视图会在你的数据发生变化时自动“更新”。如果你使用表格,你可能需要自己更新。

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)我应该保留目前的设计吗?如果   所以,那么我怎么能得到它   搜索结果按相关性排序   跨越两个不同的表?

通过上面的SQL / View可以。基本上放置

...
ORDER BY relavency DESC 
LIMIT 10
  我很好奇。这意味着我需要跑步   查询EVERYTIME进行任何搜索   输入。因为不同的输入会   有不同的相关性分数。

我真的不明白你的意思?如果你现在要搜索2个表,你不会做2个单独的SQL查询(每个表1个)?或者如果你要将结果选择到1个表中,它仍然...实际上是3个查询(2个选择进入结果表然后1个进行查询)。

我还添加了ORDER BY&限制每个SELECT以通过获得更少的记录来加速进程。然后订购&再次限制整体。

在这个例子中,我不知道你将如何计算相关性,所以我使用了随机数。

  

也许   我有点缺乏理解。一世   我怀疑你的方法是否合适   资源密集。请指教   我。我愿意考虑所有   可能性。

我不是很确定,但我想知道这个问题的答案...我猜它仍然比多个查询更好。

哦,我也不是很熟悉全文搜索,所以我不知道这种方法是否会影响任何事情

答案 4 :(得分:0)

如果我理解你的问题,答案很简单:

  1. 不要改变设计。这很好。这就是它应该如何。
  2. 执行如下连接查询:
  3. 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')
    

答案 5 :(得分:0)

我会去UNION。这就是声明的目的。

答案 6 :(得分:0)

我会选择你的第一个选择,创建一个单独的搜索表。

当我们需要跨多个SOA系统搜索数据时,我们已经做过一次。

这种方法的好处是:

  • 对搜索请求的快速响应
  • 对搜索结果组织的更多控制

缺点是:

  • 保存数据的时间较慢,因为它必须写两个地方
  • 用于存储数据的额外空间