在线商店的分面在首次选择筛选条件前看似简单。
在目录中,它们是导航的一部分。已选颜色不应从列表中消失。其他颜色应保持可用:用户可能想切换到其他颜色或扩大选择范围。没有匹配商品的选项在显示为不可用时更有用。在单个分面内,通常应用 OR:红色或蓝色。跨不同分面,则是 AND:同时按品牌、颜色和尺寸筛选。
首次选择后的部分开始变得复杂。想象一个产品目录,用户已选择了品牌、颜色和尺寸。主结果集必须保持窄范围,仅显示同时匹配这三个条件的产品。但筛选面板遵循不同规则。它需要保留已选颜色,同时显示在相同品牌和尺寸下可切换的颜色。
如果每个分面继承主查询的所有筛选条件,它会迅速退化为已选值。如果每个分面的筛选条件都需手动重建,最终会出现为颜色、尺寸、品牌、可用性、卖家以及每个其他属性创建单独查询分支的情况。
Manticore Search 25.12.0 引入了 facet_filter_mode,将此行为移入分面 API。现在查询可以描述商店筛选面板应如何行为,而无需应用为每个分面手动构建几乎相同的查询。
电商分面需要什么
对于筛选面板,回答“有多少产品有此值?”是不够的。它需要界面可直接使用的状态:
- 已选值保持可见且可快速清除;
- 同一分面中的其他值保持可用,因为它们通过
IN (...)扩大筛选范围; - 其他分面中的值指示选择它们是否会返回至少一个产品;
- 计数保持可预测:界面知道数字是指当前结果集还是更宽的选择范围;
- 在长品牌列表、类别、价格范围、SEO 规则和查询分析中搜索仍然重要;它们是搜索设计的独立部分。
facet_filter_mode 处理此问题最常见的部分:在用户已选择多个筛选条件后重新计算分面。
变更内容
分面现在有三种筛选继承模式:
| 模式 | 行为 |
|---|---|
strict |
将主查询的所有筛选条件应用于分面。这是之前的行为,保持为默认值。 |
auto |
应用除该分面自身筛选条件外的所有筛选条件,并添加 status:已选值获得 selected,其他值获得 available。 |
max |
在宽泛的基础结果集上计算桶,并添加 status 以区分已选、可用和不可用的值。 |
你也可以手动控制:
-
ALL FILTERS- 将所有筛选条件应用于分面; -
FILTERS color_id, size_id- 仅应用列出的筛选条件; -
EXCLUDE FILTERS color_id- 应用除列出条件外的所有筛选条件; -
ZEROES- 从 Manticore Search 27.3.0 开始,在 SQLmax模式下,即使当前分面的count(*)为0,也保留宽泛max范围中的桶。
相同选项在 JSON API 中通过 facet_filter_mode、mode、filters、exclude_filters 和 zeroes: true 可用。
简而言之,strict 回答“当前结果集中有什么?”,auto 回答“如果此特定筛选条件改变会怎样?”,max 显示带有 status 字段的宽泛桶列表:selected、available 或 unavailable。
最小示例
考虑一个包含品牌、颜色、尺寸和 SKU 的小型目录:
CREATE TABLE products(
title text,
brand_id int,
color_id int,
color_name string,
size_id int,
size_name string,
sku string
);
INSERT INTO products(id,title,brand_id,color_id,color_name,size_id,size_name,sku) VALUES
(1,'p1',7,1,'red',10,'small','sku1'),
(2,'p2',7,1,'red',20,'large','sku2'),
(3,'p3',7,2,'blue',10,'small','sku3'),
(4,'p4',7,3,'green',30,'xlarge','sku4'),
(5,'p5',8,1,'red',10,'small','sku5'),
(6,'p6',8,4,'black',20,'large','sku6'),
(7,'p7',9,5,'white',10,'small','sku7');
进入全屏模式 退出全屏模式
用户已选择:
brand_id=7 AND color_id=1 AND size_id=10
进入全屏模式 退出全屏模式
在这些条件下,结果集包含一个产品:p1。让我们看看分面会发生什么。
strict:之前的行为
在没有额外选项的情况下,Manticore Search 使用 strict:每个分面接收主查询的所有筛选条件。
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
FACET color_id ORDER BY color_id ASC
FACET size_id ORDER BY size_id ASC;
进入全屏模式 退出全屏模式
响应:
+----------+----------+
| color_id | count(*) |
+----------+----------+
| 1 | 1 |
+----------+----------+
+---------+----------+
| size_id | count(*) |
+---------+----------+
| 10 | 1 |
+---------+----------+
进入全屏模式 退出全屏模式
这是一个准确的 SQL 响应,但对于在线商店通常过于狭窄。用户只看到已选的 color_id=1 和 size_id=10。看起来好像没有其他选项,尽管数据包含同一品牌和尺寸但不同颜色(color_id=2)的产品,以及同一品牌和颜色但不同尺寸(size_id=20)的另一个产品。
auto:分面忽略自身筛选条件
auto 模式保留除当前计算的分面筛选条件外的所有筛选条件。
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
OPTION facet_filter_mode='auto'
FACET color_id ORDER BY color_id ASC
FACET size_id ORDER BY size_id ASC;
进入全屏模式 退出全屏模式
响应:
+----------+----------+-----------+
| color_id | count(*) | status |
+----------+----------+-----------+
| 1 | 1 | selected |
| 2 | 1 | available |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status |
+---------+----------+-----------+
| 10 | 1 | selected |
| 20 | 1 | available |
+---------+----------+-----------+
进入全屏模式 退出全屏模式
发生了什么:
-
FACET color_id应用了brand_id=7 AND size_id=10,但未应用color_id=1; -
FACET size_id应用了brand_id=7 AND color_id=1,但未应用size_id=10。
这为界面提供了替代值,而无需为每个分面单独查询。已选桶标记为 selected,同一分面中的其他值标记为 available:它们可以通过 IN (...) 作为更宽的选择添加到当前筛选条件中。
max:带有状态的宽泛桶列表
auto 仅显示特定分面当前筛选范围内的值。max 更进一步:在基础结果集上计算桶,并为界面单独标记其状态。
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
OPTION facet_filter_mode='max'
FACET color_id ORDER BY color_id ASC
FACET size_id ORDER BY size_id ASC;
进入全屏模式 退出全屏模式
响应:
+----------+----------+-----------+
| color_id | count(*) | status |
+----------+----------+-----------+
| 1 | 3 | selected |
| 2 | 1 | available |
| 3 | 1 | available |
| 4 | 1 | available |
| 5 | 1 | available |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status |
+---------+----------+-----------+
| 10 | 4 | selected |
| 20 | 2 | available |
| 30 | 1 | available |
+---------+----------+-----------+
进入全屏模式 退出全屏模式
status 直接来自 Manticore Search:
-
selected- 该值已存在于此分面的筛选条件中; -
available- 该值可以被选择;对于同一分面中的另一个值,这通过IN (...)扩大筛选范围; -
unavailable- 该值存在于分面的宽泛结果集中,但选择它在当前筛选条件下不会返回任何文档。
在此示例中,color_id=1 在目录中出现在三个产品中,因此其在 max 中的计数为 3。它被标记为 selected,因为它已参与筛选。其他颜色被标记为 available:如果用户选择其中一个,颜色筛选会变得更宽,例如 color_id IN (1,2)。当宽泛结果集中的桶在当前筛选条件下无法返回文档时,会出现不可用值;下面的 sku 示例演示了这种情况。
手动设置筛选范围
全局 facet_filter_mode 覆盖了大多数常见情况,但有时不同分面需要不同行为。例如,颜色可以严格受所有筛选条件约束,尺寸可以使用 max,SKU 可以仅使用颜色和尺寸计算,品牌可以在没有颜色筛选条件的情况下计算。
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
OPTION facet_filter_mode='max'
FACET color_id ALL FILTERS ORDER BY color_id ASC
FACET size_id ORDER BY size_id ASC
FACET sku FILTERS color_id, size_id ORDER BY sku ASC
FACET brand_id EXCLUDE FILTERS color_id ORDER BY brand_id ASC;
进入全屏模式 退出全屏模式
响应:
+----------+----------+-----------+
| color_id | count(*) | status |
+----------+----------+-----------+
| 1 | 1 | selected |
+----------+----------+-----------+
+---------+----------+-----------+
| size_id | count(*) | status |
+---------+----------+-----------+
| 10 | 4 | selected |
| 20 | 2 | available |
| 30 | 1 | available |
+---------+----------+-----------+
+------+----------+-------------+
| sku | count(*) | status |
+------+----------+-------------+
| sku1 | 1 | available |
| sku5 | 1 | unavailable |
+------+----------+-------------+
+----------+----------+----------+
| brand_id | count(*) | status |
+----------+----------+----------+
| 7 | 2 | selected |
+----------+----------+----------+
进入全屏模式 退出全屏模式
如何阅读此查询:
-
FACET color_id ALL FILTERS应用所有筛选条件并仅返回已选颜色; -
FACET size_id继承查询级别的max模式; -
FACET sku FILTERS color_id, size_id仅应用颜色和尺寸筛选条件; -
FACET brand_id EXCLUDE FILTERS color_id应用除颜色外的所有筛选条件。
当筛选面板同时包含常规和技术分面,且某些值需要根据特殊规则计算时,此模式很有用。
ZEROES:max 中的零计数桶
当计数需要保持严格但值列表需要保持宽泛时,ZEROES 很有用。它与 max 配合使用,无论是通过 OPTION facet_filter_mode='max' 还是通过单个分面上的 MODE max。
在没有 ZEROES 的情况下,带有 ALL FILTERS 的分面仅显示通过所有筛选条件的桶:
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
OPTION facet_filter_mode='max'
FACET size_id ALL FILTERS ORDER BY size_id ASC;
进入全屏模式 退出全屏模式
响应:
+---------+----------+----------+
| size_id | count(*) | status |
+---------+----------+----------+
| 10 | 1 | selected |
+---------+----------+----------+
进入全屏模式 退出全屏模式
使用 ZEROES,Manticore Search 保持相同的可见计数,但从宽泛的 max 范围返回剩余桶,计数为零:
SELECT count(*)
FROM products
WHERE brand_id=7 AND color_id=1 AND size_id=10
LIMIT 0
OPTION facet_filter_mode='max'
FACET size_id ALL FILTERS ZEROES ORDER BY size_id ASC;
进入全屏模式 退出全屏模式
响应:
+---------+----------+-----------+
| size_id | count(*) | status |
+---------+----------+-----------+
| 10 | 1 | selected |
| 20 | 0 | available |
| 30 | 0 | available |
+---------+----------+-----------+
进入全屏模式 退出全屏模式
然后界面可以显示 large 和 xlarge 与已选的 small 一起:数字是指当前严格结果集,而 status 显示这些值仍然可以被选择以扩大筛选范围。
通过 JSON API 的相同查询
SQL 在这里更短,更清楚地显示了机制,但 JSON API 支持相同的方法:
POST /search
{
"table": "products",
"limit": 0,
"query": {
"bool": {
"must": [
{ "equals": { "brand_id": 7 } },
{ "equals": { "color_id": 1 } },
{ "equals": { "size_id": 10 } }
]
}
},
"facet_filter_mode": "max",
"aggs": {
"colors": {
"terms": { "field": "color_id", "size": 10 },
"sort": [ { "color_id": { "order": "asc" } } ]
},
"sizes": {
"terms": { "field": "size_id", "size": 10 },
"sort": [ { "size_id": { "order": "asc" } } ]
}
}
}
进入全屏模式 退出全屏模式
响应:
{
"took": 0,
"timed_out": false,
"hits": {
"total": 1,
"total_relation": "eq",
"hits": []
},
"aggregations": {
"colors": {
"buckets": [
{ "key": 1, "doc_count": 3, "status": "selected" },
{ "key": 2, "doc_count": 1, "status": "available" },
{ "key": 3, "doc_count": 1, "status": "available" },
{ "key": 4, "doc_count": 1, "status": "available" },
{ "key": 5, "doc_count": 1, "status": "available" }
]
},
"sizes": {
"buckets": [
{ "key": 10, "doc_count": 4, "status": "selected" },
{ "key": 20, "doc_count": 2, "status": "available" },
{ "key": 30, "doc_count": 1, "status": "available" }
]
}
}
}
进入全屏模式 退出全屏模式
在 JSON 聚合中,mode、filters、exclude_filters 和 zeroes 也可以为每个单独的聚合设置。
这与 Meilisearch、Elasticsearch 和 OpenSearch 的区别
我们在 Manticore Search、Meilisearch、Elasticsearch 和 OpenSearch 中测试了相同场景:brand_id=7、color_id=1 和 size_id=10 筛选条件处于活动状态;结果集保持严格,而分面需要显示排除自身筛选条件时出现的选项。
| 引擎 | 在单个查询中排除自身筛选条件 | 原生桶状态 | 应用仍需处理的内容 |
|---|---|---|---|
| Manticore Search | 是的,通过 auto/max
|
是的,在 auto/max 中;unavailable 仅在 max 中 |
渲染界面、SEO、分析和自定义规则。 |
| Meilisearch | 不是这种形式 | 否 | 运行额外查询并在应用中组装分面。 |
| OpenSearch | 是的,但需通过 global + filter 聚合手动操作 |
否 | 复制筛选分支并在应用中计算 status。 |
| Elasticsearch | 是的,但需通过 global + filter 聚合手动操作 |
否 | 相同:维护单独的聚合分支和客户端逻辑。 |
在 Meilisearch 中使用基本分面很容易,但类似这样的查询:
{
"filter": ["brand_id = 7", "color_id = 1", "size_id = 10"],
"facets": ["color_id", "size_id"]
}
进入全屏模式 退出全屏模式
返回已筛选结果集的计数。在我们的数据集中,这意味着只有 color_id=1 和 size_id=10。color_id=2 和 size_id=20 的替代值不会出现在此响应中。如果界面需要它们,应用必须发出额外查询并合并结果。
在 Elasticsearch 和 OpenSearch 中,类似行为可以在一个查询中构建,但每个分面需要自己的显式聚合分支。对于 color,保留 brand_id 和 size_id;对于 size,保留 brand_id 和 color_id;依此类推。这样可行,但查询会快速增长,且桶状态仍需在应用中计算。
主要区别在于此逻辑的位置。在 Manticore Search 中,它直接在分面 API 中配置;在其他系统中,它通常由几个相似的筛选树组装而成。
性能和限制
strict 保持为默认值并保留之前的行为。如果你只需要当前结果集内的分面,无需更改任何内容。
auto 通常更适合电商筛选条件:它显示每个分面内的替代值,将已选值标记为 selected,并将其他值标记为 available。
当界面需要比当前结果集更宽的价值列表并需要显示不可用选项时,使用 max。此模式成本更高:Manticore Search 在宽泛范围内计算桶,然后单独确定它们的 status。在大型数据集和多个分面时值得考虑。
还有一些限制:
- 分面的本地筛选重写仅支持与
AND组合的属性筛选条件; - 复杂的
AND/OR树不会为单个分面自动重写; - 已选值目前仅对显式值筛选条件(如
=和IN)可靠匹配; - 状态尚未为价格等范围自动计算。
对于价格、折扣和评分,最好明确定义单独的范围。例如,保留一个用于排序和滑块的数值字段,并添加 price_band 或 discount_band 作为分面。这让界面可以为预定义范围显示清晰的状态。
分面 URL 的 SEO、A/B 测试、商品规划规则和查询分析仍由应用或搜索平台负责。
实际上,当你需要替代值时从 auto 开始,当界面需要带有 selected、available 和 unavailable 状态的宽泛桶列表时切换到 max。
进一步阅读
要了解 Manticore Search 中的分面搜索入门,请从早期的 分面搜索 文章开始。它涵盖基本的 FACET 查询、排序、限制和表达式分面。
有关详细信息,请参阅 FACET 文档。
要获得分面的交互式介绍,请参加 Manticore 分面 课程。
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.