Elasticsearch 商品搜索实战:五金门店应用场景
五金门店的核心痛点:同名不同规、同规不同牌、规格参数极多。本文详细展开 Elasticsearch 如何解决这些问题。
一、数据结构设计
// 五金商品索引 mapping 核心字段
{
"mappings": {
"properties": {
"product_id": { "type": "keyword" },
"name": { "type": "text", "analyzer": "ik_max_word", "fields": { "raw": { "type": "keyword" }}},
"brand": { "type": "keyword" },
"category_l1": { "type": "keyword" },
"category_l2": { "type": "keyword" },
"category_l3": { "type": "keyword" },
// ★ 规格参数(核心,同名不同规的关键)
"specs": {
"type": "nested",
"properties": {
"spec_name": { "type": "keyword" },
"spec_value": { "type": "keyword" },
"spec_unit": { "type": "keyword" }
}
},
// 扁平规格(方便直接 filter)
"size_mm": { "type": "keyword" },
"material": { "type": "keyword" },
"model": { "type": "keyword" },
"standard": { "type": "keyword" },
"unit": { "type": "keyword" },
"min_unit": { "type": "keyword" },
"price_cost": { "type": "float" },
"price_retail": { "type": "float" },
"price_trade": { "type": "float" },
"price_project": { "type": "float" },
"stock_qty": { "type": "float" },
"stock_safe": { "type": "float" },
"stock_unit": { "type": "keyword" },
"supplier_id": { "type": "keyword" },
"supplier_name": { "type": "keyword" },
"customer_ids": { "type": "keyword" },
"tags": { "type": "keyword" },
"pinyin_name": { "type": "text" },
"aliases": { "type": "text" },
"suggest": { "type": "completion" }
}
}
}
关键设计说明
| 设计点 | 方案 | 原因 |
|---|---|---|
规格用 nested |
嵌套对象 | 同类参数有多值,避免交叉污染 |
尺寸用 keyword |
字符串 | 10mm 和 10.0mm 是同一规格,精确匹配 |
品牌用 keyword |
不分词 | "世达"就是"世达",不拆 |
名称用 text+ik |
中文分词 | "开口扳手"拆为"开口/扳手" |
| 拼音单建字段 | pinyin_name |
支持拼音首字母搜索 sld → 世达 |
| 别名字段 | aliases |
"老虎钳"="卡簧钳"="钢丝钳" |
| 客户-商品绑定 | customer_ids |
赊销客户快速查到自己的历史价 |
二、核心搜索场景
场景1:同名不同规 — "这个牌子我要30的,另一个牌子我要19的"
典型对话: 客户要"世达的 10mm 开口扳手"和"史丹利的 19mm 梅花扳手"
// 搜索:世达 开口扳手 10mm
GET /hardware/_search
{
"query": {
"bool": {
"must": [
{ "term": { "brand": "世达" }},
{ "match": { "name": "开口扳手" }}
],
"filter": [
{ "term": { "size_mm": "10" }},
{ "term": { "in_stock": true }}
]
}
},
"sort": [{ "price_retail": "asc" }]
}
// 搜索:史丹利 19mm 梅花扳手
GET /hardware/_search
{
"query": {
"bool": {
"must": [
{ "term": { "brand": "史丹利" }},
{ "match": { "name": "梅花扳手" }}
],
"filter": [
{ "term": { "size_mm": "19" }}
]
}
}
}
关键设计:
size_mm作为 keyword 而非数值类型,因为 10mm 和 10.0mm 在五金里是同一个东西,keyword 精确匹配避免浮点误差。
场景2:规格自由组合搜索 — "我要一台能切割 30mm 钢管的切割机"
GET /hardware/_search
{
"query": {
"bool": {
"must": { "match": { "name": "切割机" }},
"filter": [
{ "range": { "spec_max_cut": { "gte": 30 }}}
]
}
},
"highlight": {
"fields": {
"name": {},
"specs.spec_value": {}
}
}
}
场景3:多规格联合查询(Nested)— "304不锈钢 M10 牙距6mm 螺栓"
// ★ nested 查询 — 必须同时满足多个规格条件
GET /hardware/_search
{
"query": {
"bool": {
"must": [
{ "match": { "name": "螺栓" }},
{
"nested": {
"path": "specs",
"query": {
"bool": {
"must": [
{ "term": { "specs.spec_name": "材质" }},
{ "term": { "specs.spec_value": "304不锈钢" }}
]
}
}
}
},
{
"nested": {
"path": "specs",
"query": {
"bool": {
"must": [
{ "term": { "specs.spec_name": "牙距" }},
{ "term": { "specs.spec_value": "6mm" }}
]
}
}
}
},
{
"nested": {
"path": "specs",
"query": {
"bool": {
"must": [
{ "term": { "specs.spec_name": "规格" }},
{ "term": { "specs.spec_value": "M10" }}
]
}
}
}
}
]
}
}
}
nested vs object: 五金商品同一个参数名会出现多次(如一颗螺栓同时有"材质"、"规格"、"牙距"三个参数),必须用
nested才能正确交叉查询,用object会串数据。
场景4:同款多品牌横向对比 — "所有品牌的 M10 膨胀螺栓,列出价格和库存"
GET /hardware/_search
{
"query": {
"bool": {
"must": { "match": { "name": "膨胀螺栓" }},
"filter": [
{ "term": { "category_l2": "螺栓" }},
{
"nested": {
"path": "specs",
"query": {
"bool": {
"must": [
{ "term": { "specs.spec_name": "规格" }},
{ "term": { "specs.spec_value": "M10" }}
]
}
}
}
}
]
},
"aggs": {
"品牌及价格": {
"terms": { "field": "brand", "size": 20 },
"aggs": {
"最低价": { "min": { "field": "price_retail" }},
"最高价": { "max": { "field": "price_retail" }},
"平均价": { "avg": { "field": "price_retail" }},
"库存总量": { "sum": { "field": "stock_qty" }},
"在售品牌": { "top_hits": { "size": 1, "_source": ["name", "price_retail", "stock_qty"] }}
}
}
}
}
}
输出结果:各品牌的 M10 膨胀螺栓最低价/最高价/库存量,一目了然方便客户比价。
场景5:工程报价单查询 — "帮我查一下这个工地的材料清单"
// 客户是某工程方,有专属工程价
GET /hardware/_search
{
"query": {
"bool": {
"must": [
{ "terms": { "product_id": ["P001", "P023", "P088", "P104"] }}
]
}
},
"_source": ["product_id", "name", "brand", "model",
"price_cost", "price_project", "stock_qty"],
"sort": [{ "price_project": "asc" }]
}
场景6:赊销客户历史采购查询 — "刘总上次拿的货是什么价"
// 查找某个客户历史采购过哪些商品(赊销场景)
GET /hardware/_search
{
"query": {
"bool": {
"filter": [
{ "term": { "customer_ids": "CUST_20240001" }}
]
}
},
"sort": [{ "_timestamp": "desc" }],
"size": 50,
"_source": ["product_id", "name", "brand", "model",
"price_trade", "last_purchase_date"]
}
场景7:模糊搜索 + 品牌纠错 — "sld 的 S2 型螺丝刀"
// 拼音首字母 + 型号 + 同义词
GET /hardware/_search
{
"query": {
"bool": {
"should": [
{ "match": { "pinyin_name": "sld" }},
{ "match": { "aliases": "sld" }},
{ "term": { "brand": "世达" }},
{ "term": { "model": "S2" }}
],
"minimum_should_match": 2
}
}
}
场景8:库存告警 + 智能补货建议
// 聚合:库存低于安全库存的商品,按类别统计
GET /hardware/_search
{
"query": {
"bool": {
"filter": [
{ "script": { "script": "doc['stock_qty'].value < doc['stock_safe'].value" }}
]
}
},
"aggs": {
"急需补货": {
"terms": { "field": "category_l2", "size": 20 },
"aggs": {
"缺货数量": { "sum": { "script": "doc['stock_safe'].value - doc['stock_qty'].value" }},
"平均成本": { "avg": { "field": "price_cost" }},
"TOP3缺货": { "top_hits": { "size": 3, "_source": ["name", "brand", "supplier_name"] }}
}
}
}
}
场景9:开店进货套餐推荐 — "开五金店要进一批货,帮我推荐一个工具箱套装"
GET /hardware/_search
{
"query": {
"bool": {
"should": [
{ "match": { "name": "工具箱套装" }},
{ "match": { "name": "汽修工具套装" }},
{ "match": { "tags": "开店必备" }}
],
"filter": [
{ "range": { "price_trade": { "lte": 500 }}},
{ "term": { "in_stock": true }}
]
}
},
"sort": [
{ "_score": "desc" },
{ "sales_count": "desc" }
],
"aggs": {
"价格区间分布": { "histogram": { "field": "price_trade", "interval": 100 }}
}
}
场景10:快速批量查找 — "这 20 个型号有没有货"
// mget 批量查询(多 ID 并行,性能极高)
GET /hardware/_mget
{
"docs": [
{ "_id": "P001" },
{ "_id": "P023" },
{ "_id": "P088" },
{ "_id": "P104" }
]
}
// 返回每个 ID 的 name, brand, model, stock_qty, price_retail
三、数据示例:同一"螺丝刀"的不同规格
[
{ "name": "螺丝刀", "brand": "世达", "model": "S2", "size_mm": "6", "material": "S2合金钢", "price_retail": 12.5, "price_trade": 8.0 },
{ "name": "螺丝刀", "brand": "史丹利", "model": "CR-V", "size_mm": "6", "material": "CR-V铬钒钢","price_retail": 18.0, "price_trade": 13.5 },
{ "name": "螺丝刀", "brand": "得力", "model": "普通", "size_mm": "6", "material": "碳钢", "price_retail": 4.5, "price_trade": 2.8 },
{ "name": "螺丝刀", "brand": "博世", "model": "S4", "size_mm": "4", "material": "S4工具钢", "price_retail": 22.0, "price_trade": 16.0 },
{ "name": "螺丝刀", "brand": "史努博", "model": "S2", "size_mm": "5", "material": "S2合金钢", "price_retail": 9.8, "price_trade": 6.5 }
]
这 5 个商品 name 完全相同,靠 brand + model + material + size_mm 四个字段组合才能唯一区分。
四、基础关键词搜索(补充)
match — 全文检索
GET /products/_search
{
"query": {
"match": {
"name": "运动鞋"
}
}
}
multi_match — 多字段搜索,提高召回率
GET /products/_search
{
"query": {
"multi_match": {
"query": "耐克跑步鞋",
"fields": ["name^3", "category^2", "description"]
}
}
}
query_string — 支持 AND/OR 语法
GET /products/_search
{
"query": {
"query_string": {
"default_field": "name",
"query": "(运动鞋 OR 跑步鞋) AND NOT 儿童"
}
}
}
filter 不评分,性能优于 query
GET /products/_search
{
"query": {
"bool": {
"must": { "match": { "name": "手机" }},
"filter": [
{ "term": { "brand": "华为" }},
{ "term": { "in_stock": true }},
{ "range": { "price": { "gte": 2000, "lte": 5000 }}}
]
}
}
}
五、聚合统计(Facets)
GET /products/_search
{
"query": { "match": { "name": "扳手" }},
"aggs": {
"品牌分布": { "terms": { "field": "brand", "size": 10 }},
"价格区间": {
"histogram": { "field": "price", "interval": 50 }
},
"平均价格": { "avg": { "field": "price" }}
}
}
六、搜索建议 / 自动补全(Completion Suggester)
索引定义
PUT /products/_mapping
{
"properties": {
"suggest": {
"type": "completion",
"analyzer": "ik_smart"
}
}
}
搜索时前缀补全
GET /products/_search
{
"suggest": {
"前缀建议": {
"prefix": "运动",
"completion": { "field": "suggest", "size": 5 }
}
}
}
七、拼音搜索 + 纠错
// 拼音分词器:输入 "dq" 匹配 "电器"
GET /products/_search
{
"query": {
"match": {
"pinyin_name": {
"query": "shubiag",
"analyzer": "pinyin"
}
}
}
}
// Term Suggester — 拼写纠错
GET /products/_search
{
"suggest": {
"纠错": {
"text": "运动鞋",
"term": { "field": "name", "size": 2 }
}
}
}
八、同义词搜索
// 索引用同义词过滤器,搜索 "酒店" 也能匹配 "旅馆"
GET /products/_search
{
"query": {
"match": {
"name": {
"query": "旅馆",
"synonym_analyzer": "my_synonym"
}
}
}
}
例:
T恤 → 短袖、手机 → 移动电话、螺丝刀 → 改锥
九、相关商品推荐(More Like This)
// 根据当前商品,推荐相似商品
GET /products/_search
{
"query": {
"more_like_this": {
"fields": ["name", "category", "description"],
"like": [
{ "_index": "products", "_id": "12345" }
],
"min_term_freq": 1,
"min_doc_freq": 1
}
}
}
十、智能排序(Function Score)
// 综合评分:关键词匹配度 × 销量权重 × 评分权重
GET /products/_search
{
"query": {
"function_score": {
"query": { "match": { "name": "显示器" }},
"functions": [
{
"field_value_factor": {
"field": "sales_count",
"factor": 1.2,
"modifier": "log1p"
}
},
{
"gauss": {
"rating": { "origin": 5, "scale": 2 }
}
},
{ "filter": { "term": { "in_stock": true }}},
"score_mode": "sum",
"boost_mode": "multiply"
]
}
}
}
十一、组合搜索 + 高亮
GET /products/_search
{
"query": {
"bool": {
"must": {
"multi_match": {
"query": "黑色 512G 手机",
"fields": ["name^3", "description"],
"type": "best_fields"
}
},
"filter": [
{ "terms": { "color": ["黑色", "深灰"]}},
{ "range": { "storage": { "gte": 512 }}}
],
"should": [
{ "term": { "in_stock": true, "boost": 2 }}
]
}
},
"highlight": {
"fields": {
"name": { "pre_tags": "<em>", "post_tags": "</em>" }
}
}
}
十二、额外场景速查
| 场景 | 示例 |
|---|---|
| 地理搜索 | 查找"附近5公里的五金店" — geo_distance 查询 |
| 规格属性搜索 | attributes.规格: M码 AND attributes.颜色: 红色 |
| 爆款加权 | 销量超过 1000 的商品搜索排名 ×2 |
| 季节性推荐 | 夏天自动提升"风扇/凉席",冬天提升"取暖器" |
| 搜索日志分析 | 聚合用户搜索词,分析"热搜榜"和"无结果搜索词" |
十三、关联文档
- ClickHouse与Elasticsearch对比分析 — ClickHouse 与 Elasticsearch 核心技术架构对比与选型建议