2014-03-12 111 views
1

我正试图在一个对一对多关系的两个表上实现搜索功能。把它想象成与多个标签。每个标签在tag表中都有自己的行。MySQL全文搜索:一对多关系

我想找回后,如果所有的搜索词可以发现无论是)的后文,二)后标签或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); 

如果我的搜索查询是“特玛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在标签中,我想检索该文章。

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

回答

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) 
+0

我试过了。唯一的问题是,如果只有一个条款在帖子文本中,并且没有任何条款在标签中,它仍然匹配。我需要它是一个**和**。 – musicnothing

+0

它在MATCH(post.text)中未找到+。测试更新的查询。 –

+0

现在它只匹配两个词都在文本中,并且两个词都在标签中。我需要的是在文本,标签或两者中找到所有术语。就像TermA在文本中,但TermB在标签中。 – musicnothing

1

这是可能的,但我猜你的Tags表中,你有每个帖子每个标签一行。因此,一行包含标签'TermA'用于发布1,另一行包含标签'TermB',对吧?

全部词语查询(与+)只返回搜索字段包含所有指定词的行。对于标签表而言,情况绝非如此。

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

另一种可能性是完全改变标签的条件。也就是说,对文本使用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中的“标签”字段是最简单的方法。从标准化的角度来看,这并不理想,但我认为这是可以管理的。

+0

这可以工作,如果我知道有多少条款与帖子文本匹配。有没有一种方法来计算? – musicnothing

+0

因为这是现在,至少有一个词必须在帖子文本中,所有的词必须在标签中找到。如果我可以从预计在标签中找到的标签数量中减去在帖子文本中找到的标签数量,我可以将其取消。 – musicnothing

+0

也许你可以为每个搜索到的关键字添加一个“MATCH”条件。在布尔模式下,我认为它返回0或1,在这种情况下,你可以将它们加起来。 – GolezTrol

0

您可以搜索texttags

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

为了得到这个工作,你需要为两个列创建一个FULLTEXT索引。

CREATE FULLTEXT INDEX keywords ON pos(text,tags) 

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

+0

问题在于您无法创建跨多个表格的全文索引。标签不是一个领域,它是一张桌子。您的建议是,我还在邮寄表中保留了一个标签中的字段? – musicnothing