多面过滤器:如何烹饪以及与之搭配

有什么事 



如何在网上商店中进行多面搜索?多面搜索过滤器中的值是如何生成的?在过滤器中选择一个值如何影响相邻过滤器中的值?为了寻找答案,我进入了Google搜索结果的第五页。我没有找到详尽的信息,我不得不自己弄清楚。本文介绍:



  1. 用户使用过滤器时,UI的反应如何;
  2. 生成过滤器值的算法; 
  3. ElasticSearch查询模板和索引结构及其说明。


这里没有现成的解决方案。您无法复制和粘贴。为了解决自己的问题,您必须深入研究。







明确概念 



全文搜索-按单词或短语搜索产品。对于用户而言,这是一个用于使用“查找”按钮输入文本的字段,该字段可在网站的任何页面上使用。



多面搜索-通过以下几种特征搜索产品:颜色,大小,内存大小,价格等。对于用户来说,它是一组过滤器。每个滤波器仅与一个特征相关,反之亦然。过滤器值是特征的所有可能值。用户可以在带有全文搜索结果的页面的部分页面,类别中看到过滤器。当用户选择一个值时,该过滤器被认为是活动的。



多面过滤器行为 



简而言之,一个过滤器过滤产品,并在其他过滤器中过滤选择选项。 



过滤产品



这很容易。用户选择了:



  1. 一个价值,看到与价值相匹配的产品;
  2. 在一个过滤器中有多个值,可以看到至少匹配一个的产品;
  3. 几个过滤器中的值,查看与每个过滤器中的值相匹配的产品。


用布尔代数来说:过滤器之间有一个逻辑``与'',过滤器中的值之间有一个逻辑``或''简单的逻辑。 



过滤其他过滤器中的选择



“嗯...那里有什么选项-显示,没有-隐藏什么”-这就是企业描述过滤器行为的方式。听起来合乎逻辑。实际上,它的工作方式如下:



  1. 转到“电话”部分,按特征查看过滤器:品牌,对角线,记忆。每个过滤器都包含值。 
  2. . . 1.
  3. . , . , 2. 
  4. . . , 3.
  5. «» . 3 ..


过滤器值的数量取决于产品的数量:具有不同特征值的产品越多,过滤器中的值就越多。当用户选择品牌时,用户会减少其余过滤器的选择产品数量。这导致值列表的更新。



这引起了一个通用规则:从产品的选择中检索过滤器值,这是由其余的有源过滤器形成的。



每个有源滤波器都有自己的产品选择。



如果我们有N个过滤器,并且:



  • 不活跃,则样本是常规的。所有过滤器都相同,并且匹配搜索结果;
  • M是活动的,并且M <N,则样本数为M +1,其中1是应用了所有有源滤波器的样本。所有无效过滤器均相同,并且与搜索结果一致;
  • 激活M,并且N = M,则样本数为N。每个过滤器都有自己的样本。


最终,当用户选择构面过滤器值时,会发生以下情况: 



  1. 形成对商品的搜索选择; 
  2. 从搜索选择中检索无效过滤器的值;
  3. 对于每个有源滤波器,形成一个新样本并从中提取有源滤波器的新值。


出现了问题-如何在实践中实施?



Elasticsearch(ES)实施



产品特性不是通用的,因此在这里您将找不到用于存储产品或现成查询的现成索引结构。相反,将有指向文档的链接,这些文档解释了如何构建“正确的”索引并自行查询。“正确”-根据我的经验和知识。 



文本框的“正确”类型



在ES中,我们对2种数据类型感兴趣: 



  • 文本全文搜索。此类型的字段不能用于精确比较,排序和汇总;
  • 精确比较,排序和聚合操作中涉及的字符串的关键字


ES解析文本字段中的值并构建用于全文本搜索的字典。关键字字段中的值被索引为已接收。汇总和排序仅适用于关键字字段。



两种情况下,用户都使用特征:全文搜索和通过过滤器。ES不允许指定2种类型单场,但提供其他解决方案:



领域 

PUT my_index
{
  «mappings»: {
    «properties»: {
      «some_property»: { 
        «type»: «text», // 1
        «fields»: { // 2
          «raw»: { 
            «type»: «keyword»
          }
        }
      }
    }
  }
}


  1. 我们将产品特征声明为文本类型的字段  。 
  2. 使用fields参数,创建一个类型为keyword的子虚拟字段虚拟的,因为它存在于索引中而不在产品描述中。ES会在收到数据后自动将数据保存到子字段。


因此,对于每个特征。  



在进行精确比较,排序和聚合操作的查询中,必须使用类型为keyword的子虚拟字段在示例中,这是some_property.raw对于文本搜索-父级。



copy_to

