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

26

我们在一个新项目中受到了这篇文章的启发,链接为http://project-a.github.io/on-site-search-design-patterns-for-e-commerce/#generic-faceted-search。我们采用了“facet”结构。虽然我已经按照文章的描述使其发挥作用,但在选择特征时遇到了问题。希望有人能够给一个提示,让我不必重新将所有聚合计算分开。

问题基本上是我们使用单个聚合在同时计算所有“特征”时,但在添加筛选(例如检查品牌名称)时,返回聚合时会“移除”所有其他品牌。我想要的基本上是,在计算其他特征时应该将该品牌用作过滤器,但在计算品牌聚合时不应该加以考虑。这是必要的,以便用户可以选择多个品牌。

查看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个值的属性(有机,无香味)和具有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"
                      }
                    ]
   } } } } } } } }

现在,如果我选择属性为“有机”的品牌为“Adidas”,那么我将构建相同的聚合,但需要应用这两个约束条件的过滤器(这就是出问题的地方...):

  "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"
                      }
                    ]
   } } } } } } } }

我能看到这个模型向前发展的唯一方式是计算每个选择的方面的聚合,并且以某种方式合并结果。但这似乎非常复杂,而且有点违背了文章中描述的模型的初衷,所以我希望有更简洁的解决方案,并且希望有人能够给出一些尝试的提示。

2个回答

31
我能看到这个模型推进的唯一方式就是计算每个选定维度的聚合并以某种方式合并结果。
这完全正确。如果选择了一个维度(如品牌),则无法使用全局品牌筛选器,如果您还想获取其他品牌进行多重选择。您可以对所选维度应用所有其他筛选器,并对未选择的维度应用所有筛选器。因此,您将有 n+1 个单独的聚合结果,其中一个是所有维度的,其余的是所选维度的。
在您的情况下,查询可能是这样的:
{
  "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"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

这个查询看起来非常庞大而可怕,但是只需要几十行代码就可以生成这样的查询。当解析查询结果时,你需要先解析普通聚合(使用所有过滤器的聚合),然后解析特殊的分面聚合。从上面的例子中,首先解析agg_attr_strings_filter的结果,但这些结果还将包含BrandProperty的聚合值,应该用special_agg_propertyspecial_agg_brand的聚合值覆盖它们。此外,这个查询是高效的,因为Elasticsearch在缓存单独的过滤器子句方面做得很好,因此在查询的不同部分应用相同的过滤器应该很便宜。

实际上没有办法避免你需要对不同的分面应用不同的过滤器,并同时具有不同的查询过滤器。如果你需要支持“正确的电子商务分面行为”,那么你将会有一个复杂的查询:)

免责声明:我是提到文章的共同作者。


3
非常感谢您对此的回答和建议。很抱歉回复晚了——我得了流感。您的回答非常有道理,只是在考虑时似乎过于冗长,因此获得您的想法真的很好,特别是关于ES性能方面的问题。如果有其他人想做同样的事情,请仔细阅读“hakaa”的答案,它提供了我希望自己一开始更仔细阅读的重要提示。 :) - Reonekot
2
嗨,我知道这是一个非常旧的帖子,但是我想知道是否有人可以为我澄清:虽然这个解决方案似乎是可行的,但我对ElasticSearch的了解非常有限,但我正在快速学习:如果这些多聚合是在结果集上计算出来的,那么它们实际上是如何按描述的方式工作的呢?例如,如果我有一个书籍“装订”(平装、精装等)的列表,并通过“平装”筛选我的结果,那么如果结果决定了第一次的聚合,我如何还能获得其他装订的聚合,就像描述的那样呢?该聚合应该处于不同的范围内吗?它能在单个查询中完成吗? - Cristian Cotovan
1
@hakaa 这是使用 post_filter 应用的吗? - Cristian Cotovan
如果你和我一样,正在犹豫要跟随哪个答案,那就用这个吧。为什么呢?因为如果你想展示某个选择的所有可能选项,你必须有N+1个查询。可以在这里找到参考资料:https://medium.com/swlh/faceted-navigation-for-e-commerce-with-elasticsearch-dbf82bdeb7d4 - Tobias Sarnow

11
问题出在你在聚合内添加了 PropertyOrganic 的过滤器,因此你选择的维度越多,你得到的结果将越受限。在这篇文章中,他们使用的 filter 实际上是 post_filter,这两个名称直到最近都被允许使用,但是 filter 已被删除,因为那会导致歧义。
你需要做的是将该过滤器移至聚合之外的 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中省略过滤器,则它将始终显示完整查询的聚合,这也不是我想要的。当选择其他过滤器时,过滤器应更改。 - Reonekot
例如,查看我上面链接的网站没有筛选器,“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) - Reonekot
1
你可以保留post_filter(以确保结果集与所选分面匹配),然后在聚合中,你可以使用一个带有过滤器的聚合(以了解仍然可用的分面与所选分面匹配的情况,我称之为agg_attr_strings_filtered),另一个聚合则没有过滤器(以展示完整的可用分面集,以防用户想要选择其他分面,我称之为agg_attr_strings_full)。我已更新我的答案。 - Val
我仍然认为它不能完全满足我的需求。比如说,如果我选择了一个属性和一个品牌,它只应该显示具有该属性的品牌,反之亦然,应该从所选品牌的产品中显示属性,并且我不知道如何将两个聚合的数据合并以实现这一点。无论如何,非常感谢您提供的想法和时间!我认为我必须将每个属性重写到自己的聚合中,以获得更多对查询过滤部分的控制。 - Reonekot
1
@rs82uk 是指 post_filter 吗? - Val
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接