ElasticSearch:商品SKU+SPU实现join查询,设计及优化

文章目录

  • 一、SPU+SKU
    • 1、商品SPU和SKU
    • 2、SPU和SKU的关系
    • 3、实现SPU+SKU父子嵌套查询
      • 1. **嵌套对象(Nested Objects)**
      • 2. **父子关系(Parent-Child)**
      • 3. **应用层关联(Application-Side Join)**(推荐)
      • 4. **预连接数据(Denormalization)**(推荐)
      • 方案对比
      • 优化建议

一、SPU+SKU

1、商品SPU和SKU

每个店铺中会包含多个商品,每个商品会有多个规格。

红框上面的部分是商品的SPU(Standard Product Unit 标准产品单位),SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。这里会选择对应店铺的名称,填入商品编号、商品名称、上架状态、定时上架时间、商品标签、商品图片等信息。

下面红色的框体是商品的SKU(stock keeping unit 库存量单位),SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位,SKU是物理上不可分割的最小存货单元。包括商品颜色、商品存储容量、商品价格、商品剩余数量。

在这里插入图片描述

2、SPU和SKU的关系

如图所示,一个店铺会对应多个商品的SPU。同时一个商品的SPU会包含多个商品SKU,以手机为例,一款iPhone12手机就是SPU,如果消费者需要购买该手机的时候,还需要选择手机颜色、内存容量,在商家这边还要查看商品的库存是否足够。因此,一个商品SPU会对
应多个商品SKU的描述。
在这里插入图片描述
如果我们建立数据库表的时候会将店铺、商品SPU和商品SKU建立三张表,通过主外键的关系关联。
如果mysql的数据同步到ES中,用户进行搜索的时候,应该怎么实现呢?

在 Elasticsearch(ES)中实现关联查询(join)的方式与传统关系型数据库不同,因为 ES 是分布式搜索引擎,其设计初衷并非处理复杂关联。以下是几种常见的 ES 关联实现方法:

3、实现SPU+SKU父子嵌套查询

1. 嵌套对象(Nested Objects)

场景:SPU 与 SKU 强关联,查询时需同时检索。
示例:一款手机(SPU)有多个颜色和配置(SKU)。

映射与数据

