ステミングを用いた正確な検索の混合

検索アプリケーションを構築する際、ステミングはしばしば必須です。skiing に対するクエリが skiskis を含む文書と一致することが望ましいからです。しかし、ユーザーが特に skiing を検索したい場合はどうでしょうか?これを行う一般的な方法は、同じコンテンツを異なる方法でインデックス化するために multi-field を使用することです。

Python

  1. resp = client.indices.create(
  2. index="index",
  3. settings={
  4. "analysis": {
  5. "analyzer": {
  6. "english_exact": {
  7. "tokenizer": "standard",
  8. "filter": [
  9. "lowercase"
  10. ]
  11. }
  12. }
  13. }
  14. },
  15. mappings={
  16. "properties": {
  17. "body": {
  18. "type": "text",
  19. "analyzer": "english",
  20. "fields": {
  21. "exact": {
  22. "type": "text",
  23. "analyzer": "english_exact"
  24. }
  25. }
  26. }
  27. }
  28. },
  29. )
  30. print(resp)
  31. resp1 = client.index(
  32. index="index",
  33. id="1",
  34. document={
  35. "body": "Ski resort"
  36. },
  37. )
  38. print(resp1)
  39. resp2 = client.index(
  40. index="index",
  41. id="2",
  42. document={
  43. "body": "A pair of skis"
  44. },
  45. )
  46. print(resp2)
  47. resp3 = client.indices.refresh(
  48. index="index",
  49. )
  50. print(resp3)

Ruby

  1. response = client.indices.create(
  2. index: 'index',
  3. body: {
  4. settings: {
  5. analysis: {
  6. analyzer: {
  7. english_exact: {
  8. tokenizer: 'standard',
  9. filter: [
  10. 'lowercase'
  11. ]
  12. }
  13. }
  14. }
  15. },
  16. mappings: {
  17. properties: {
  18. body: {
  19. type: 'text',
  20. analyzer: 'english',
  21. fields: {
  22. exact: {
  23. type: 'text',
  24. analyzer: 'english_exact'
  25. }
  26. }
  27. }
  28. }
  29. }
  30. }
  31. )
  32. puts response
  33. response = client.index(
  34. index: 'index',
  35. id: 1,
  36. body: {
  37. body: 'Ski resort'
  38. }
  39. )
  40. puts response
  41. response = client.index(
  42. index: 'index',
  43. id: 2,
  44. body: {
  45. body: 'A pair of skis'
  46. }
  47. )
  48. puts response
  49. response = client.indices.refresh(
  50. index: 'index'
  51. )
  52. puts response

Js

  1. const response = await client.indices.create({
  2. index: "index",
  3. settings: {
  4. analysis: {
  5. analyzer: {
  6. english_exact: {
  7. tokenizer: "standard",
  8. filter: ["lowercase"],
  9. },
  10. },
  11. },
  12. },
  13. mappings: {
  14. properties: {
  15. body: {
  16. type: "text",
  17. analyzer: "english",
  18. fields: {
  19. exact: {
  20. type: "text",
  21. analyzer: "english_exact",
  22. },
  23. },
  24. },
  25. },
  26. },
  27. });
  28. console.log(response);
  29. const response1 = await client.index({
  30. index: "index",
  31. id: 1,
  32. document: {
  33. body: "Ski resort",
  34. },
  35. });
  36. console.log(response1);
  37. const response2 = await client.index({
  38. index: "index",
  39. id: 2,
  40. document: {
  41. body: "A pair of skis",
  42. },
  43. });
  44. console.log(response2);
  45. const response3 = await client.indices.refresh({
  46. index: "index",
  47. });
  48. console.log(response3);

コンソール

  1. PUT index
  2. {
  3. "settings": {
  4. "analysis": {
  5. "analyzer": {
  6. "english_exact": {
  7. "tokenizer": "standard",
  8. "filter": [
  9. "lowercase"
  10. ]
  11. }
  12. }
  13. }
  14. },
  15. "mappings": {
  16. "properties": {
  17. "body": {
  18. "type": "text",
  19. "analyzer": "english",
  20. "fields": {
  21. "exact": {
  22. "type": "text",
  23. "analyzer": "english_exact"
  24. }
  25. }
  26. }
  27. }
  28. }
  29. }
  30. PUT index/_doc/1
  31. {
  32. "body": "Ski resort"
  33. }
  34. PUT index/_doc/2
  35. {
  36. "body": "A pair of skis"
  37. }
  38. POST index/_refresh

このような設定では、skibody で検索すると、両方の文書が返されます:

Python

  1. resp = client.search(
  2. index="index",
  3. query={
  4. "simple_query_string": {
  5. "fields": [
  6. "body"
  7. ],
  8. "query": "ski"
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. query: {
  5. simple_query_string: {
  6. fields: [
  7. 'body'
  8. ],
  9. query: 'ski'
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. query: {
  4. simple_query_string: {
  5. fields: ["body"],
  6. query: "ski",
  7. },
  8. },
  9. });
  10. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body" ],
  6. "query": "ski"
  7. }
  8. }
  9. }

