繁体   English   中英

MySQL - 如何获得具有准确相关性的搜索结果

[英]MySQL - How to get search results with accurate relevance

我已经多次重新审视这个问题,但我从未真正找到正确的答案。

是否可以执行 MySQL 搜索,该搜索返回 ACTUAL 按相关性准确排序的结果?

我正在尝试创建一个 ajax 搜索表单,它在用户输入输入字段时提出建议,并且仅使用纯 MySQL 查询没有找到合适的解决方案。 我知道有可用的搜索服务器,例如 ElasticSearch,我想知道如何仅使用原始 MySQL 查询来做到这一点。


我有一张学校科目表。 少于 1200 行,这永远不会改变。 让我们执行一个基本的 FULLTEXT 搜索,用户开始输入“Bio”。

查询(“生物...”) - 全文布尔模式

SELECT name, MATCH(name) AGAINST('bio*' IN BOOLEAN MODE) AS relevance
FROM subjects
WHERE MATCH(name) AGAINST('bio*' IN BOOLEAN MODE)
ORDER BY relevance DESC
LIMIT 10

结果

name                                  |  relevance
--------------------------------------------------------
Biomechanics, Biomaterials and Prosthetics  |  1
Applied Biology                             |  1
Behavioural Biology                         |  1
Cell Biology                                |  1
Applied Cell Biology                        |  1
Developmental/Reproductive Biology          |  1
Developmental Biology                       |  1
Reproductive Biology                        |  1
Environmental Biology                       |  1
Marine/Freshwater Biology                   |  1

为了显示这些结果有多糟糕,这里是一个简单的LIKE查询的比较,它显示了所有未显示的更相关的结果:

查询(“生物...”) - 喜欢

SELECT id, name
WHERE name LIKE 'bio%'
ORDER BY name

结果

name                                  |  relevance
--------------------------------------------------------
Bio-organic Chemistry                       |  1
Biochemical Engineering                     |  1
Biodiversity                                |  1
Bioengineering                              |  1
Biogeography                                |  1
Biological Chemistry                        |  1
Biological Sciences                         |  1
Biology                                     |  1
Biomechanics, Biomaterials and Prosthetics  |  1
Biometry                                    |  1

而且您已经看到有多少主题没有被建议,即使这些主题更有可能是用户正在寻找的。

然而,使用LIKE的问题是如何像FULLTEXT那样在多个单词和单词中间进行搜索。

我想要实现的基本排序是这样的:

  1. 以搜索词开头的第一个词
  2. 以搜索词开头的第二个词
  3. 词条不在词首的词
  4. 如果没有进一步相关,所有内容通常按字母顺序排列

所以我的问题是,如何通过跨多个单词的 MySQL 搜索为用户获取合理排序的建议列表?

您可以使用字符串函数,例如:

select id, name
from subjects
where name like concat('%', @search, '%')
order by 
  name like concat(@search, '%') desc,
  ifnull(nullif(instr(name, concat(' ', @search)), 0), 99999),
  ifnull(nullif(instr(name, @search), 0), 99999),
  name;

这将为您获取包含@search 的所有条目。 首先是在开头有它的那些,然后是在空白之后有它的那些,然后是出现的位置,然后是字母。

name like concat(@search, '%') desc顺便使用了 MySQL 的布尔逻辑。 1 = 真,0 = 假,所以按降序排序首先给你真。

SQL小提琴: http ://sqlfiddle.com/#!9/c6321a/1

对于其他登陆这里的人(就像我一样):根据我的经验,为了获得最佳结果,您可以根据搜索词的数量使用条件。 如果只有一个词使用LIKE '%word%' ,否则使用布尔全文搜索,如下所示:

