gpt4 book ai didi

loops - 轻松遍历 ElasticSearch 文档源数组

转载 作者:行者123 更新时间:2023-12-04 03:30:37 26 4
gpt4 key购买 nike

我有以下用于网上商店产品的 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
只要其中一个可配置的 child 满足销售产品的条件,就会返回产品。这是不正确的,因为我需要遍历整个集合 op configure_children 以确定它是否是销售产品。我怎样才能确保所有 child 都被计算在内?带循环?

这是乔在答案中建议的新查询:
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 ...]
}
}

知道为什么查询需要大约 15 秒吗?

最佳答案

你的直觉是对的——你需要使用 for如果您想检查,请循环全部 数组列表对象。
现在,在我进入迭代方面之前,有一件重要的事情需要了解 Elasticsearch 中的数组。当它们未定义为 nested 时, 他们的内容 will be flattened以及各个键/值对之间的关​​系 会丢失 .因此,您绝对应该像这样调整映射:

{
"vue_storefront_catalog_1_product_1614928276" : {
"mappings" : {
"properties" : {
"configurable_children" : {
"type": "nested", <---
"properties" : {
"price" : {
"type" : "double"
},
...
}
}
}
}
}
}
并重新索引您的数据以确保 configurable_children被视为单独的、独立的实体。
一旦它们被映射为 nested ,您将能够仅检索与您的脚本条件匹配的子项:
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 attribute nested query允许您让 Elasticsearch 知道您只对那些真正匹配的 child 感兴趣。否则,所有 configurable_children会被退回。当在 _source parameter 中指定时,原始的、完整的 JSON 文档源将被跳过,只有命名的 inner_hits会被退回。
  • 由于 ES 的分布式特性,不推荐使用 java 的 new Date() .我已经解释了背后的原因 my answerHow to get current time as unix timestamp for script use .你会看到我使用参数化 now在此答案底部的查询中。

  • 继续,重要的是要提到 嵌套对象在内部表示为单独的子文档 .
    这个事实的一个副作用是,一旦你进入 nested查询的上下文,您无权访问同一文档的其他嵌套子项。
    为了缓解这种情况,习惯上定期保持嵌套的子级同步,这样当您将对象的一个​​属性展平以在顶层使用时,您可以使用简单的迭代相应的 doc 值。这种扁平化通常是通过 copy_to 完成的。我在 my answer 中说明的功能至 How to iterate through a nested array in elasticsearch with filter script?
    例如,在您的特定用例中,这意味着您将使用 copy_to场上 stock.is_in_stock这会产生一个顶级 bool 数组列表,它比对象数组列表更容易使用。
    到目前为止一切顺利,但您仍然缺少一种基于 special_dates 的过滤方法。 .
    现在,无论您是否正在处理 nested或普通 object字段类型,访问 params._source常规脚本查询在 ES 中不起作用,因为 v6.4 .
    但是,仍然有一种类型的查询支持迭代 _source — 输入 function_score查询。
    如您的问题所述,您

    ..need to loop through the whole set of configurable_children to determine if it's a sale product..


    话虽如此,下面是我的查询如何工作:
  • function_score query通常会生成自定义计算的分数,但它可以在 min_score 的帮助下生成, 用作 bool 值是/否过滤器以排除 configurable_children 的文档不满足某个条件。
  • configurable_children正在迭代,每个循环都将一个 bool 值附加到 childrenAreMatching然后传递给 allEntriesAreTrue如果是,则返回 1,否则返回 0。
  • 日期被解析并与参数化 now 进行比较; fraction也比较。如果在任何时候某些条件失败,循环将跳转到下一次迭代。

  • 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满足指定条件,将被退回。

    附言如果您从这个答案中学到了一些东西并想了解更多信息,我在 my Elasticsearch Handbook 中专门用了一整章介绍 ES 脚本。 .

    关于loops - 轻松遍历 ElasticSearch 文档源数组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66924077/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com