コンソール-結果

  1. {
  2. "took": 2,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 1,
  6. "successful": 1,
  7. "skipped" : 0,
  8. "failed": 0
  9. },
  10. "hits": {
  11. "total" : {
  12. "value": 2,
  13. "relation": "eq"
  14. },
  15. "max_score": 0.18232156,
  16. "hits": [
  17. {
  18. "_index": "index",
  19. "_id": "1",
  20. "_score": 0.18232156,
  21. "_source": {
  22. "body": "Ski resort"
  23. }
  24. },
  25. {
  26. "_index": "index",
  27. "_id": "2",
  28. "_score": 0.18232156,
  29. "_source": {
  30. "body": "A pair of skis"
  31. }
  32. }
  33. ]
  34. }
  35. }

一方、skibody.exact で検索すると、1 の文書のみが返されます。なぜなら、body.exact の分析チェーンはステミングを行わないからです。

Python

  1. resp = client.search(
  2. index="index",
  3. query={
  4. "simple_query_string": {
  5. "fields": [
  6. "body.exact"
  7. ],
  8. "query": "ski"
  9. }
  10. },
  11. )
  12. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. query: {
  5. simple_query_string: {
  6. fields: [
  7. 'body.exact'
  8. ],
  9. query: 'ski'
  10. }
  11. }
  12. }
  13. )
  14. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. query: {
  4. simple_query_string: {
  5. fields: ["body.exact"],
  6. query: "ski",
  7. },
  8. },
  9. });
  10. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body.exact" ],
  6. "query": "ski"
  7. }
  8. }
  9. }

コンソール-結果

  1. {
  2. "took": 1,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 1,
  6. "successful": 1,
  7. "skipped" : 0,
  8. "failed": 0
  9. },
  10. "hits": {
  11. "total" : {
  12. "value": 1,
  13. "relation": "eq"
  14. },
  15. "max_score": 0.8025915,
  16. "hits": [
  17. {
  18. "_index": "index",
  19. "_id": "1",
  20. "_score": 0.8025915,
  21. "_source": {
  22. "body": "Ski resort"
  23. }
  24. }
  25. ]
  26. }
  27. }

これはエンドユーザーに簡単に公開できるものではありません。なぜなら、彼らが正確な一致を探しているのかどうかを判断し、それに応じて適切なフィールドにリダイレクトする方法が必要だからです。また、クエリの一部だけを正確に一致させ、他の部分はステミングを考慮する必要がある場合はどうすればよいのでしょうか?

幸いなことに、query_stringsimple_query_string のクエリには、この正確な問題を解決する機能があります:quote_field_suffix。これは、引用符の間に現れる単語が異なるフィールドにリダイレクトされるべきであることを Elasticsearch に伝えます。以下を参照してください:

Python

  1. resp = client.search(
  2. index="index",
  3. query={
  4. "simple_query_string": {
  5. "fields": [
  6. "body"
  7. ],
  8. "quote_field_suffix": ".exact",
  9. "query": "\"ski\""
  10. }
  11. },
  12. )
  13. print(resp)

Ruby

  1. response = client.search(
  2. index: 'index',
  3. body: {
  4. query: {
  5. simple_query_string: {
  6. fields: [
  7. 'body'
  8. ],
  9. quote_field_suffix: '.exact',
  10. query: '"ski"'
  11. }
  12. }
  13. }
  14. )
  15. puts response

Js

  1. const response = await client.search({
  2. index: "index",
  3. query: {
  4. simple_query_string: {
  5. fields: ["body"],
  6. quote_field_suffix: ".exact",
  7. query: '"ski"',
  8. },
  9. },
  10. });
  11. console.log(response);

コンソール

  1. GET index/_search
  2. {
  3. "query": {
  4. "simple_query_string": {
  5. "fields": [ "body" ],
  6. "quote_field_suffix": ".exact",
  7. "query": "\"ski\""
  8. }
  9. }
  10. }

コンソール-結果

  1. {
  2. "took": 2,
  3. "timed_out": false,
  4. "_shards": {
  5. "total": 1,
  6. "successful": 1,
  7. "skipped" : 0,
  8. "failed": 0
  9. },
  10. "hits": {
  11. "total" : {
  12. "value": 1,
  13. "relation": "eq"
  14. },
  15. "max_score": 0.8025915,
  16. "hits": [
  17. {
  18. "_index": "index",
  19. "_id": "1",
  20. "_score": 0.8025915,
  21. "_source": {
  22. "body": "Ski resort"
  23. }
  24. }
  25. ]
  26. }
  27. }

上記のケースでは、ski が引用符の間にあったため、quote_field_suffix パラメータにより body.exact フィールドで検索され、1 の文書のみが一致しました。これにより、ユーザーは正確な検索とステミング検索を自由に混合できます。

quote_field_suffix に渡されたフィールドが存在しない場合、検索はクエリ文字列のデフォルトフィールドを使用することにフォールバックします。