Elasticsearch - 通用构面结构 - 计算聚合与过滤器相结合

Posted

技术标签:

【中文标题】Elasticsearch - 通用构面结构 - 计算聚合与过滤器相结合【英文标题】:Elasticsearch - generic facets structure - calculating aggregations combined with filters 【发布时间】:2017-05-13 04:35:28 【问题描述】:

在我们的一个新项目中,我们受到这篇文章 http://project-a.github.io/on-site-search-design-patterns-for-e-commerce/#generic-faceted-search 的启发,因为我们做了我们的“平面”结构。虽然我已经让它在文章描述的范围内工作,但我在选择方面时遇到了问题。我希望有人可以给出一些尝试的提示,这样我就不必再次将我们所有的聚合重做为单独的聚合计算。

问题基本上是我们使用单个聚合来一次计算所有“方面”,但是当我添加一个过滤器(fx。检查品牌名称)时,它会在返回时“删除”所有其他品牌聚合体。我基本上想要的是它应该在计算其他方面时使用该品牌作为过滤器,而不是在计算品牌聚合时。这是必要的,因此用户可以选择多个品牌。

查看https://www.contorion.de/search/Metabo_Fein/ou1-ou2?q=Winkelschleifer&c=bovy(上面文章中描述的网站),我选择了“Metabo”和“Fein”制造商(Hersteller),展开 Hersteller 菜单它显示所有制造商,而不仅仅是那些制造商选择。所以我知道这是有可能的,我希望有人能提示如何编写聚合/过滤器,这样我就能得到“正确的电子商务方面行为”。

关于 ES 中的产品,我有以下结构:(与原始文章中的相同,尽管命名为“C#'ified”)

"attributeStrings": [
    
        "facetName": "Property",
        "facetValue": "Organic"
    ,
    
        "facetName": "Property",
        "facetValue": "Without parfume"
    ,
    
        "facetName": "Brand",
        "facetValue": "Adidas"
    
]

因此,上述产品有 2 个属性/方面组 - 具有 2 个值的属性(有机、无香精)和具有 1 个值的品牌(阿迪达斯)。 在没有任何过滤器的情况下,我从以下查询计算聚合:

  "aggs": 
    "agg_attr_strings_filter": 
      "filter": ,
      "aggs": 
        "agg_attr_strings": 
          "nested": 
            "path": "attributeStrings"
          ,
          "aggs": 
            "attr_name": 
              "terms": 
                "field": "attributeStrings.facetName"
              ,
              "aggs": 
                "attr_value": 
                  "terms": 
                    "field": "attributeStrings.facetValue",
                    "size": 1000,
                    "order": [
                      
                        "_term": "asc"
                      
                    ]
          

现在,如果我选择属性“有机”和品牌“阿迪达斯”,我将构建相同的聚合,但使用过滤器来应用这两个约束(这是不是有点问题...):

  "aggs": 
    "agg_attr_strings_filter": 
      "filter": 
        "bool": 
          "filter": [
            
              "nested": 
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Property"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Organic"
                          ]
                        
                      
                    ]
                  
                ,
                "path": "attributeStrings"
              
            ,
            
              "nested": 
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Brand"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Adidas"
                          ]
                        
                      
                    ]
                  
                ,
                "path": "attributeStrings"
              
            
          ]
        
      ,
      "aggs": 
        "agg_attr_strings": 
          "nested": 
            "path": "attributeStrings"
          ,
          "aggs": 
            "attr_name": 
              "terms": 
                "field": "attributeStrings.facetName",
              ,
              "aggs": 
                "attr_value": 
                  "terms": 
                    "field": "attributeStrings.facetValue",
                    "size": 1000,
                    "order": [
                      
                        "_term": "asc"
                      
                    ]
          

我可以看到这个模型的唯一方法是计算每个选定方面的聚合并以某种方式合并结果。但这似乎非常复杂,并且有点违背了文章中描述的模型的意义,所以我希望有一个更干净的解决方案,有人可以给出一些尝试的提示。

【问题讨论】:

【参考方案1】:

我能看到这个模型的唯一方法是计算每个选定方面的聚合并以某种方式合并结果。

这完全正确。如果选择了一个方面(例如 brand),那么如果您还想获取其他品牌进行多选,则不能使用全局品牌过滤器。您可以做的是在选定的方面应用所有其他过滤器,并在非选定方面应用所有 过滤器。结果,您将拥有 n+1 单独的聚合 n 选定过滤器 - 第一个用于所有方面,其余用于选定方面。

