Faceted Search-Navigation where counts should not be updated for the siblings of a group when it is active

Hello,

(data to play, at the end)

I've been reading about faceted navigation and filtering and how it can be applied to ecommerce apps.

So far I've managed to get the bellow example working.

Facet Group 1

  • Facet Option 1 (counts)
  • Facet Option 2 (counts)
  • ...
  • Facet Option Nth (counts)

Facet Group 2

  • Facet Option 1 (counts)
  • Facet Option 2 (counts)
  • ...
  • Facet Option Nth (counts)

Facet Group 3

  • Facet Option 1 (counts)
  • Facet Option 2 (counts)
  • ...
  • Facet Option Nth (counts)

My problem is that I always want all options to show and have only the counts change depending of what is selected, while also not changing the counts within a group after selecting siblings in that group. I'll throw some visual examples bellow.

Example Initial state without selections

RAM

  • 4GB (400)
  • 6GB (200)
  • 8GB (100)
  • >8GB (40)

Storage

  • 64GB (400)
  • 128GB (300)
  • 256GB (30)
  • >256GB (10)

Example when selecting RAM

RAM

  • 4GB (400)
  • 6GB (200)
  • 8GB (100) (selected, but notice the rest of the sibling option counts remain unchanged)
  • >8GB (40)

Notice now, how other groups should recalculate their counts when there are facet optons selected in sibling groups

Storage

  • 64GB (0) (from 400 initially)
  • 128GB (60) (from 300 initially)
  • 256GB (30)
  • >256GB (10)

How can this be achieved?
My idea is that, the selected option should not be filtered within the sub aggregation group it belongs, but applied to the rest. Does this make sense? in pseudo: add filter in agg only if it is from other group

But I haven't been able to get desired results.

Here is a sample index and some data to play with

PUT products
{
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "fields": {
          "raw": {
            "type": "keyword"
          }
        }
      },
      "manufacturer": {
        "type": "text",
        "fields": {
          "raw": {
            "type": "keyword"
          }
        }
      },
      "attributes": {
        "type": "nested",
        "properties": {
          "attribute_name": {"type": "keyword"},
          "attribute_value": {"type": "keyword"}
        }
      }
    }
  }
}

POST _bulk
{"index": {"_index": "products", "_id": 1}}
{"title":"Huawei P Smart Z (64GB) Midnight Black","manufacturer":"Huawei","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"64GB"}]}
{"index": {"_index": "products", "_id": 2}}
{"title":"Apple iPhone 12 Pro (128GB) Gold","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"6GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 3}}
{"title":"Apple iPhone 11 (64GB) Yellow","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"64GB"}]}
{"index": {"_index": "products", "_id": 4}}
{"title":"Samsung Galaxy Note 20 Ultra (256GB) Mystic Bronze","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"12GB"},{"attribute_name":"Storage","attribute_value":"256GB"}]}
{"index": {"_index": "products", "_id": 5}}
{"title":"Samsung Galaxy S20 (128GB) Cosmic Gray","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"8GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 6}}
{"title":"Samsung Galaxy S21+ 5G (128GB) Phantom Violet","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"8GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 7}}
{"title":"Apple iPhone 12 Mini (128GB) Black","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 8}}
{"title":"Huawei Nova 5T (6GB/128GB) Black","manufacturer":"Huawei","attributes":[{"attribute_name":"RAM","attribute_value":"6GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 9}}
{"title":"Apple iPhone XR (64GB) White","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"3GB"},{"attribute_name":"Storage","attribute_value":"64GB"}]}
{"index": {"_index": "products", "_id": 10}}
{"title":"Samsung Galaxy A21s (128GB) Black","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 11}}
{"title":"Samsung Galaxy S20 5G (128GB) Cloud White","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"12GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 12}}
{"title":"Samsung Galaxy A21s (32GB) Red","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"3GB"},{"attribute_name":"Storage","attribute_value":"32GB"}]}
{"index": {"_index": "products", "_id": 13}}
{"title":"Apple iPhone 12 Mini (256GB) Black","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"256GB"}]}
{"index": {"_index": "products", "_id": 14}}
{"title":"Apple iPhone 11 Pro (256GB) Gold","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"256GB"}]}
{"index": {"_index": "products", "_id": 15}}
{"title":"Apple iPhone 8 Plus (64GB) Silver","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"3GB"},{"attribute_name":"Storage","attribute_value":"64GB"}]}
{"index": {"_index": "products", "_id": 16}}
{"title":"Samsung Galaxy Note 8 (64GB) Midnight Black","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"6GB"},{"attribute_name":"Storage","attribute_value":"64GB"}]}
{"index": {"_index": "products", "_id": 17}}
{"title":"Apple iPhone XS Max (256GB) Space Gray","manufacturer":"Apple","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"256GB"}]}
{"index": {"_index": "products", "_id": 18}}
{"title":"Samsung Galaxy S20 FE (128GB) Cloud Lavender","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"6GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 19}}
{"title":"Samsung Galaxy A31 (128GB) Prism Crush Blue","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"4GB"},{"attribute_name":"Storage","attribute_value":"128GB"}]}
{"index": {"_index": "products", "_id": 20}}
{"title":"Samsung Galaxy A21s (32GB) White","manufacturer":"Samsung","attributes":[{"attribute_name":"RAM","attribute_value":"3GB"},{"attribute_name":"Storage","attribute_value":"32GB"}]}

GET products/_search?size=0
{
  "aggs": {
    "facet": {
      "nested": {
        "path": "attributes"
      },
      "aggs": {
        "facet_group": {
          "terms": {
            "field": "attributes.attribute_name"
          },
          "aggs": {
            "values": {
              "terms": {
                "field": "attributes.attribute_value"
              }
            }
          }
        }
      }
    }
  }
}

Any ideas ?

Hey,

so that is quite a common e-commerce requirement, however it requires some thought and proper queries.

Usually an ecommerce query looks like this:

For all documents the following applies:

  1. Filter by category:a
  2. Filter by brand:b
  3. Filter by price<200

For category facet: filter 2) and 3) apply
For brand facet: filter 1) and 3) apply
For price facet: filter 1) and 2) apply

This means you need to have a different filter for each facet - which can be done with a facet filter aggregation (note, filter, not filters). See Filter aggregation | Elasticsearch Guide [8.1] | Elastic

And for the hits, which need all the three filters applied, you could go with a post filter do run this within a single request.

I have however seen, that there are two requests, one for the filter part of your website and one for the product/document part of the website.

Hope that makes sense!

--Alex

We are currently working exactly on the same feature and found it was much easier to handle it in our code with some caching of previous aggregations keys, than trying to achieve it with a single Elastic request.

So basically you trigger your search with your initial aggregations, then when a user filters on one value, you search with the new filter and update your cached values accordingly (if an aggregation value is missing from the results it means the value is 0 - else update the values of other groups).

Thus you can display the items which don't have results like in your example: Storage 64Gb (0).

if with 'our code' you mean on the client side, than that may work for a certain set of documents, so it's fine to go that route.

In general, don't be afraid of executing more than one request to satisfy a single page result, as long as it solves your problem. May even help you with caching on the Elasticsearch side, if you split certain requests (but just wild assumptions here without knowing the implementation details).

Hey, thanks for the reply. I was thinking to implement it that way, by doing a single request and filter every aggs by the selected filters from other groups.

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.