if(sizeof($keywords) > 1){
   $query = "SELECT *,
             MATCH (col1) AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             AS relevance1,
             MATCH (col2) AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             AS relevance2
             FROM table1 c
             LEFT JOIN table2 p ON p.id = c.id
             WHERE MATCH(col1, col2) 
             AGAINST ('+word1* +word2*' IN BOOLEAN MODE) 
             HAVING (relevance1 + relevance2) > 0
             ORDER BY relevance1 DESC;";
    $execute_query = $this->conn->prepare($query);
}else{          
   $query = "SELECT * FROM table1_description c
             LEFT JOIN table2 p ON p.product_id = c.product_id
             WHERE colum1 LIKE ? AND column2 LIKE ?;";
        // sanitize
        $execute_query = $this->conn->prepare($query);
        $word=htmlspecialchars(strip_tags($keywords[0]));
        $word = "%{$word}%";
        $execute_query->bindParam(1, $word);
        $execute_query->bindParam(2, $word);
    }

这是使用上述答案的组合我可以获得的最佳结果:

$searchTerm = 'John';
// $searchTerm = 'John Smit';
if (substr_count($searchTerm, ' ') <= 1)
    $sql = "SELECT id, name
    FROM people
    WHERE name like '%{$searchTerm}%')
    ORDER BY
      name LIKE '{$searchTerm}%') DESC,
      ifnull(nullif(instr(name, ' {$searchTerm}'), 0), 99999),
      ifnull(nullif(instr(name, '{$searchTerm}'), 0), 99999),
      name
    LIMIT 10";
}
else {
$searchTerm = '+' . str_replace(' ', ' +', $searchTerm) . '*';
$sql = "SELECT id,name, MATCH(lead.name) AGAINST('{$searchTerm}' IN BOOLEAN MODE) AS SCORE
        FROM lead
    WHERE MATCH(lead.name) AGAINST('{$searchTerm}' IN BOOLEAN MODE)
    ORDER BY `SCORE` DESC
    LIMIT 10";

确保在列上设置全文索引(如果最终使用的是多列)并使用OPTIMIZE table_name重置索引。

最好的一点是,如果您输入Jo ,那么名字为Jo的人的排名将高于John ,这正是您想要的!

我根据您描述的顺序尝试了这个。

SET @src := 'bio';
SELECT name,
name LIKE (CONCAT(@src,'%')),
         LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(name,' ',2),' ',-1),LENGTH(@src)) = @src,
         name LIKE (CONCAT('%',@src,'%'))
FROM subjects
ORDER BY name LIKE (CONCAT(@src,'%')) DESC,
         LEFT(SUBSTRING_INDEX(SUBSTRING_INDEX(name,' ',2),' ',-1),LENGTH(@src)) = @src DESC,
         name LIKE (CONCAT('%',@src,'%')) DESC,
         name

http://sqlfiddle.com/#!9/6bffa/1

我想也许您甚至可能还想包括@src 的出现次数 计算 VARCHAR 字段中字符串的出现次数?

MATCH(s.name) AGAINST('"Applied Bio"' IN BOOLEAN MODE)

以上语句将搜索确切的搜索词,意味着这两个词必须存在于每条记录中。

ORDER BY s.name like concat("Applied Bio", '%') desc,
ifnull(nullif(instr(s.name, concat(' ', "Applied Bio")), 0), 99999),
ifnull(nullif(instr(s.name, "Applied Bio"), 0), 99999),
s.name

按以搜索词开头的第一个单词排序。

完整的 SQL 语句:

SELECT SQL_NO_CACHE 
s.id, s.name
FROM subjects s use index(name_fulltext) 
WHERE 
MATCH(s.name) AGAINST('"Applied Bio"' IN BOOLEAN MODE) 
GROUP BY s.id 
ORDER BY 
s.name like concat("Applied Bio", '%') desc,
ifnull(nullif(instr(s.name, concat(' ', "Applied Bio")), 0), 99999),
ifnull(nullif(instr(s.name, "Applied Bio"), 0), 99999),
s.name
LIMIT 100;

为了得到你想要的,你可以看看将几个“case when…”语句与 mysql 的正则表达式结合起来,这将根据你的要求为你提供每行的准确分数。 正则表达式可能是您缺少的拼图的一部分:请参阅https://dev.mysql.com/doc/refman/5.6/en/regexp.html (在我的手机上回答,因此很难格式化答案或给出示例)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM