Запрос Elasticsearch для массива объектов с датой

Привет, я новичок в Elasticsearch и пытаюсь реализовать решение, используя spring-data-elasticsearch. Мои проиндексированные данные выглядят так:

[
  {
    "worker": "A",
    "availability": [
      {
        "startDate": "2020-01-12",
        "endDate": "2020-02-12"
      },
      {
        "startDate": "2020-04-12",
        "endDate": "2020-05-12"
      }
    ]
  },
  {
    "worker": "B",
    "availability": [
      {
        "startDate": "2020-04-12",
        "endDate": "2020-11-12"
      }
    ]
  }
]

Ссылаясь на эластичные документы, я планировал использовать запрос диапазона для получения записей за указанный диапазон дат, например, я хотел получить доступного рабочего с 12 мая 2020 года по 12 июня 2020 года. Это запрос, который я сформировал:

{
    "query": {
        "bool": {
            "must": [
                {
                    "nested": {
                        "query": {
                            "range": {
                                "availability.start_date": {
                                    "from": "2020-05-12T00:00:00.000Z",
                                    "to": "2020-06-12T00:00:00.000Z",
                                    "include_lower": true,
                                    "include_upper": true,
                                    "boost": 1.0
                                }
                            }
                        },
                        "path": "availability",
                        "ignore_unmapped": false,
                        "score_mode": "none",
                        "boost": 1.0
                    }
                }
            ],
            "adjust_pure_negative": true,
            "boost": 1.0
        }
    }
}

Приведенный выше запрос показывает пустые совпадения при его выполнении, но когда я использую проиндексированные даты, я могу получить записи (например, если я укажу даты с 2020-04-12 по 2020-11-12, рабочий результат B отображаются). Согласно запросу диапазона, это должно было сработать и для моего более раннего случая, если я не ошибаюсь. Есть ли что-то неправильное в подходе, которому я следовал. Пожалуйста посоветуй.




Ответы (1)


У вас нет работника, чье start_date (!!) находится между 2020-05-12 и 2020-06-12. Я думаю, вам нужно действовать по-другому для того, чего вы пытаетесь достичь.

Поскольку вы пытаетесь сопоставить диапазоны, вероятно, будет проще использовать date_range тип поля. Ваше сопоставление должно выглядеть так:

PUT your-index
{
  "mappings": {
    "properties": {
      "availability": {
        "type": "date_range", 
        "format": "yyyy-MM-dd"
      }
    }
  }
}

Затем вы можете проиндексировать все возможности вашего работника следующим образом:

{
  "worker": "A",
  "availability": [
    {
      "gte": "2020-01-12",
      "lte": "2020-02-12"
    },
    {
      "gte": "2020-04-12",
      "lte": "2020-05-12"
    }
  ]
}
{
  "worker": "B",
  "availability": [
    {
      "gte": "2020-04-12",
      "lte": "2020-11-12"
    }
  ]
}

И затем вы можете выполнить поиск, который вы хотите, следующим образом:

{
  "query": {
    "range": {
      "availability": {
        "gte": "2020-05-12",
        "lte": "2020-06-12",
        "relation": "contains"
      }
    }
  }
}

И вы обнаружите, что только рабочий B удовлетворяет условию.

ОБНОВЛЕНИЕ

Ваш запрос в Java должен быть таким:

    final BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();

    queryBuilder.must(QueryBuilders.matchQuery("name", "A"));

    RangeQueryBuilder availability = QueryBuilders.rangeQuery("availability")
           .gte(query.getStartDate())
           .lte(query.getEndDate());

    queryBuilder.must(availability);

    Pageable pageable = PageRequest.of(pageNumber, pageSize);

    // @formatter:off
    return new NativeSearchQueryBuilder()
            .withPageable(pageable)
            .withQuery(queryBuilder)
            .build();
