ElasticSearch - повернення унікальних значень


122

Як я можу отримати значення всіх languagesзаписів із записів і зробити їх унікальними.

Записи

PUT items/1
{ "language" : 10 }

PUT items/2
{ "language" : 11 }

PUT items/3
{ "language" : 10 }

Запит

GET items/_search
{ ... }

# => Expected Response
[10, 11]

Будь-яка допомога була б чудовою.


1
fields: [languages]дасть лише значення даного поля, але зробити їх унікальними, мабуть, простіше зробити в коді. Хоча, можливо, є зручна сукупність, яка може зробити це за вас.
Ашалінд

1
Для тих, хто досліджує цю тему, тут також є корисна дискусія: Знайдіть виразні значення, а не окремі підрахунки в еластичному дослідженні
blong

Відповіді:


165

Ви можете використовувати агрегацію термінів .

{
"size": 0,
"aggs" : {
    "langs" : {
        "terms" : { "field" : "language",  "size" : 500 }
    }
}}

Пошук поверне щось на зразок:

{
"took" : 16,
"timed_out" : false,
"_shards" : {
  "total" : 2,
  "successful" : 2,
  "failed" : 0
},
"hits" : {
"total" : 1000000,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
  "langs" : {
    "buckets" : [ {
      "key" : "10",
      "doc_count" : 244812
    }, {
      "key" : "11",
      "doc_count" : 136794

    }, {
      "key" : "12",
      "doc_count" : 32312
       } ]
    }
  }
}

sizeПараметр в межах агрегації вказує максимальне число членів включити в результаті агрегації. Якщо вам потрібні всі результати, встановіть це значення, яке перевищує кількість унікальних термінів у ваших даних.


2
"fields" : ["language"]повертає той же результат. Чи можете ви розширити свою відповідь, щоб побачити, чи може структура агрегації повертати лише мовні значення? #=> [10, 11, 10]
ChuckJHardy

1
@CharlesJHardy, це не має однакового результату. Дані, які ви шукаєте, знаходяться під клавішею "агрегації". Я відредагував свою відповідь на прикладі результату. Ви також можете встановити "розмір": 0, щоб не включати жоден документ, а лише узагальнені результати, які ви хотіли.
Антон

1
Зауважте, що якщо у вас є багато можливих значень, можливо, languageви хочете додати size=0і shard_size=0, щоб переконатися, що ви отримаєте всі значення. Дивіться elasticsearch.org/guide/en/elasticsearch/reference/current/…
Dror

3
Я думаю, що ця відповідь не стосується ОП. Оригінальне запитання хоче, щоб різні значення не враховувалися. Я щось пропускаю?
bhurlow

4
@BHBH, Відповідь дає чіткі значення. Вони є "ключовими" значеннями, тобто "10", "11" і "12". (агрегації> langs> відра> ключ ...)
Антон

9

Elasticsearch 1.1+ має Агрегацію Кардинальності, яка дасть Вам унікальну кількість

Зауважте, що насправді наближення і точність можуть зменшуватися за допомогою наборів даних з високою кардинальністю, але це, як правило, досить точно в моєму тестуванні.

Ви також можете налаштувати точність за допомогою precision_thresholdпараметра. Компроміс, або звичайно, - використання пам'яті.

Цей графік з Документів показує, як вище precision_thresholdпризводить до набагато точніших результатів.


Відносна похибка та поріг


2
Чи гарантує агрегація кардинальності, що якщо термін існує, то він відображатиметься в результатах (з підрахунком> = 1)? Чи, можливо, він може пропустити деякі терміни, які з’являються лише один раз у великому наборі даних?
позначити

2
@mark це залежить від встановленого порогу точності. Чим вище поріг, тим менший шанс, що він пропустить. Зауважте, що при встановленні граничного значення точності встановлено обмеження в 40 000. Це означає, що набір даних вище, ніж буде, буде оцінка, а значить, єдине значення може бути пропущене
Sundar

12
Я вважаю, що ця відповідь неправильна. Агрегація кардинальності - чудовий інструмент. Однак завданням було отримати самі терміни, а не оцінити, скільки існує різних термінів.
Антон

4

якщо ви хочете отримати перший документ для кожного languageполя унікального значення, ви можете зробити це:

{
 "query": {
    "match_all": {
    }
  },
  "collapse": {
    "field": "language.keyword",
    "inner_hits": {
    "name": "latest",
      "size": 1
    }
  }
}

3

Я шукаю подібне рішення і для себе. Я знайшов посилання в терміні агрегації .

Отже, згідно з цим, наступне - правильне рішення.

{
"aggs" : {
    "langs" : {
        "terms" : { "field" : "language",  
                    "size" : 500 }
    }
}}

Але якщо ви зіткнулися з такою помилкою:

"error": {
        "root_cause": [
            {
                "type": "illegal_argument_exception",
                "reason": "Fielddata is disabled on text fields by default. Set fielddata=true on [fastest_method] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory. Alternatively use a keyword field instead."
            }
        ]}

У такому випадку вам потрібно додати " KEYWORD " у запиті, наприклад:

   {
    "aggs" : {
        "langs" : {
            "terms" : { "field" : "language.keyword",  
                        "size" : 500 }
        }
    }}

1

Якщо ви хочете отримати всі унікальні значення без будь-якого наближення чи встановлення магічного числа ( size: 500), використовуйте КОМПОЗИТНУ АГРЕГАЦІЮ (ES 6.5+) .

З офіційної документації :

"Якщо ви хочете отримати всі терміни або всі комбінації термінів в агрегації вкладених термінів, ви повинні використовувати КОМПОЗИТНУ АГРЕГАЦІЮ, яка дозволяє пропагувати всі можливі терміни, а не встановлювати розмір, більший, ніж простота поля в агрегації термінів. Агрегація термінів призначена для повернення основних термінів і не дозволяє пагінацію. "

Приклад реалізації в JavaScript:

const ITEMS_PER_PAGE = 1000;

const body =  {
    "size": 0, // Returning only aggregation results: https://www.elastic.co/guide/en/elasticsearch/reference/current/returning-only-agg-results.html
    "aggs" : {
        "langs": {
            "composite" : {
                "size": ITEMS_PER_PAGE,
                "sources" : [
                    { "language": { "terms" : { "field": "language" } } }
                ]
            }
        }
     }
};

const uniqueLanguages = [];

while (true) {
  const result = await es.search(body);

  const currentUniqueLangs = result.aggregations.langs.buckets.map(bucket => bucket.key);

  uniqueLanguages.push(...currentUniqueLangs);

  const after = result.aggregations.langs.after_key;

  if (after) {
      // continue paginating unique items
      body.aggs.langs.composite.after = after;
  } else {
      break;
  }
}

console.log(uniqueLanguages);

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.