在您的情况下,查询可能如下所示:


  "aggs": 
    "agg_attr_strings_filter": 
      "filter": 
        "bool": 
          "filter": [
            
              "nested": 
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Property"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Organic"
                          ]
                        
                      
                    ]
                  
                ,
                "path": "attributeStrings"
              
            ,
            
              "nested": 
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Brand"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Adidas"
                          ]
                        
                      
                    ]
                  
                ,
                "path": "attributeStrings"
              
            
          ]
        
      ,
      "aggs": 
        "agg_attr_strings": 
          "nested": 
            "path": "attributeStrings"
          ,
          "aggs": 
            "attr_name": 
              "terms": 
                "field": "attributeStrings.facetName"
              ,
              "aggs": 
                "attr_value": 
                  "terms": 
                    "field": "attributeStrings.facetValue",
                    "size": 1000,
                    "order": [
                      
                        "_term": "asc"
                      
                    ]
                  
                
              
            
          
        
      
    ,
    "special_agg_property": 
      "filter": 
        "nested": 
          "query": 
            "bool": 
              "filter": [
                
                  "term": 
                    "attributeStrings.facetName": 
                      "value": "Brand"
                    
                  
                ,
                
                  "terms": 
                    "attributeStrings.facetValue": [
                      "Adidas"
                    ]
                  
                
              ]
            
          ,
          "path": "attributeStrings"
        
      ,
      "aggs": 
        "special_agg_property": 
          "nested": 
            "path": "attributeStrings"
          ,
          "aggs": 
            "agg_filtered_special": 
              "filter": 
                "query": 
                  "match": 
                    "attributeStrings.facetName": "Property"
                  
                
              ,
              "aggs": 
                "facet_value": 
                  "terms": 
                    "size": 1000,
                    "field": "attributeStrings.facetValue"
                  
                
              
            
          
        
      
    ,
    "special_agg_brand": 
      "filter": 
        "nested": 
          "query": 
            "bool": 
              "filter": [
                
                  "term": 
                    "attributeStrings.facetName": 
                      "value": "Property"
                    
                  
                ,
                
                  "terms": 
                    "attributeStrings.facetValue": [
                      "Organic"
                    ]
                  
                
              ]
            
          ,
          "path": "attributeStrings"
        
      ,
      "aggs": 
        "special_agg_brand": 
          "nested": 
            "path": "attributeStrings"
          ,
          "aggs": 
            "agg_filtered_special": 
              "filter": 
                "query": 
                  "match": 
                    "attributeStrings.facetName": "Brand"
                  
                
              ,
              "aggs": 
                "facet_value": 
                  "terms": 
                    "size": 1000,
                    "field": "attributeStrings.facetValue"
                  
                
              
            
          
        
      
    
  

这个查询看起来超级大而且很吓人,但是生成这样的查询可以用几十行代码完成。 解析查询结果时,需要先解析通用聚合(使用所有过滤器的聚合),然后再解析特殊的 facet 聚合。在上面的示例中,首先解析来自 agg_attr_strings_filter 的结果,但这些结果还将包含 BrandProperty 的聚合值,这些值应该被来自 special_agg_property 的聚合值覆盖,并且special_agg_brand 此外,此查询效率很高,因为 Elasticsearch 在缓存单独的过滤器子句方面做得很好,因此在查询的不同部分应用相同的过滤器应该很便宜。

但它似乎非常复杂,并且有点违背了文章中描述的模型的意义,所以我希望有一个更干净的解决方案,有人可以给出一些尝试的提示。

您需要将不同的过滤器应用于不同的方面并同时具有不同的查询过滤器,这确实是无可避免的。如果您需要支持“正确的电子商务方面行为”,您将有复杂的查询:)

免责声明:我是上述文章的合著者。

【讨论】:

非常感谢您对此的回答和意见。 (对于迟到的反馈感到抱歉——流感使我好转。)这确实完全有道理,只是在考虑它时看起来很冗长,所以很高兴能得到你的想法,也关于 ES 的性能。如果其他人也想做同样的事情,请仔细阅读“hakaa”的答案,因为它提供了我希望我一开始更仔细阅读的提示。 :) 嗨,我知道这是一个非常古老的线程,但我想知道是否有人可以为我澄清一下:虽然这个解决方案似乎合理,但我对 ElasticSearch 的了解非常有限,但我学得很快:如果在结果集上计算,这种多重聚合如何实际工作?例如,如果我有一个书籍“装订”列表(平装本、精装本等)并按“平装本”过滤我的结果,如果结果决定了 aggs,我如何还获得其他装订的装订 aggs首先? agg 应该在不同的范围内吗?可以在单个查询中完成吗? @hakaa 这是使用 post_filter 应用的吗? 如果您像我一样想知道要遵循哪个答案,请使用此答案。为什么?因为如果你想显示某个选择的所有可能选择,你必须有 N+1 个查询。可以在这里找到参考medium.com/swlh/…【参考方案2】:

问题在于您在聚合中添加了一个过滤器 PropertyOrganic inside,因此您选择的方面越多,您将越多地限制您将使用的条款得到。在那篇文章中,他们使用的 filter 实际上是 post_filter,直到最近才允许使用这两个名称,但 filter got removed 因为这会造成歧义。