person Val    schedule 11.01.2021
comment
Привет @Val, я попробовал твой подход, но получаю исключение, например org.elasticsearch.ElasticsearchException: Elasticsearch exception [type=mapper_parsing_exception, reason=error parsing field [availability.gte], expected an object but got gte] - person Sandeep K Nair; 11.01.2021
comment
Вы, должно быть, пропустили шаг, можете перепроверить? Я проверил выше, и это сработало. Можете ли вы сказать, какую версию ES вы используете? - person Val; 11.01.2021
comment
Привет, @Val, вот минимальная копия проекта github.com/sandeepsuvit/search-poc. Не могли бы вы указать мне правильное направление, дайте мне знать, если я что-то упустил в конфигурации. - person Sandeep K Nair; 11.01.2021
comment
За это респект!!! этот должен быть типа FieldType.Date_Range, вам не нужен класс Availability, это должна быть просто карта с ключами gte и lte - person Val; 11.01.2021
comment
Привет, @Val, спасибо за проверку, но когда я пытаюсь проиндексировать массив availability, возникает ошибка, например: Ошибка синтаксического анализа JSON: невозможно десериализовать экземпляр java.util.LinkedHashMap<java.lang.String,java.lang.String> из токена START_ARRAY; вложенным исключением является com.fasterxml.jackson.databind.exc.MismatchedInputException: невозможно десериализовать экземпляр java.util.LinkedHashMap<java.lang.String,java.lang.String> из токена START_ARRAY (через цепочку ссылок: com.search.documents.Worker[\availability\]) - person Sandeep K Nair; 12.01.2021
comment
Ваш репозиторий обновлен? Почему значение карты является строкой, а не датой? Your test search query is not correct by the way - person Val; 12.01.2021
comment
Ошибка, которую вы получаете, связана с тем, что availability на самом деле должно быть List<Map<String, String>> или List<Map<String, Date>>. - person Val; 12.01.2021
comment
Ой, извините, это была моя ошибка. Исправил это сейчас и обновил мое репо. Теперь я могу индексировать данные и запрашивать результаты. Это запрос, который я формирую сейчас. Это правильно? java final RangeQueryBuilder queryBuilder = QueryBuilders.rangeQuery("availability"); queryBuilder.gte(query.getStartDate()); queryBuilder.lte(query.getEndDate()); return new NativeSearchQueryBuilder() .withQuery(queryBuilder) .build(); - person Sandeep K Nair; 12.01.2021
comment
Большой! Как я уже говорил ранее, ваш запрос неверен. Я обновил свой ответ, чтобы показать вам правильный путь. - person Val; 12.01.2021
comment
Вау, большое спасибо за это, очень ценю это. Теперь он работает идеально. У меня есть еще один вопрос, если можно. Предположим, мне нужно добавить еще одно поле с именем partial: true в availability, указывающее, что рабочий доступен только частично в определенный день, и я хотел, чтобы этот флаг также был параметром запроса, насколько это будет отличаться? Я думаю, что мы не можем добавить его к date_range объекту карты availability в этом случае, верно? - person Sandeep K Nair; 12.01.2021
comment
Круто, рад, что получилось! Да, вам нужно воскресить свой объект Availability и переместить туда диапазон дат вместе с новым полем partial. это станет составным объектом, и вам нужно будет сделать поле availability nested - person Val; 12.01.2021
comment
О, я понял. Что-то вроде этого, верно? java public class Availability { @Field(type = FieldType.Boolean) private boolean partial; @Field(type = FieldType.Date_Range, format = DateFormat.custom, pattern = "uuuu-MM-dd") private List<Map<String, LocalDate>> dates; } ... // Then in the parent Worker.java mapping to Availability @Field(type = FieldType.Nested) private List<Availability> availability; - person Sandeep K Nair; 12.01.2021
comment
Просто private Map<String, LocalDate> dates; внутри Availability, потому что на уровне Worker у вас уже есть список Availability - person Val; 12.01.2021
comment
В яблочко. Это была опечатка, моя ошибка, я обновил свой код. Это то, что вы имеете в виду? - person Sandeep K Nair; 12.01.2021
comment
Да, это правильно сейчас. И так как вы ввели вложенный тип, теперь запрос тоже должен быть вложенным, но это легко сделать - person Val; 12.01.2021
comment
Привет @Val, я обновил свой запрос в коде и думаю, что он правильный с точки зрения вложенности. Но результатов сейчас не видно. Это из-за сформированного запроса? java queryBuilder.must(QueryBuilders.termQuery("availability.partial", query.isPartial())); RangeQueryBuilder availability = QueryBuilders.rangeQuery("availability.dates") .gte(query.getStartDate()) .lte(query.getEndDate()); queryBuilder.must(availability); - person Sandeep K Nair; 12.01.2021
comment
Не могли бы вы создать новый вопрос (с правильно отформатированным кодом), этот становится переполненным :-) - person Val; 12.01.2021
comment
Конечно, хотелось бы сделать это. Вот ссылка на этот вопрос stackoverflow.com/questions/65679710/ - person Sandeep K Nair; 12.01.2021
comment
Это было для меня? Я уже проголосовал за ваш ответ - person Sandeep K Nair; 12.01.2021
comment
Извините, я не заметил эту опцию на экране. Сделанный! :) и спасибо, что указали мне правильное направление - person Sandeep K Nair; 12.01.2021
comment
Большое спасибо! - person Val; 12.01.2021