【问题标题】:how to implement search for 2 different table data?如何实现对 2 个不同的表数据的搜索?
【发布时间】:2010-12-01 15:27:48
【问题描述】:

使用mysql和PHP

我已经在使用 MATCH AGAINST 子句了。

它对单个表运行良好。就像我想在商店表中搜索一样。没问题。

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

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

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

Shop1 结果

ShopItem1 结果

ShopItem2 结果

Shop2 结果

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

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

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

2) 我应该保留我目前的设计吗?如果是这样,那么我到底如何才能获得按 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 表的描述和名称列。

但正如您所见,它尚未实施。

虽然商店搜索已经启动并运行。

【问题讨论】:

  • 添加表结构有助于得到一个好的答案
  • 嗨,你是什么意思?你的意思是我应该有一个名为 search_results 的单独表,其中包含所有现有数据并仅基于该表进行匹配?
  • 在 Sphinx 或 Xapian 上进行全文搜索不是更容易吗?以给定的时间间隔创建索引并只在其中搜索将大大提高搜索速度。
  • 我不明白什么是 SPhinx。以及如何通过使用 sphinx 我可以显示来自 2 个或更多不同数据表的搜索结果

标签: php mysql search full-text-search


【解决方案1】:

为了解决这个问题,您必须牢记以下几条“游戏规则”。您可能已经知道这些,但清楚地说明它们可能有助于其他读者确认。

  • 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 中的给定行,只有一个表应该匹配,要么是 shop 要么是 shopitems,并且这些表具有不同的列。因此,sh.*si.* 在结果中将为 NULL。您可以自行决定应用程序中的输出格式。


其他几个答案建议使用Sphinx Search。这是另一种补充 MySQL 并增加了更复杂的全文搜索功能的技术。它具有出色的查询性能,因此有些人对它非常着迷。

但是创建索引,尤其是增量添加到索引是昂贵的。事实上,更新 Sphinx 搜索索引的成本非常高,因此推荐的解决方案是为较旧的存档数据创建一个索引,并为更有可能更新的最新数据创建另一个较小的索引。然后每次搜索都必须针对两个单独的索引运行两个查询。如果您的数据不能自然地适应旧数据不变的模式,那么您可能无论如何都无法利用这个技巧。


关于您的评论:这是Sphinx Search documentation 关于索引实时更新的摘录:

经常出现以下情况 总数据集太大而不能 经常从头开始重新索引,但是 新记录的数量相当少。 示例:一个拥有 1,000,000 人的论坛 已归档的帖子,但只有 1,000 个新帖子 每天发帖数。

在这种情况下,“活”(几乎是真实的 时间)索引更新可能是 使用所谓的实现 “main+delta”方案。

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

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

但我的观点是,与经常更新的最新数据相比,并非每个数据集都自然而然地属于拥有一组永不更改的存档数据的模式。

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

【讨论】:

  • 我通常不建议在查询结果中使用 * 选择器。一时兴起这似乎是个好主意,但它通常会妨碍与应该处理结果的软件的前向兼容性。
  • @Matthieu M:是的,我同意,我只在 StackOverflow 的临时查询和示例中使用通配符。我不将通配符用于生产代码。但是这个问题与全文搜索问题是正交的。
  • 嗨,比尔,感谢您的回答。它非常清晰,很有启发性。不过,我对 Sphinx Search 有一些疑问。 “事实上,更新 Sphinx 搜索索引的成本非常高,因此推荐的解决方案是为较旧的存档数据创建一个索引,并为更有可能更新的最新数据创建另一个较小的索引。然后每次搜索都必须运行两个查询“你能详细说明这部分吗?
  • 哇,谢谢比尔!而且我认为我使用 Sphinx 的唯一问题是它不能在 site5 的共享主机上使用。我知道我还没有遇到这个问题,但是如果一段时间后,我遇到了扩展问题怎么办。我应该考虑什么才能使我的全文搜索即使对于像商店这样的单基表也很好?
  • 我一直认为更多的中小型网站应该使用外部搜索解决方案,例如 Google 自定义搜索 (google.com/cse) 或 Yahoo Build Your Own Search Service (@987654324 @) 而不是尝试在内部实现完整搜索。让其他人维护可扩展搜索的铁杆!那么您所需要担心的就是让您的网站对 SEO 友好。
【解决方案2】:

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

据我所知,问题在于您有 2 个布局非常不同的表格,因此我假设您希望基于这些字段进行全文搜索:

  • 对于商店:标题、描述和关键字
  • 对于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:后处理

