如何在Elasticsearch 2.3中构建随时间变化的商品促销价格数据
Hey there, let's break down your problem and find the right approach for Elasticsearch 2.3. First, let's evaluate the three options you're considering, then land on the best solution with concrete examples.
为什么排除其他两种类型?
Inner Object
Inner objects in Elasticsearch get flattened at index time. For example, if you have two promotion periods for a product:
{ "product_name": "商品A", "promotions": [ {"start_time": "15:00", "end_time": "17:00", "price": 3}, {"start_time": "16:00", "end_time": "16:30", "price": 2} ] }
Elasticsearch will store this as separate arrays: promotions.start_time: ["15:00", "16:00"], promotions.end_time: ["17:00", "16:30"], promotions.price: [3, 2]. This breaks the association between the time range and its corresponding price—when you query for a time in both ranges, you might get both prices mixed up, which is not what you want. So inner objects are a no-go here.
Parent/Child Relationships
Parent/child lets you separate products and promotions into distinct documents, but in ES 2.3, this approach adds unnecessary complexity:
- You'd need to manage parent-child mappings and routing, which is more overhead.
- Querying to get the active promotion price requires joining parent and child documents, which is slower than nested queries for this use case.
- Since your promotions are tightly tied to individual products, there's no need to decouple them this way.
最优方案:Nested Type
Nested types in ES 2.3 solve exactly this problem—each nested object (your promotion period) is stored as an independent hidden document, preserving the relationship between start_time, end_time, and price. This lets you accurately query for promotions that match the current time and retrieve the correct price.
Step 1: Create the Mapping
First, define your index mapping with a nested promotions field. We'll also add a priority field to handle overlapping periods (like your example where two promotions overlap—higher priority means it takes precedence):
PUT /products { "mappings": { "product": { "properties": { "product_id": {"type": "string"}, "product_name": {"type": "string"}, "promotions": { "type": "nested", "properties": { "start_time": {"type": "date", "format": "HH:mm"}, "end_time": {"type": "date", "format": "HH:mm"}, "price": {"type": "double"}, "priority": {"type": "integer"} // Higher number = higher priority } } } } } }
Note: Using date type with HH:mm format makes time range queries much cleaner than storing strings.
Step 2: Index a Sample Product
Index your product with overlapping promotions, setting a higher priority for the shorter, lower-price period:
POST /products/product/1 { "product_id": "A", "product_name": "商品A", "promotions": [ { "start_time": "15:00", "end_time": "17:00", "price": 3.0, "priority": 1 }, { "start_time": "16:00", "end_time": "16:30", "price": 2.0, "priority": 2 } ] }
Step 3: Query for the Active Promotion Price
To get the correct price for the current time (e.g., 16:15), use a nested query combined with inner_hits to retrieve the matching promotion. We'll sort by priority descending to ensure we get the highest-priority (most specific) promotion first:
GET /products/_search { "query": { "nested": { "path": "promotions", "query": { "bool": { "must": [ {"range": {"promotions.start_time": {"lte": "16:15"}}}, {"range": {"promotions.end_time": {"gt": "16:15"}}} ] } }, "inner_hits": { "sort": [{"promotions.priority": "desc"}], "size": 1 } } } }
The inner_hits section will return the top-priority promotion that matches the current time. For 16:15, you'll get the 2.0 price; for 16:35, you'll get the 3.0 price.
Handling Edge Cases
- If no promotions are active, you can fall back to a base price (add a
base_pricefield to your product mapping). - For time zones, adjust the date format to include time zone info (e.g.,
HH:mmZ) and ensure your queries use the same time zone.
内容的提问来源于stack exchange,提问作者Ken

