Как добавить фильтр между вложенными script_score?

Я динамически фильтрую цены по заданным курсам валют и сортирую их по баллам, которые генерируются скриптом. Но есть одна вещь, которую я не мог понять, как это сделать, - это фильтр диапазона.

Например, я хочу получить только результат соответствия product_platforms от 10 до 100.

Запрос индекса.

      PUT /test_products
{
  "settings": {
      "number_of_shards": 3,
      "number_of_replicas": 0,
      "analysis": {
        "filter": {
          "autocomplete_filter": {
            "type": "edge_ngram",
            "min_gram": "2",
            "max_gram": "15"
          }
        },
        "analyzer": {
          "autocomplete": {
            "type": "custom",
            "tokenizer": "standard",
            "filter": [
              "lowercase",
              "autocomplete_filter"
            ]
          }
        }
      }
    },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword",
        "doc_values": true
      },
      "name": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          },
          "raw": {
            "type": "keyword"
          }
        },
        "analyzer": "autocomplete",
        "search_analyzer": "standard"
      },
      "product_platforms": {
        "type": "nested",
        "properties": {
          "id": {
            "type": "long"
          },
          "platform_id": {
            "type": "long"
          },
          "price": {
            "type": "float"
          },
          "currency_id": {
            "type": "long"
          },
          "currency_code": {
            "enabled": false
          },
          "sku": {
            "type": "keyword"
          },
          "quantity": {
            "type": "long"
          }
        }
      }
    }
  }
}

Вставьте тестовые документы:

      POST /test_products/_bulk?pretty&refresh
{"index":{"_id": 1}}
{"id": 1, "name": "1. Product", "product_platforms": [{"id": 11, "platform_id": 3, "price": 100, "currency_id": 1, "currency_code": "TRY", "sku": "URN_1_1", "quantity": 1},{"id": 12, "platform_id": 3, "price": 75, "currency_id": 2, "currency_code": "USD", "sku": "URN_1_2", "quantity": 1},{"id": 13, "platform_id": 2, "price": 15, "currency_id": 2, "currency_code": "USD", "sku": "URN_1_3", "quantity": 1}]}
{"index":{"_id": 2}}
{"id": 2, "name": "2. Product", "product_platforms": [{"id": 21, "platform_id": 3, "price": 50, "currency_id": 1, "currency_code": "TRY", "sku": "URN_2_1", "quantity": 1},{"id": 22, "platform_id": 3, "price": 25, "currency_id": 2, "currency_code": "USD", "sku": "URN_2_2", "quantity": 1},{"id": 23, "platform_id": 3, "price": 75, "currency_id": 1, "currency_code": "TRY", "sku": "URN_2_3", "quantity": 1}, {"id": 24, "platform_id": 3, "price": 20, "currency_id": 2, "currency_code": "USD", "sku": "URN_2_4", "quantity": 1}]}

И вот мой поисковый запрос:

      GET /test_products/_search
{
  "query": {
    "nested": {
      "path": "product_platforms",
      "score_mode": "max",
      "query": {
        "function_score": {
          "query": {
            "bool": {
              "must": [
                {
                  "term": {
                    "product_platforms.platform_id": {
                      "value": "3"
                    }
                  }
                }
              ]
            }
          },
          "boost_mode": "replace",
          "script_score": {
            "script": {
              "source": """

              doc['product_platforms.price'].value * (doc['product_platforms.currency_id'].value == 2 ? params.rate_usd : (doc['product_platforms.currency_id'].value == 3 ? params.rate_eur : params.rate_try))         """,
              "params": {
                "rate_try": 1,
                "rate_usd": 7,
                "rate_eur": 8
              }
            }
          }
        }
      },
      "inner_hits": {
        "name": "product_platforms",
        "_source": true,
        "size": 5,
        "sort": {
          "_script": {
            "type": "number",
            "script": {
              "lang": "painless",
              "source": """                 doc['product_platforms.price'].value * (doc['product_platforms.currency_id'].value == 2 ? params.rate_usd : (doc['product_platforms.currency_id'].value == 3 ? params.rate_eur : params.rate_try))               """,
              "params": {
                "rate_try": 1,
                "rate_usd": 7,
                "rate_eur": 8
              }
            },
            "order": "desc"
          }
        }
      }
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    }
  ]
}

Я использую версию 7.10 кстати.

1 ответ

Вы можете повторить этот калькулятор очков еще раз, на этот раз в виде логического <tcode id="256525"></tcode>запрос сам по себе.

Теперь, поскольку ваш скрипт конвертации валюты повторяется слишком много раз, вы можете сохранить его и ссылаться на него по его идентификатору каждый раз, когда он вам понадобится. Вы, конечно, сохраните параметризацию ставок, но все будет немного более читаемым и обслуживаемым.

Итак, сначала сохраним скрипт:

      POST _scripts/product-platforms-converter
{
  "script": {
    "source": """
      def price = doc['product_platforms.price'].value;
      def currency_id = doc['product_platforms.currency_id'].value;
    
      def converted_price = price * (currency_id == 2 
            ? params.rate_usd : (currency_id == 3 
                ? params.rate_eur : params.rate_try)); 
              
      if (params.final_range != null) {
        def is_in_range = converted_price >= params.final_range.gte 
            && converted_price <= params.final_range.lte;
        
        return is_in_range;
      }
      
      return converted_price;
      """,
    "lang": "painless"
  }
}

Обратите внимание, что если final_range предоставляется в params, скрипт возвращает boolean; если нет, он просто вернет converted_price.

После этого исходный запрос можно переписать как:

      GET /test_products/_search
{
  "query": {
    "nested": {
      "path": "product_platforms",
      "score_mode": "max",
      "query": {
        "function_score": {
          "query": {
            "bool": {
              "must": [
                {
                  "term": {
                    "product_platforms.platform_id": {
                      "value": "3"
                    }
                  }
                },
                {
                  "script": {
                    "script": {
                      "id": "product-platforms-converter",
                      "params": {
                        "rate_try": 1,
                        "rate_usd": 7,
                        "rate_eur": 8,
                        "final_range": {             <--- the desired "range" query
                          "gte": 10,
                          "lte": 100
                        }
                      }
                    }
                  }
                }
              ]
            }
          },
          "boost_mode": "replace",
          "script_score": {
            "script": {
              "id": "product-platforms-converter",
              "params": {
                "rate_try": 1,
                "rate_usd": 7,
                "rate_eur": 8
              }
            }
          }
        }
      },
      "inner_hits": {
        "name": "product_platforms",
        "_source": true,
        "size": 5,
        "sort": {
          "_script": {
            "type": "number",
            "script": {
              "id": "product-platforms-converter",
              "params": {
                "rate_try": 1,
                "rate_usd": 7,
                "rate_eur": 8
              }
            },
            "order": "desc"
          }
        }
      }
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    }
  ]
}