我应该指出,计算的值实际上可以返回(并因此使用)。因此,您可以使用该值创建一个临时表!请注意,如果您希望返回 'title' 和 'description' 两列应具有相同的类型,以便以 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 是否会优化每个 Selects 中对 MATCH / AGAINST 的双重调用(我希望它会这样做)。

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

不管怎样,希望对你有所帮助。

【讨论】:

  • 谢谢。我认为您的回答至少比其他答案更有意义。我至少会给你一个赞成票。其他答案是,我觉得,从时髦的风格拍摄......令人失望。
  • 您的两个解决方案都存在 id 冲突问题,但这可以通过向每个表添加另一个字段并将表名放入该字段中的所有行来解决。然而,这也意味着当我在网页上显示我的结果时,我必须再次检索所有相关信息,因为我只有 id。
  • 是的,双重检索的问题很烦人,这就是为什么我建议尽可能尝试使用更多类似的表格布局。请注意,在第二个解决方案中,您可以要求检索更多信息(标题、描述)并通过别名来平滑差异。如果您告诉我您的每个表格需要哪些行以及您准备对表格结构进行哪些更改,我可以尝试提出一个更完整的解决方案。
【解决方案3】:

我建议你第一个选项。冗余并不总是邪恶的。

所以我会做一个这样的表:

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');

【讨论】:

  • 我可以问一下你为什么推荐它而不是其他选项吗?
【解决方案4】:

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

  1. 不要更改设计。完全没问题。这就是它应该的样子。
  2. 执行这样的联合查询:
从商店中选择 * LEFT OUTER JOIN shopitems ON (shopitems.shopid = shop.id) 在哪里 匹配(shops.title,shops.description,shops.keywords, shopitems.name,shopitems.description) 反对('无论文本')

【讨论】:

  • 1) 你理解错了。 2)查询甚至根本不起作用,更不用说我的问题了。
【解决方案5】:

我会选择 UNION。这就是声明的目的。

【讨论】:

    【解决方案6】:

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

    当我们需要跨多个 SOA 系统搜索数据时,我们曾经这样做过。

    这种方法的好处是:

    • 更快地响应搜索请求
    • 更好地控制搜索结果的组织

    缺点是:

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

    【讨论】:

      【解决方案7】:

      嗯,也许你可以使用联合?喜欢

      创建表 search1 ( 标题 varchar(12), relavency tinyint 无符号 ); 创建表 search2 ( 标题 varchar(12), relavency tinyint 无符号 ); 插入 search1 值 (substring(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)); 插入 search2 值 (substring(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)), (子串(md5(rand()), 1, 12), (rand()*100)); (选择 *,'search1' 作为来自 search1 的源) union (select *, 'search2' 作为 search2 的源) 按相关性 desc 排序;

      选择您的行并按照正常情况计算相关性,然后合并结果。我不知道我是否理解你的方式是错误的,因为似乎没有人想到工会?

      更新 1:

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

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

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

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

      2) 我应该保留我目前的设计吗?如果 那么,我到底怎么才能得到 按相关性排序的搜索结果 跨 2 个不同的表?

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

      ... ORDER BY relavency DESC 限制 10

      我很好奇。这意味着我需要跑步 每次都查询任何搜索 输入。因为不同的输入会 有不同的相关性分数。

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

      我还在每个 SELECT 中添加了 ORDER BY & LIMIT 以通过获得更少的记录来加快流程。然后 ORDER BY & LIMIT 再次作为一个整体。

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

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

      我不太确定是否诚实,但我想知道这个问题的答案……我猜它仍然比多个查询要好。

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

      【讨论】:

      • 我很好奇。这意味着我需要为任何搜索输入每次运行该查询。因为不同的输入会有不同的相关性分数。可能我理解的有点少。我怀疑您的方法是否占用大量资源。请赐教。我愿意考虑所有的可能性。
      • 感谢您的努力。但是您还没有进行全文搜索,所以我认为您没有看到问题。我很确定您不能在 VIEW 上进行全文搜索。
      • 嗯,好的,我不知道你将如何维护结果表。但我想触发器将是一个选项
      • 我认为您不太了解我对 search_results 表的用途。它们只是商店和商店产品表中数据的克隆。坏事是当我更新商店或商店产品时,我必须同时更新商店表和 search_results 表。好在不知何故,搜索单个表而不是 2 个表并相应地显示结果更容易。
      • 我的意思是,当商店或产品表更新时,您可以使用触发器来更新结果表
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-12
      • 1970-01-01
      • 2022-11-22
      • 2019-04-15
      • 1970-01-01
      • 2021-12-01
      相关资源
      最近更新 更多