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

Elasticsearch - generic facets structure - calculating aggregations combined with filters

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

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

查看https://www.contorion.de/search/Metabo_Fein/ou1-ou2?q=Winkelschleifer&c=bovy(即上文所述的站点),我select编辑了“Metabo”和“Fein”制造商(Hersteller),并展开了Hersteller菜单它显示了所有制造商,而不仅仅是 selected 的制造商。所以我知道这是可能的,我希望有人能提示如何编写聚合/过滤器,所以我得到了 "correct e-commerce facet behavior".

在ES的产品上我有如下结构:(和原文章一样,只是命名上“C#化”了)

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

所以上面的产品有 2 个 attributes/facet 组 – 属性 有 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"
                      }
                    ]
   } } } } } } } }

现在,如果我 select 属性 "Organic" 和 Brand "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"
                      }
                    ]
   } } } } } } } }

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

问题是因为您在 PropertyOrganic 聚合中添加过滤器,因此您选择的方面越多,您越会限制您将获得的条款。在那篇文章中,他们使用的 filter 实际上是 post_filter, both names were allowed until recently, but 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"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    }
  }
}

The only way I can see forward with this model, is to calculate the aggregation for each selected facet and somehow merge the result.

完全正确。如果选择了一个方面(例如 brand),如果您还想获取其他品牌进行多选,则不能使用全局品牌过滤器。您可以做的是在选定方面应用所有 other 过滤器,在非选定方面应用 all filters方面。结果,您将为 n 个选定的过滤器提供 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 的结果,但这些结果还将包含 Brand属性 的聚合值应该被 special_agg_propertyspecial_agg_brand 的聚合值覆盖 此外,此查询非常高效,因为 Elasticsearch 在缓存单独的过滤器子句方面做得很好,因此在查询的不同部分应用相同的过滤器应该很便宜。

But it seems very complex and kind of defeats the point of having the model as described in the article, so I hope there's a more clean solution and someone can give a hint at something to try.

您需要对不同的方面应用不同的过滤器,同时具有不同的查询过滤器,这一事实确实没有办法解决。如果你需要支持 "correct e-commerce facet behavior" 你将有复杂的查询:)

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