您需要做的是将该过滤器移到聚合之外的 post_filter 部分,以便通过选择的任何方面正确过滤掉结果,但您的所有方面仍然可以在整个文档上正确计算设置。


  "post_filter": 
    "bool": 
      "filter": [
        
          "nested": 
            "query": 
              "bool": 
                "filter": [
                  
                    "term": 
                      "attributeStrings.facetName": 
                        "value": "Property"
                      
                    
                  ,
                  
                    "terms": 
                      "attributeStrings.facetValue": [
                        "Organic"
                      ]
                    
                  
                ]
              
            ,
            "path": "attributeStrings"
          
        ,
        
          "nested": 
            "query": 
              "bool": 
                "filter": [
                  
                    "term": 
                      "attributeStrings.facetName": 
                        "value": "Brand"
                      
                    
                  ,
                  
                    "terms": 
                      "attributeStrings.facetValue": [
                        "Adidas"
                      ]
                    
                  
                ]
              
            ,
            "path": "attributeStrings"
          
        
      ]
    
  ,
  "aggs": 
    "agg_attr_strings_full": 
      "nested": 
        "path": "attributeStrings"
      ,
      "aggs": 
        "attr_name": 
          "terms": 
            "field": "attributeStrings.facetName"
          ,
          "aggs": 
            "attr_value": 
              "terms": 
                "field": "attributeStrings.facetValue",
                "size": 1000,
                "order": [
                  
                    "_term": "asc"
                  
                ]
              
            
          
        
      
    ,
    "agg_attr_strings_filtered": 
      "filter": 
        "bool": 
          "filter": [
            
              "nested": 
                "path": "attributeStrings",
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Property"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Organic"
                          ]
                        
                      
                    ]
                  
                
              
            ,
            
              "nested": 
                "path": "attributeStrings",
                "query": 
                  "bool": 
                    "filter": [
                      
                        "term": 
                          "attributeStrings.facetName": 
                            "value": "Brand"
                          
                        
                      ,
                      
                        "terms": 
                          "attributeStrings.facetValue": [
                            "Adidas"
                          ]
                        
                      
                    ]
                  
                
              
            
          ]
        
      ,
      "aggs": 
        "nested": 
          "path": "attributeStrings"
        ,
        "aggs": 
          "attr_name": 
            "terms": 
              "field": "attributeStrings.facetName"
            ,
            "aggs": 
              "attr_value": 
                "terms": 
                  "field": "attributeStrings.facetValue",
                  "size": 1000,
                  "order": [
                    
                      "_term": "asc"
                    
                  ]
                
              
            
          
        
      
    
  

【讨论】:

我确实有一个 post_filter,其条件与聚合中的条件相同,因此在这方面结果(命中)是正确的。 (很抱歉省略了这个 - 我试图将 JSON 归结为易于管理的东西......)在 agg 中省略过滤器的问题在于,它总是会显示完整查询的聚合,这不是我想要的任何一个。如果选择其他过滤器,则过滤器应更改。 span> (很抱歉之前按 Enter 保存 - 后续):Fx。查看我上面链接的没有过滤器的站点,“Max. Scheibendurchmesser (mm)”包含:115 (9) 125 (46) 150 (10) 180 (11) 230 (20) 但选择制造商 (Hersteller) Metabo,其他聚合/过滤器更新和“Max. Scheibendurchmesser (mm)”包含:115 (3) 125 (16) 150 (5) 180 (4) 230 (5) 您可以做的是保留post_filter(以确保结果集与所选方面匹配),然后在您的聚合中您可以使用过滤器(了解哪些方面仍然可以使用已选择的方面,我称之为agg_attr_strings_filtered)和另一个没有过滤器(以显示完整的可用方面,以防用户想要选择另一个方面,我称之为agg_attr_strings_full)。我已经更新了答案 我仍然认为它不能完全满足我的需求。比如说。我选择一个属性和一个品牌,那么它应该只显示具有该属性的品牌,反之亦然来自所选品牌的产品的属性,我看不出如何合并来自两个聚合的数据来实现这一点.无论如何,非常感谢您的想法和时间!我认为我必须将每个属性重写为它自己的聚合,而不是更好地控制查询的过滤器部分。 @rs82uk post_filter 你的意思是?

以上是关于Elasticsearch - 通用构面结构 - 计算聚合与过滤器相结合的主要内容,如果未能解决你的问题,请参考以下文章

Rails 5搜索构面过滤器

使用Elasticsearch查询字段的所有唯一值

无法执行构面计数,因为没有设置构面 Meil​​isearch

将空图添加到构面,并与另一个构面结合

在 Rails 4.1 中使用 Searchkick gem 在 Elasticsearch 中设置 Facets

电商项目使用ElasticSearch定义商品索引