Порядок ElasticSearch по количеству совпадений во вложенных полях

Полный новичок здесь, вполне возможно, пытающийся сделать невозможное. У меня есть следующая структура, которую я хотел бы сохранить в Elasticsearch:

{
    "id" : 1,
    "code" : "03f3301c-4089-11e7-a919-92ebcb67fe33",
    "countries" : [
        {
            "id" : 1,
            "name" : "Netherlands"
        },
        {
            "id" : 2,
            "name" : "United Kingdom"
        }
    ],
    "tags" : [
        {
            "id" : 1,
            "name" : "Scanned"
        },
        {
            "id" : 2,
            "name" : "Secured"
        },
        {
            "id" : 3,
            "name" : "Cleared"
        }
    ]
}

У меня есть полный контроль над тем, как он будет храниться, поэтому структура может меняться, но она должна содержать все эти поля в той или иной форме. Я хотел бы иметь возможность запрашивать эти данные по countries и tags таким образом, чтобы возвращались все элементы, имеющие хотя бы одно совпадение, упорядоченные по количеству совпадений. Если это вообще возможно, я бы предпочел не выполнять полнотекстовый поиск.

Например:

id, code, country ids, tag ids
1,  ...,  [1, 2, 3],   [1]
2,  ...,  [1],         [1, 2, 3]

На вопрос: "which of these was in country 1 or has tag 1 or has tag 2" должно вернуться:

2, ..., [1], [1, 2, 3]
1, ..., [1, 2, 3], [1]

Именно в таком порядке, потому что вторая строка соответствует большему количеству подзапросов в приведенной выше дизъюнкции.

По сути, я хотел бы воспроизвести этот SQL-запрос:

SELECT p.id, p.code, COUNT(p.id) FROM packages p
LEFT JOIN tags t ON t.package_id = p.id
LEFT JOIN countries c ON c.package_id = p.id
WHERE t.id IN (1, 2, 3) OR c.id IN (1, 2, 3)
GROUP BY p.id
ORDER BY COUNT(p.id);

Я использую ElasticSearch 2.4.5, если это имеет значение.

Надеюсь, я был достаточно ясен. Спасибо за помощь!


person Robert    schedule 24.05.2017    source источник


Ответы (1)


Вам нужно, чтобы countries и tags были типа nested. Кроме того, вам нужно взять под контроль оценку с помощью function_score, дать weight из 1 для запросов внутри function_score, а также поиграть с boost_mode и score_mode. В конце вы можете использовать этот запрос:

GET /nested/test/_search
{
  "query": {
    "function_score": {
      "query": {
        "match_all": {}
      }, 
      "functions": [
        {
          "filter": {
            "nested": {
              "path": "tags",
              "query": {
                "term": {
                  "tags.id": 1
                }
              }
            }
          },
          "weight": 1
        },
        {
          "filter": {
            "nested": {
              "path": "tags",
              "query": {
                "term": {
                  "tags.id": 2
                }
              }
            }
          },
          "weight": 1
        },
        {
          "filter": {
            "nested": {
              "path": "countries",
              "query": {
                "term": {
                  "countries.id": 1
                }
              }
            }
          },
          "weight": 1
        }
      ],
      "boost_mode": "replace", 
      "score_mode": "sum"
    }
  }
}

Для более полного теста я также предоставляю сопоставление и тестовые данные:

PUT nested
{
  "mappings": {
    "test": {
      "properties": {
        "tags": {
          "type": "nested",
          "properties": {
            "name": {
              "type": "string",
              "index": "not_analyzed"
            }
          }
        },
        "countries": {
          "type": "nested",
          "properties": {
            "name": {
              "type": "string",
              "index": "not_analyzed"
            }
          }
        }
      }
    }
  }
}

POST nested/test/_bulk
{"index":{"_id":1}}
{"name":"Foo Bar","tags":[{"id":2,"name":"My Tag 5"},{"id":3,"name":"My Tag 7"}],"countries":[{"id":1,"name":"USA"}]}
{"index":{"_id":2}}
{"name":"Foo Bar","tags":[{"id":3,"name":"My Tag 6"}],"countries":[{"id":1,"name":"USA"},{"id":2,"name":"UK"},{"id":3,"name":"UAE"}]}
{"index":{"_id":3}}
{"name":"Foo Bar","tags":[{"id":1,"name":"My Tag 4"},{"id":3,"name":"My Tag 1"}],"countries":[{"id":3,"name":"UAE"}]}
{"index":{"_id":4}}
{"name":"Foo Bar","tags":[{"id":1,"name":"My Tag 1"},{"id":2,"name":"My Tag 4"},{"id":3,"name":"My Tag 2"}],"countries":[{"id":2,"name":"UK"},{"id":3,"name":"UAE"}]}
person Andrei Stefan    schedule 26.05.2017
comment
Спасибо, это сработало отлично! Одно небольшое изменение, которое я должен был сделать, это установить «вес» всех функций равным 2 и добавить «min_score» 2, потому что, если документ не соответствует ни одному из фильтров, он все равно получит «оценку» 1 . - person Robert; 28.05.2017