PUT my_index
{
  «mappings»: {
    «properties»: {
      «all_properties»: { // 1
        «type»: «text»
      },      «some_property_1»: {
        «type»: «keyword»,
        «copy_to»: «all_properties» // 2 
      },
      «some_property_2»: {
        «type»: «keyword»,
        «copy_to»: «all_properties»
      }
    }
  }


  1. 使用索引中的文本类型创建一个虚拟字段。    
  2. 使用copy_to参数将每个特征声明为  关键字使用参数值指定虚拟字段。保存文档时,ES会将所有特征的值复制到虚拟字段。 


为了进行精确比较,排序和汇总操作,您需要使用特征字段进行文本搜索-一个具有所有特征值的字段。



两种方法都在索引中创建原始文档结构中不存在的其他字段。因此,要创建查询,您需要知道索引的结构。



我更喜欢copy_to选项然后,要构建全文搜索查询,只需知道一个具有所有特征值的副本的字段即可。 



询价 



搜索产品 



假设索引结构与copy_to变体中的相同对于ES中的全文搜索,使用match结构,用于与多面过滤器的值进行比较-术语查询。  布尔查询将构造合并为一个查询。将会是这样的:



{
  «query» : { 
    «bool»: {
      «must»: {
        «match»: { 
          «virtual_field_for_fulltext_searching»: {
            «query»: «some text»
          }
        }
      },
      «filter»: { 
        «must»: [
           {«property_1»: [ «value_1_1», …, «value_1_n»]},
          … 
           {«property_n»: [ «value_n_1», …, «value_n_m»]}
        ]
      }
    }
  }
}


query.bool.must.match主要全文搜索 

查询query.bool.filter过滤器可优化主查询。必须在内部意味着过滤器之间的逻辑“与”。每个过滤器中的值数组是布尔值或。   



对于过滤器值



术语聚集子句基团的产物通过特性值,并计算每个组中的数量。此操作称为聚合。困难在于,对于每个有源滤波器,  术语聚合必须在其他有源滤波器形成的产品选择上执行。对于无效过滤器-选择与搜索结果匹配的过滤器。筛选器聚合构造使您可以为每个聚合创建单独的选择,并将操作“打包”到一个查询中。



请求结构将如下所示: 

{
  «size»: 0,
  «query» : { 
    «bool»: {
      «must»: {
        «match»: {
          «field_for_fulltext_searching»: {
            «fuzziness»: 2,
            «query»: «some text»
          }
        }
      },
      «filter»: {
      
      }
    }
  },
  «aggs» : {
    «inavtive_filter_agg» : {
      «filter» : {        … 
      },
      «aggs»: {
        «some_inavtive_filter_subagg»: { 
          «terms» : {
            «field» : «some_property»
          }
        },
        ... 
        «some_other_inavtive_filter_subagg»: {
          «terms» : {
            «field» : «some_other_property»
          }
        }
      }
      
    }, 
    «active_filter_1_agg» : {
       «filter»: {
         …        },
       «aggs»: {
          «active_filter_1_subagg»: {
             «terms» : {
                «field»: «property_1»
             }
          }
       }
    },
    …,  
    «active_filter_N_agg» : {
       «filter»: {
         …        
      },
       «aggs»: {
          «active_filter_N_subagg»: {
             «terms» : {
                «field»: «property_N»
             }
          }
       }
    }
  }
}


query.bool-主查询,过滤操作在其上下文中执行。它包括: 

  • 匹配-请求全文搜索;
  • 过滤器-按与方面过滤器无关的特征进行过滤,并且必须存在于任何子集中。如果您始终只想显示库存产品或仅显示可见产品,则可以按in_stock,is_visible进行过滤。


aggs.inavtive_filter_agg-非活动构面过滤器的聚合包括:

  • 过滤器-  由活动的构面过滤器形成的特征条件。与主要查询一起,形成了商品选择,在该商品上执行了本节的子汇总; 
  • aggs是每个不活动过滤器的命名聚合的对象。 


aggs.active_filter_1_agg-汇总第一个活动构面过滤器的值。每种设计都与一个方面过滤器相关联。由组成: 

  • 过滤器-由主动小平面过滤器形成的特征所构成的条件,当前的除外。它与主查询一起形成一个商品选择,在此商品上执行本节的子汇总;
  • aggs-根据当前活动的构面滤镜的特征来自一个聚合的对象。 


重要的是指定“ size”:0,否则您将获得与主查询匹配且没有聚合的产品列表。 



最终 



收到两个请求:



  1. 对于搜索结果,返回要显示给用户的产品;
  2. 对于过滤器值,执行汇总,返回过滤器值以及具有该值的产品数量。


每个请求都是独立的,因此最好异步执行它们。   



PS我承认,有更多“正确”的方法和工具可以解决分面搜索的问题。谢谢您提供其他信息和评论中的示例。



All Articles