PUT /products
{"mappings": {"properties": {"spu_id": {"type": "keyword"},"spu_name": {"type": "text"},"brand": {"type": "keyword"},"skus": {"type": "nested",  // 嵌套类型"properties": {"sku_id": {"type": "keyword"},"color": {"type": "keyword"},"size": {"type": "keyword"},"price": {"type": "double"},"stock": {"type": "integer"}}}}}
}PUT /products/_doc/1
{"spu_id": "SPU001","spu_name": "苹果 iPhone 14","brand": "Apple","skus": [{"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00,"stock": 100},{"sku_id": "SKU002","color": "蓝色","size": "256GB","price": 6799.00,"stock": 50}]
}

查询示例

  • 需求:查找所有品牌为 Apple 且有蓝色 256GB 的手机。
  • 实现
{"query": {"bool": {"must": [{"term": {"brand": "Apple"}},{"nested": {  // 嵌套查询"path": "skus","query": {"bool": {"must": [{"term": {"skus.color": "蓝色"}},{"term": {"skus.size": "256GB"}}]}}}}]}}
}

2. 父子关系(Parent-Child)

场景:SPU 与 SKU 生命周期独立,需灵活管理(如动态添加 SKU)。
示例:一款手机(SPU)的 SKU 库存需实时更新。

映射与数据

PUT /products
{"mappings": {"properties": {"join_field": {"type": "join","relations": {"spu": "sku"  // 定义父子关系}}}}
}// 索引 SPU(父文档)
PUT /products/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","join_field": {"name": "spu"}
}// 索引 SKU(子文档)
PUT /products/_doc/SKU001?routing=SPU001  // 必须使用父 ID 作为路由
{"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00,"stock": 100,"join_field": {"name": "sku", "parent": "SPU001"}
}PUT /products/_doc/SKU002?routing=SPU001
{"sku_id": "SKU002","color": "蓝色","size": "256GB","price": 6799.00,"stock": 50,"join_field": {"name": "sku", "parent": "SPU001"}
}

查询示例

  • 需求:查找价格低于 6000 的 SKU 所属的 SPU 信息。
  • 实现
{"query": {"has_child": {"type": "sku","query": {"range": {"price": {"lt": 6000}}},"inner_hits": {}  // 返回匹配的子文档}}
}

3. 应用层关联(Application-Side Join)(推荐)

场景:SPU 和 SKU 存储在不同索引,需跨索引关联。
示例:SPU 数据在 spu_index,SKU 数据在 sku_index

数据结构

// SPU 索引
PUT /spu_index/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","category": "手机","description": "2023年新款智能手机..."
}// SKU 索引
PUT /sku_index/_doc/SKU001
{"sku_id": "SKU001","spu_id": "SPU001",  // 关联字段"color": "黑色","size": "128GB","price": 5999.00,"stock": 100,"sales": 2000
}

查询流程(Python)

from elasticsearch import Elasticsearches = Elasticsearch()# 1. 查询手机分类下的所有 SPU
spu_result = es.search(index="spu_index",body={"query": {"term": {"category": "手机"}},"_source": ["spu_id", "name", "brand"]},size=100
)# 2. 提取 SPU IDs
spu_ids = [hit["_source"]["spu_id"] for hit in spu_result["hits"]["hits"]]# 3. 批量查询 SKU(按价格排序)
sku_result = es.search(index="sku_index",body={"query": {"terms": {"spu_id": spu_ids}},"sort": {"price": "asc"},"_source": ["spu_id", "color", "size", "price"]},size=1000
)# 4. 应用层组装结果(SPU + 最低价格 SKU)
spu_sku_map = {}
for sku in sku_result["hits"]["hits"]["_source"]:spu_id = sku["spu_id"]if spu_id not in spu_sku_map or sku["price"] < spu_sku_map[spu_id]["price"]:spu_sku_map[spu_id] = sku# 5. 合并 SPU 和 SKU 信息
merged_result = []
for spu_hit in spu_result["hits"]["hits"]:spu = spu_hit["_source"]sku = spu_sku_map.get(spu["spu_id"], {})merged_result.append({"spu": spu,"lowest_price_sku": sku})

4. 预连接数据(Denormalization)(推荐)

场景:读多写少,需快速查询(如商品列表页)。
示例:将常用 SKU 信息冗余到 SPU 文档中。

数据结构

PUT /products/_doc/SPU001
{"spu_id": "SPU001","name": "苹果 iPhone 14","brand": "Apple","category": "手机","min_price": 5999.00,  // 预计算:最低价格"max_price": 6799.00,  // 预计算:最高价格"default_sku": {  // 预定义:默认 SKU"sku_id": "SKU001","color": "黑色","size": "128GB","price": 5999.00},"sku_summary": [  // 预聚合:SKU 摘要{"color": "黑色", "size": "128GB", "price": 5999.00},{"color": "蓝色", "size": "256GB", "price": 6799.00}]
}

查询示例

  • 需求:查找价格区间在 5000-6500 的手机,返回 SPU 及默认 SKU。
  • 实现
{"query": {"range": {"min_price": {"gte": 5000, "lte": 6500}}},"_source": ["spu_id", "name", "brand", "default_sku"]
}

方案对比

方法查询性能写入性能数据一致性适用场景
嵌套对象SKU 数量少,需原子性操作
父子关系SKU 动态变化,需独立管理
应用层关联跨索引关联,数据量大
预连接数据最高读多写少,实时性要求低

优化建议

  1. 嵌套对象深度限制:避免超过 1000 个嵌套文档,否则性能下降。
  2. 父子关系分片一致性:父子文档必须在同一分片(通过 routing 参数保证)。
  3. 应用层关联批量查询:使用 terms 查询替代循环单条查询。
  4. 预连接数据更新策略:通过异步任务(如消息队列)更新冗余字段。

根据实际业务场景(如 SKU 数量、读写比例、实时性要求)选择合适的方案,或组合使用多种方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/91889.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/91889.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Objective-c 初阶 —— Runtime(方法交换 消息传递)

一、消息传递1、什么是消息[a func1];我们会把这种用方括号来调函数的方式称为发消息。对于这个例子&#xff0c;就相当于我们给 a 这个对象发了个 func1 的消息&#xff08;个人认为指令更好理解&#xff09;。2、什么是 selectorselector 就是一个函数区分器。它只会给这个方…

【计算机网络架构】树型架构简介

引言在当今数字化时代&#xff0c;网络架构如同复杂的神经系统&#xff0c;支撑着各种信息的流通与交互。从个人日常的网络浏览、在线购物&#xff0c;到企业的远程办公、数据存储&#xff0c;再到国家层面的政务信息化、智慧城市建设&#xff0c;网络架构都扮演着不可或缺的角…

llama-factory快速开始

llama-factory快速开始 文章目录llama-factory快速开始前言一、环境配置1.1 训练顺利运行需要包含4个必备条件1.2 llama-factory下载1.3 环境下载1.4 硬件环境校验二、启动前言 https://github.com/hiyouga/LLaMA-Factory/blob/main/README_zh.md这是GitHub中文介绍文档&#…

408数据结构强化(自用)

常用代码片段&#xff08;持续更新&#xff09;折半查找void SearchBinary(int A[];int x){int low 0, high n-1, mid;while(low<high){mid (lowhigh)/2;if(A[mid]x) break;else if(A[mid] < x) low mid 1;else high mid - 1;}顺序表逆置void Reverse(SqList &…

linux cpu频率和AVS调压等级

1&#xff0c;linux常见的cpu频率对应的电压等级对应参数表如下:频率&#xff08;GHz&#xff09;电压&#xff08;V&#xff09;1.61.41.41.21.21.01.00.82&#xff0c;avs调压的几种方式linux内核宏解释Linux内核中&#xff0c;AVS调压的实现依赖于一些宏定义和配置选项&…

Input输入和Screen相关

知识点using System.Collections; using System.Collections.Generic; using UnityEngine;public class Lesson11 : MonoBehaviour {// Start is called before the first frame updatevoid Start(){}// Update is called once per framevoid Update(){#region 注意&#xff0c…

如何在CSDN变现?如何赚钱?如何涨粉?如何找到优质大V博主合作伙伴?

&#x1f525; 2025最新 如何在CSDN变现&#xff1f;如何赚钱&#xff1f;如何跟对人&#xff1f;如何找到优质博主合作伙伴&#xff1f; 大家好&#xff0c;我是猫头虎&#xff0c;今天✍️想和大家聊聊在CSDN平台变现的问题。这也是绝大多数伙伴非常关心的一个话题——其实&…

OpenCV特征点提取算法orb、surf、sift对比

下面是 OpenCV 中三种常用特征点提取算法&#xff1a;ORB、SURF 和 SIFT 的详细对比&#xff0c;从 算法原理、性能、使用限制 和 适用场景 多维度进行总结&#xff0c;帮助大家在实际项目中合理选择。一览表&#xff1a;ORB vs. SURF vs. SIFT属性/算法ORBSURFSIFT全称Oriente…

LeafletJS 与 React:构建现代地图应用

引言 LeafletJS 是一个轻量、灵活的 JavaScript 地图库&#xff0c;广泛用于创建交互式 Web 地图&#xff0c;而 React 作为现代前端框架&#xff0c;以其组件化、状态管理和虚拟 DOM 特性&#xff0c;成为构建动态用户界面的首选工具。将 LeafletJS 与 React 结合&#xff0c…

前后端数据交互,关于表单数据传输问题

表单提交var formData new FormData(); // 添加每个事故ID作为单独的参数 accidentIds.forEach(id > formData.append(accidentIds, id)); formData.append(status, statusText); $.messager.confirm(确认, 确定要将事故记录标记为 statusText 吗&#xff1f;, function …

新书推介 | 吉林大学出版教材《汽车智能辅助驾驶系统技术》,国产仿真工具链GCKontrol-GCAir教学应用

近日&#xff0c;吉林大学出版了由高镇海教授、孙天骏副教授主编的新教材《汽车智能辅助驾驶系统技术》&#xff0c;本书系统地介绍了汽车智能辅助驾驶系统的发展需求、物理架构、功能算法、技术原理以及应用场景。在教材第17章《仿真测试》&#xff0c;应用国产化GCKontrol-GC…

从 0 到 1 玩转 XSS - haozi 靶场:环境搭建 + 全关卡漏洞解析

文章目录前言靶场地址0X00 直接注入0X01 闭合标签10X02 闭合标签20X03 绕过特殊符号10X04 绕过特殊符号20X05 绕过注释符0X06 绕过更多符号0X07 绕过更多符号20X08 绕过闭合符号0X09 绕过URL匹配0X0A 绕过URL过滤0X0B 绕过大写转换10X0C 绕过大写转换20X0D 绕过注释0X0E 古英语…

TF卡格式化

cmd 输入diskpart,在新打开的窗口输入list disk&#xff0c;然后select disk [磁盘号]&#xff0c;clean&#xff0c;回车变成未分区的。再选中磁盘&#xff0c;选中之后create partition [分区名] 回车&#xff0c;list partition&#xff0c;查看分区&#xff0c;输入active&…

Python爬虫实战:研究sqlparse库相关技术

1. 引言 1.1 研究背景与意义 在当今数据驱动的时代,SQL 作为关系型数据库的标准查询语言,被广泛应用于各种数据处理和分析场景。随着数据库应用的不断发展,SQL 代码的规模和复杂度也在不断增加,这给 SQL 代码的编写、维护和优化带来了挑战。 研究表明,低效的 SQL 查询是…

全球天气预报5天(经纬度版)免费API接口教程

本文全面介绍由接口盒子免费API提供的全球天气预报API&#xff0c;支持通过经纬度坐标获取任意地区未来5天的详细天气预报数据。 一、接口核心功能 ​全球覆盖​&#xff1a;支持全球任意经纬度坐标点的天气预报​高精度预报​&#xff1a;提供每3小时为间隔的精细化预报&…

5 基于STM32单片机的绝缘检测系统设计(STM32代码编写+手机APP设计+PCB设计+Proteus仿真)

系列文章目录 文章目录 系列文章目录前言1 1 资料获取与演示视频1.1 资料介绍1.2 资料获取1.3 演示视频 2 系统框架3 硬件3.1 主控制器3.2 显示屏3.3 WIFI模块3.4 DHT11温湿度传感器3.5 可调电位器 4 设计PCB4.1 安装下载立创EDA专业版4.2 画原理图4.3 摆放元器件&#xff0c;设…

CPP学习之list使用及模拟实现

一、list简介及用法 1. list简介 list是可以在常数范围内任意位置进行插入、删除、修改操作的有顺序性的容器&#xff0c;而且支持双向迭代&#xff0c;其底层是双链表结构&#xff0c;逻辑上连续但物理空间上不连续&#xff0c;只能通过指针来进行元素访问&#xff0c;无法使用…

Spring Boot 参数校验:@Valid 与 @Validated

在日常开发中&#xff0c;参数校验是保障接口健壮性与数据安全的第一道防线。Spring Boot 为我们提供了基于 JSR-303/JSR-380 的强大校验机制&#xff0c;通过注解与 AOP 实现了灵活且高效的数据校验方式。本篇博客将详细介绍 Spring Boot 中 Valid、Validated 注解的使用方法&…

linux看门狗重启定位思路总结

1&#xff0c;看门狗定位思路&#xff08;1&#xff09;是否是死锁导致查看日志查看是否有RCU install或者deadlock相关打印&#xff0c;如果有的话可以考虑使用lockdep死锁检测工具&#xff08;2&#xff09;中断风暴查看中断&#xff0c;抓中断打印&#xff0c;可以查看/proc…

基于单片机直流电机测速中文液晶显示设计

摘 要 在现在工业自动化高度发展的时期&#xff0c;几乎所有的工业设备都离不开旋转设备&#xff0c;形形色色的电机在不同领域发挥着很重要的作用。不同场合对电机控制要求是不同的&#xff0c;但大部分都会涉及到旋转设备的转速测量&#xff0c;从而利用转速来实施对旋转设备…