Как отфильтровать по размеру массива во вложенном типе?

Допустим, у меня такой тип:

{
    "2019-11-04": {
        "mappings": {
            "_doc": {
                "properties": {
                    "labels": {
                        "type": "nested",
                        "properties": {
                            "confidence": {
                                "type": "float"
                            },
                            "created_at": {
                                "type": "date",
                                "format": "strict_date_optional_time||date_time||epoch_millis"
                            },
                            "label": {
                                "type": "keyword"
                            },
                            "updated_at": {
                                "type": "date",
                                "format": "strict_date_optional_time||date_time||epoch_millis"
                            },
                            "value": {
                                "type": "keyword",
                                "fields": {
                                    "numeric": {
                                        "type": "float",
                                        "ignore_malformed": true
                                    }
                                }
                            }
                        }
                    },
                    "params": {
                        "type": "object"
                    },
                    "type": {
                        "type": "keyword"
                    }
                }
            }
        }
    }
}

И я хочу фильтровать по размеру / длине массива labels. Я пробовал следующее (как официальные документы предлагают):

{
    "query": {
        "bool": {
            "filter": {
                "script": {
                    "script": {
                        "source": "doc['labels'].size > 10"
                    }
                }
            }
        }
    }
}

но я продолжаю получать:

{
  "error": {
    "root_cause": [
      {
        "type": "script_exception",
        "reason": "runtime error",
        "script_stack": [
          "org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:81)",
          "org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:39)",
          "doc['labels'].size > 10",
          "    ^---- HERE"
        ],
        "script": "doc['labels'].size > 10",
        "lang": "painless"
      }
    ],
    "type": "search_phase_execution_exception",
    "reason": "all shards failed",
    "phase": "query",
    "grouped": true,
    "failed_shards": [
      {
        "shard": 0,
        "index": "2019-11-04",
        "node": "kk5MNRPoR4SYeQpLk2By3A",
        "reason": {
          "type": "script_exception",
          "reason": "runtime error",
          "script_stack": [
            "org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:81)",
            "org.elasticsearch.search.lookup.LeafDocLookup.get(LeafDocLookup.java:39)",
            "doc['labels'].size > 10",
            "    ^---- HERE"
          ],
          "script": "doc['labels'].size > 10",
          "lang": "painless",
          "caused_by": {
            "type": "illegal_argument_exception",
            "reason": "No field found for [labels] in mapping with types []"
          }
        }
      }
    ]
  },
  "status": 500
}

person Patryk    schedule 04.11.2019    source источник
comment
Привет, @Patryk, просто хотел посмотреть, сможете ли вы разрешить вышеуказанный запрос. Был ли приведенный ниже ответ каким-либо образом полезным.   -  person Opster ES Ninja - Kamal    schedule 07.11.2019
comment
@Kamal В некотором смысле да. Я понял, что мне нужен другой способ решить эту проблему. Однако я не нашел способа делать то, что мне хотелось бы.   -  person Patryk    schedule 07.11.2019


Ответы (1)


Боюсь, что это невозможно, потому что поле labels не является полем, которое сохраняет ES или albiet создает инвертированный индекс.

Документ doc['fieldname'] применим только к полям, для которых создается инвертированный индекс, а запрос DSL Elasticsearch также работает только с полями, для которых создается инвертированный индекс, и, к сожалению, тип nested не является допустимым полем, для которого создается инвертированный индекс.

Сказав это, у меня есть два способа сделать это.

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

Отображение:

PUT my_sample_index
{
  "mappings": {
    "properties": {
      "myfield": {
        "type": "nested",
        "properties": {
          "label": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

Образцы документов:

// single field inside 'myfield'
POST my_sample_index/_doc/1
{
  "myfield": {                              
    "label": ["New York", "LA", "Austin"]   
  }
}


// two fields inside 'myfield' 
POST my_sample_index/_doc/2
{                                          
  "myfield": {                             
    "label": ["London", "Leicester", "Newcastle", "Liverpool"],
    "country": "England"
  }
}

Решение 1. Использование полей сценария (Управление на уровне приложения)

У меня есть обходной путь, чтобы получить то, что вы хотите, не совсем так, но он поможет вам отфильтровать уровень обслуживания или приложение.

POST my_sample_index/_search
{
  "_source": "*", 
  "query": {
    "bool": {
      "must": [
        {
          "match_all": {}
        }
      ]
    }
  },
  "script_fields": {
    "label_size": {
        "script": {
            "lang": "painless",
            "source": "params['_source']['labels'].size() > 1"
        }
    }
  }
}

Вы могли бы заметить, что в ответ создается отдельное поле label_size со значением true или false.

Пример ответа выглядит примерно так:

{
  "took" : 5,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_sample_index",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "myfield" : {
            "label" : [
              "New York",
              "LA",
              "Austin"
            ]
          }
        },
        "fields" : {
          "label_size" : [              <---- Scripted Field
            false
          ]
        }
      },
      {
        "_index" : "my_sample_index",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "myfield" : {
            "country" : "England",
            "label" : [
              "London",
              "Leicester",
              "Newcastle",
              "Liverpool"
            ]
          }
        },
        "fields" : {                  <---- Scripted Field
          "label_size" : [
            true                      <---- True because it has two fields 'labels' and 'country'
          ]
        }
      }
    ]
  }
}

Обратите внимание, что только второй документ имеет смысл, поскольку он имеет два поля, то есть country и labels. Однако, если вам нужны только документы с label_size и true, это нужно будет контролировать на уровне вашего приложения.

Решение 2. Повторное индексирование с помощью labels.size с использованием обработчика сценариев

Создайте новый индекс, как показано ниже:

PUT my_sample_index_temp
{
  "mappings": {
    "properties": {
      "myfield": {
        "type": "nested",
        "properties": {
          "label": {
            "type": "keyword"
          }
        }
      },
      "labels_size":{             <---- New Field where we'd store the size
        "type": "integer"
      }
    }
  }
}

Создайте следующий конвейер:

PUT _ingest/pipeline/set_labels_size
{
  "description": "sets the value of labels size",
  "processors": [
      {
        "script": {
          "source": """
            ctx.labels_size = ctx.myfield.size();
          """
        }
      }
    ]
}

Используйте Reindex API для повторной индексации из my_sample_index индекса

POST _reindex
{
  "source": {
    "index": "my_sample_index"
  },
  "dest": {
    "index": "my_sample_index_temp",
    "pipeline": "set_labels_size"
  }
}

Подтвердите документы в my_sample_index_temp с помощью GET my_sample_index_temp/_search

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "my_sample_index_temp",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "labels_size" : 1,           <---- New Field Created 
          "myfield" : {
            "label" : [
              "New York",
              "LA",
              "Austin"
            ]
          }
        }
      },
      {
        "_index" : "my_sample_index_temp",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "labels_size" : 2,           <----- New Field Created
          "myfield" : {
            "country" : "England",
            "label" : [
              "London",
              "Leicester",
              "Newcastle",
              "Liverpool"
            ]
          }
        }
      }
    ]
  }
}

Теперь вы можете просто использовать это поле labels_size в своем запросе, и это будет проще, не говоря уже об эффективности.

Надеюсь это поможет!

person Opster ES Ninja - Kamal    schedule 04.11.2019