对于WebShop中的产品,我有以下ElasticSearch数据结构:
{
"_index": "vue_storefront_catalog_1_product_1617378559",
"_type": "_doc",
"_source": {
"configurable_children": [
{
"price": 49.99,
"special_price": 34.99,
"special_from_date": "2020-11-27 00:00:00",
"special_to_date": "2020-11-30 23:59:59",
"stock": {
"qty": 0,
"is_in_stock": false,
"stock_status": 0
}
}
{
"price": 49.99,
"special_price": null,
"special_from_date": null,
"special_to_date": null,
"stock": {
"qty": 0,
"is_in_stock": false,
"stock_status": 0
}
}
]
}
使用以下映射:
{
"vue_storefront_catalog_1_product_1614928276" : {
"mappings" : {
"properties" : {
"configurable_children" : {
"properties" : {
"price" : {
"type" : "double"
},
"special_from_date" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
"special_price" : {
"type" : "double"
},
"special_to_date" : {
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
},
}
}
}
}
}
}
我创建了一个Elasticsearch查询,只过滤出正在销售的产品,这意味着:special_price必须低于价格,并且当前日期必须介于special_from_date和special_to_date之间。
这是我创建的无痛脚本:
boolean hasSale = false;
long timestampNow = new Date().getTime();
if (doc.containsKey('configurable_children.special_from_date') && !doc['configurable_children.special_from_date'].empty) {
long timestampSpecialFromDate = doc['configurable_children.special_from_date'].value.toInstant().toEpochMilli();
if (timestampSpecialFromDate > timestampNow) {
hasSale = false;
}
} else if (doc.containsKey('configurable_children.special_to_date') && !doc['configurable_children.special_to_date'].empty) {
long timestampSpecialToDate = doc['configurable_children.special_to_date'].value.toInstant().toEpochMilli();
if (timestampSpecialToDate < timestampNow) {
hasSale = false;
}
} else if (doc.containsKey('configurable_children.stock.is_in_stock') && doc['configurable_children.stock.is_in_stock'].value == false) {
hasSale = false;
} else if (1 - (doc['configurable_children.special_price'].value / doc['configurable_children.price'].value) > params.fraction) {
hasSale = true;
}
return hasSale
一旦configurable_children中的一个符合销售产品的条件,则返回该产品。这是不正确的,因为我需要遍历整个set op configurable_children以确定它是否是销售产品。我如何确保所有的孩子都被纳入计算?用循环?
以下是Joe在答案中建议的新查询:
GET vue_storefront_catalog_1_product/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"script_score": {
"script": {
"source": """
int allEntriesAreTrue(def arrayList) {
return arrayList.stream().allMatch(Boolean::valueOf) == true ? 1 : 0
}
ArrayList childrenAreMatching = [];
long timestampNow = params.timestampNow;
ArrayList children = params._source['configurable_children'];
if (children == null || children.size() == 0) {
return allEntriesAreTrue(childrenAreMatching);
}
for (config in children) {
if (!config.containsKey('stock')) {
childrenAreMatching.add(false);
continue;
} else if (!config['stock']['is_in_stock']
|| config['special_price'] == null
|| config['special_from_date'] == null
|| config['special_to_date'] == null) {
childrenAreMatching.add(false);
continue;
}
if (config['special_from_date'] != null && config['special_to_date'] != null) {
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
def from_millis = sf.parse(config['special_from_date']).getTime();
def to_millis = sf.parse(config['special_to_date']).getTime();
if (!(timestampNow >= from_millis && timestampNow <= to_millis)) {
childrenAreMatching.add(false);
continue;
}
}
def sale_fraction = 1 - (config['special_price'] / config['price']);
if (sale_fraction <= params.fraction) {
childrenAreMatching.add(false);
continue;
}
childrenAreMatching.add(true);
}
return allEntriesAreTrue(childrenAreMatching);
""",
"params": {
"timestampNow": 1617393889567,
"fraction": 0.1
}
}
}
}
],
"min_score": 1
}
}
}
答复如下:
{
"took" : 15155,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2936,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [... hits here ...]
}
}
现在,在讨论迭代方面之前,有一件关于ElasticSearch中的数组的重要事情需要了解。当它们没有定义为嵌套
时,它们的内容将被扁平化,单个键/值对之间的关系将丢失。因此,您应该明确地调整您的映射,如下所示:
{
"vue_storefront_catalog_1_product_1614928276" : {
"mappings" : {
"properties" : {
"configurable_children" : {
"type": "nested", <---
"properties" : {
"price" : {
"type" : "double"
},
...
}
}
}
}
}
}
并重新索引数据,以确保configurable_children
被视为单独的独立实体。
一旦它们映射为嵌套的
,您就可以只检索那些与脚本条件匹配的子级:
POST vue_storefront_catalog_1_product_1614928276/_search
{
"_source": "configurable_children_that_match",
"query": {
"nested": {
"path": "configurable_children",
"inner_hits": {
"name": "configurable_children_that_match"
},
"query": {
"bool": {
"must": [
{
"script": {
"script": {
"source": """
boolean hasSale = false;
long timestampNow = new Date().getTime();
if (doc.containsKey('configurable_children.special_from_date') && !doc['configurable_children.special_from_date'].empty) {
long timestampSpecialFromDate = doc['configurable_children.special_from_date'].value.toInstant().toEpochMilli();
if (timestampSpecialFromDate > timestampNow) {
return false
}
}
if (doc.containsKey('configurable_children.special_to_date') && !doc['configurable_children.special_to_date'].empty) {
long timestampSpecialToDate = doc['configurable_children.special_to_date'].value.toInstant().toEpochMilli();
if (timestampSpecialToDate < timestampNow) {
return false
}
}
if (doc.containsKey('configurable_children.stock.is_in_stock') && doc['configurable_children.stock.is_in_stock'].value == false) {
return false
}
if (1 - (doc['configurable_children.special_price'].value / doc['configurable_children.price'].value) > params.fraction) {
hasSale = true;
}
return hasSale
""",
"params": {
"fraction": 0.1
}
}
}
}
]
}
}
}
}
}
这里需要注意两件事:
嵌套
查询的inner_hits
属性允许您让Elasticsearch知道您只对那些真正匹配的子查询感兴趣。否则,将返回所有configurable_children
。当在_source
参数中指定时,将跳过原始的完整JSON文档源,只返回命名的inner_hits
。new Date()
。我已经解释了它背后的原因,以及如何将当前时间作为unix时间戳以供脚本使用的答案。您将看到我在这个答案底部的查询中使用了参数化的now
。这一事实的一个副作用是,一旦您进入嵌套的
查询的上下文中,就无法访问同一文档的其他嵌套子文档。
为了减轻这种情况,通常会定期保持嵌套子级的同步,这样当您将对象的一个属性扁平化以便在顶层使用时,您可以使用简单的迭代相应的doc值。这种扁平化通常是通过copy_to
特性来完成的,我在回答如何使用筛选器脚本在elasticsearch中迭代嵌套数组时对此进行了说明?
在您的特定用例中,这意味着您将在stock.is_in_stock
字段上使用copy_to
,这将产生一个顶级布尔数组列表,该列表比对象数组列表更容易使用。
到目前为止还不错,但是仍然缺少一种基于special_dates
进行筛选的方法。
现在,无论您处理的是嵌套
还是常规对象
字段类型,自V6.4
以来,在常规脚本查询中访问params._source
在ES中都不起作用。
正如你在问题中所说,你
..需要遍历整个configurable_children
集,以确定它是否是销售产品..
话虽如此,下面是我的查询的工作原理:
function_score
查询通常生成自定义的计算得分,但在min_score
的帮助下,它可以用作布尔是/否筛选器,以排除configurable_children
不满足特定条件的文档。configurable_childrenareMatching
时,每个循环都会向childrenareMatching
追加一个布尔值,然后将其传递到AllentriesareTrue
helper,如果是,则返回1,如果不是,则返回0。now
进行比较;还比较分数
。如果在任何时候某个条件失败,循环将跳转到下一次迭代。POST vue_storefront_catalog_1_product_1614928276/_search
{
"query": {
"function_score": {
"query": {
"match_all": {}
},
"functions": [
{
"script_score": {
"script": {
"source": """
// casting helper
int allEntriesAreTrue(def arrayList) {
return arrayList.stream().allMatch(Boolean::valueOf) == true ? 1 : 0
}
ArrayList childrenAreMatching = [];
long timestampNow = params.timestampNow;
ArrayList children = params._source['configurable_children'];
if (children == null || children.size() == 0) {
return allEntriesAreTrue(childrenAreMatching);
}
for (config in children) {
if (!config['stock']['is_in_stock']
|| config['special_price'] == null
|| config['special_from_date'] == null
|| config['special_to_date'] == null) {
// nothing to do here...
childrenAreMatching.add(false);
continue;
}
SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
def from_millis = sf.parse(config['special_from_date']).getTime();
def to_millis = sf.parse(config['special_to_date']).getTime();
if (!(timestampNow >= from_millis && timestampNow <= to_millis)) {
// not in date range
childrenAreMatching.add(false);
continue;
}
def sale_fraction = 1 - (config['special_price'] / config['price']);
if (sale_fraction <= params.fraction) {
// fraction condition not met
childrenAreMatching.add(false);
continue;
}
childrenAreMatching.add(true);
}
// need to return a number because it's a script score query
return allEntriesAreTrue(childrenAreMatching);
""",
"params": {
"timestampNow": 1617393889567,
"fraction": 0.1
}
}
}
}
],
"min_score": 1
}
}
}
总而言之,只有那些所有configurable_children
满足指定条件的文档才会返回。
附言。如果您从这个答案中学到了一些东西,并想学到更多,我在我的Elasticsearch手册中专门用了一章来介绍ES脚本。
问题内容: 我正在尝试使用Elasticsearch的脚本语言来操纵日期。具体来说,我尝试增加4小时,即14,400秒。 这抛出 谢谢 问题答案: 解决方案是使用 但是,我实际上想将其用于重新索引,但格式略有不同。这是我在api中操纵时间的版本 这是可读版本的内联脚本 这是无痛支持的功能列表,很痛苦。
假设您有一个数组,其中包含一周中的几天: 现在假设你有一个数组,它记录一年中的每一个数字日,这个数组由366个元素组成。 有没有可能写一个循环或一些东西,当在天数组中循环时,它重置回星期一以保持输出看起来像: 周一:1周二:2周三:3周四:4周五:5周六:6周日:7周一:8周二:9 ect一直到366
Elasticsearch 是目前流行的大数据处理框架之一,全文搜索引擎 Elasticsearch PHP 中文文档。
问题内容: 注意: 我最初发布此问题的方式有所不同,因此不值得更新,因为阅读后我学到了更多。 需求 搜索文档并根据文档中的嵌套元素计算自定义分数。 结构体 样品查询 孤立无痛 错误 在类型[book]的映射中找不到[topics]的字段 问题 怎么了? 该怎么办? 问题答案: 嵌套文档存储在索引中的不同文档中,因此您不能通过父文档中的doc值来访问它们。您需要使用源文档并导航至属性,如下所示: 孤
我正在尝试学习无痛技术,这样我就可以在尝试丰富和操作传入文档时使用它。然而,我看到的每一种访问文档的方法都会导致错误。在基巴纳的无痛实验室输入这些信息后,我得到的错误如下: 尽管测试文档中存在该字段,但仍会抱怨,尽管 确实返回 。 我应该如何访问和操作传入文档?我用的是ElasticSearch 7.12。
问题内容: 我必须在弹性中插入一个json数组。链接中可接受的答案建议在每个json条目之前插入标题行。答案是2岁,市场上是否有更好的解决方案?我需要手动编辑json文件吗? 问题答案: 好的,那么您可以使用简单的Shell脚本来完成一些非常简单的操作(请参见下文)。这个想法是不必手动编辑文件,而是让Python进行编辑并创建另一个文件格式符合端点期望的文件。它执行以下操作: 首先,我们声明一个小