【问题标题】:MySQL - How to get search results with accurate relevanceMySQL - 如何获得具有准确相关性的搜索结果
【发布时间】:2016-10-09 05:53:15
【问题描述】:

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

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

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


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

查询(“Bio...”) - FULLTEXT BOOLEAN MODE

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 查询的比较,它显示了所有未显示的更相关的结果:

查询(“生物...”) - 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

您已经看到有多少主题没有被推荐,尽管这些主题更有可能是用户正在寻找的。​​p>

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

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

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

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

【问题讨论】:

    标签: php mysql sql search


    【解决方案1】:

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

    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

    【讨论】:

      【解决方案2】:

      对于其他登陆这里的人(就像我一样):根据我的经验,为了获得最佳结果,您可以根据搜索词的数量使用条件。如果只有一个词使用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);
          }
      

      【讨论】:

      • 次要注意:我建议使用count() 而不是sizeof(),这是一个很少使用的别名,在大多数其他编程语言中具有不同的含义。
      【解决方案3】:

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

      $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,这正是您想要的!

      【讨论】:

        【解决方案4】:

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

        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 的出现次数Count the number of occurrences of a string in a VARCHAR field?

        【讨论】:

          【解决方案5】:
          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;
          

          【讨论】:

            猜你喜欢
            • 2016-01-05
            • 2019-10-15
            • 2011-02-21
            • 2015-11-21
            • 2023-03-04
            • 2014-02-18
            • 1970-01-01
            • 2014-12-16
            相关资源
            最近更新 更多