MySQL全文搜索:一对多关系

时间:2014-03-12 21:06:10

标签: mysql sql one-to-many relationship

我正在尝试在具有一对多关系的两个表上实现搜索功能。将其视为具有多个标记帖子。每个标记在tag表中都有自己的行。

如果可以在a)发布文字,b)帖子标记或c)中找到所有搜索字词,我想要检索帖子的两个

假设我已经创建了这样的表:

CREATE TABLE post (
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    text VARCHAR(100) NOT NULL
);

CREATE TABLE tag (
    id MEDIUMINT NOT NULL AUTO_INCREMENT,
    name VARCHAR(30) NOT NULL,
    post MEDIUMINT NOT NULL
);

我创建了这样的索引:

CREATE FULLTEXT INDEX post_idx ON post(text);
CREATE FULLTEXT INDEX tag_idx ON tag(name);

如果我的搜索查询是“TermA TermB”并想在帖子文本中搜索只是,我会像这样制定我的SQL查询:

SELECT * FROM post WHERE MATCH(text) AGAINST('+TermA +TermB' IN BOOLEAN MODE);

有没有办法在混音中添加标签?我以前的尝试是这样的:

SELECT * FROM post 
RIGHT JOIN tag ON tag.post = post.id 
WHERE MATCH(post.text) AGAINST('TermA TermB' IN BOOLEAN MODE)
OR MATCH(tag.name) AGAINST('TermA TermB' IN BOOLEAN MODE);

问题是,这只是任何单词查询,而不是所有单词查询。我的意思是,如果TermA在文本中并且TermB在标签中,我想要检索帖子。

我在这里缺少什么?这甚至可以使用全文搜索吗?有没有更好的方法来解决这个问题?

3 个答案:

答案 0 :(得分:1)

试试这个:

SELECT post.* 
FROM post 
INNER JOIN (SELECT post, GROUP_CONCAT(name SEPARATOR ' ') tags FROM tag GROUP BY post) tag ON post.id=tag.post
WHERE MATCH(post.text) AGAINST('+TermA +TermB' IN BOOLEAN MODE)
OR MATCH(tags) AGAINST('+TermA +TermB' IN BOOLEAN MODE)

这可能也可以获得与内容或标签匹配的结果,但它在MySQL 5.1中不起作用:

SELECT post.*, GROUP_CONCAT(tag.name SEPARATOR ' ') tags 
FROM post 
LEFT JOIN tag ON post.id=tag.post
GROUP BY post.id
HAVING MATCH(post.text,tags) AGAINST('+TermA +TermB' IN BOOLEAN MODE)

所以我把它重写为:

SELECT post.*, tags
FROM post 
LEFT JOIN (SELECT post, GROUP_CONCAT(tag.name SEPARATOR ' ') tags FROM tag GROUP BY post) tags ON post.id=tags.post
WHERE MATCH(post.text, tags) AGAINST('+TermA +TermB' IN BOOLEAN MODE)

答案 1 :(得分:1)

这是可能的,但我猜测在你的Tags表格中,每个帖子的每个标记都有一行。所以一行包含标签' TermA'对于帖子1和带有标签' TermB'的另一条记录,对吧?

所有单词查询(带+)仅返回搜索字段包含所有指定单词的行。对于标签表,情况绝对不是这样。

一种可能的解决方案是将所有标签存储在posts表本身的单个字段中。那么在标签上进行高级匹配也很容易。

另一种可能性是完全改变标签的条件。也就是说,对文本使用all查询,对标记使用any查询。要做到这一点,您必须自己修改搜索查询,幸运的是,从查询中删除加号一样容易。

您还可以查询完全匹配,如下所示:

SELECT * FROM post p
WHERE
  MATCH(p.text) AGAINST('TermA TermB' IN BOOLEAN MODE)
  AND 
     /* Number of matching tags .. */
     (SELECT COUNT(*) FROM tags t 
      WHERE 
        t.post = p.id
        AND (t.tag in ('TermA', 'TermB')
     = /* .. must be .. */ 
     2 /* .. number of searched tags */ )

在此查询中,我计算匹配标记的数量。在这种情况下,我希望它正好是2,意味着两个标签匹配(假设每个帖子的标签是唯一的)。您还可以检查> = 1以查看是否有任何标签匹配。

但是正如您所看到的,这也需要解析搜索字符串。您将不得不删除这些加号(甚至检查它们的存在,以了解您是否需要'任何'所有')。而且你必须将它拆分以获得搜索到的单词的数量,并自己获得单独的单词。

总而言之,将所有标签添加到“标签”中。 post中的字段是最简单的方法。从标准化的角度来看,这不是理想的,但我认为这是可管理的。

答案 2 :(得分:0)

您可以同时搜索texttags

SELECT * 
  FROM post 
 WHERE MATCH(text,tags) AGAINST('+TermA +TermB' IN BOOLEAN MODE)

要使其工作,您需要为两个列一起制作FULLTEXT索引。

CREATE FULLTEXT INDEX keywords ON pos(text,tags)

在布尔搜索模式下,这应该做你想要的。