Elasticsearch-dsl с вложенными фильтрами и условиями И и ИЛИ с точным соответствием - PullRequest
1 голос
/ 17 июня 2020

Из внешнего интерфейса поступают три параметра:

  1. State - строка
  2. Categories - массив строк. Строка может состоять из нескольких слов.
  3. Tags - аналогично категориям.

Все параметры необязательны.

Если передается несколько , нужно реализовать свою связку через AND (и совпадение state, и category, и tag). Если отправлено несколько categories или tags , совпадение будет выполнено для хотя бы одного из них .

То есть, если запрос приходит с параметрами

{"state": "Alaska", "categories": ["category 1", "category 2"]}

ответы будут

  • state = Alaska, categories = category 1;
  • state = Alaska, categories = category 2;
  • state = Alaska, categories = [category 1, category 2];
  • state = Alaska, categories = [category 1, category 3] ( имеет хотя бы одну из запрошенных категорий ).

не подходят

  • state = Alabama, categories = category 1
  • state = Alaska, categories = 3
  • state = Alaska, categories = 1 category ( название категории должно быть 1-в-1 "category 1" != "1 category")

На elastikserch Я отправляю запросы с python (3.7). Взял библиотеку elasticsearch-dsl

Собрал три фильтра от до Q объектов (использовал в них match).

combined_filter = state_filter & categories_filter & tags_filter

Списки categories и tags делятся на subfilters through OR.

query = queries.pop()
for item in queries:
    query |= item

Такой запрос создается для elasticsearch.

Bool(minimum_should_match=1, 
    must=[Match(state='Alaska'), MatchAll()], 
    should=[Match(categories='category 1'), Match(categories='category 2')]
)

Почему этот logi c находит записи по неточно category / tag именам?

from typing import List

from elasticsearch import Elasticsearch
from elasticsearch_dsl import Q, Search
from flask import request
from flask.views import MethodView


es = Elasticsearch()


class ArticleSearchAPIView(MethodView):
    """
    Search articles using ElasticSearch
    """

    @staticmethod
    def filter_create(queries: List[Q]) -> Q:
        """
        Creates Q.OR filter
        """
        query = queries.pop()
        for item in queries:
            query |= item
        return query

    def get(self) -> dict:
        """
        Search article
        First request - with empty params
        """
        search = Search(using=es, index=ArticleModel.__tablename__)
        state_filter = categories_filter = tags_filter = Q()
        result = "Articles not found."

        data = request.get_json()
        categories = data.get("categories")
        tags = data.get("tags")
        state = data.get("state")

        if state:
            state_filter = Q("match", state=state)

        if categories:
            queries = [Q("match", categories=value) for value in categories]
            categories_filter = self.filter_create(queries)

        if tags:
            queries = [Q("match", tags=value) for value in tags]
            tags_filter = self.filter_create(queries)

        combined_filter = state_filter & categories_filter & tags_filter
        found = (
            search.filter(combined_filter)
            .execute()
            .to_dict()["hits"]
            .get("hits")
        )

        if found:
            result = [article["_source"] for article in found]
        return {"response": result}

Обновить


Связь между Article and Category и Article and Tag - MTM

Отображение

{
  "articles": {
    "mappings": {
      "properties": {
        ...
        "categories": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "state": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        },
        "tags": {
          "type": "text",
          "fields": {
            "keyword": {
              "type": "keyword",
              "ignore_above": 256
            }
          }
        }
        ...
      }
    }
  }
}

Ответы [ 2 ]

1 голос
/ 17 июня 2020

Вы можете использовать логический запрос.

В логическом запросе Логический запрос ElasticSearch

У вас есть «must», что эквивалентно оператору «И». И "следует", которое действует как оператор "ИЛИ".

{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user" : "kimchy" }
      },
      "should" : [
        { "term" : { "tag" : "wow" } },
        { "term" : { "tag" : "elasticsearch" } }
      ],
    }
  }
}
0 голосов
/ 18 июня 2020

Я решил, что использовать elasticsearch-dsl здесь не нужно.

Вот к какому решению я пришел.

from typing import Dict, List, Tuple, Union

from elasticsearch import Elasticsearch
from flask import request
from flask.views import MethodView

from .models import AticleModel  # ArticleModel.__tablename__ == "articles"


es = Elasticsearch()


class ArticleSearchAPIView(MethodView):
    """
    Search articles using ElasticSearch
    """

    def get(
        self,
    ) -> Union[
        Dict[str, Union[list, List[str]]],
        Tuple[Dict[str, str], int],
        Dict[str, Union[list, str]],
    ]:
        """
        Search articles
        """
        data = request.get_json()
        categories = data.get("categories")
        tags = data.get("tags")
        state = data.get("state")
        result = "Articles not found."

        query = {"bool": {"must": []}}
        if state:
            query["bool"]["must"].append({"term": {"state.keyword": state}})
        if categories:
            query["bool"]["must"].append(
                {"terms": {"categories.keyword": categories}}
            )
        if tags:
            query["bool"]["must"].append({"terms": {"tags.keyword": tags}})

        found = es.search(
            index=ArticleModel.__tablename__, body={"query": query},
        )["hits"].get("hits")

        if found:
            result = [article["_source"] for article in found]
        return {"response": result}
...