バケット範囲フィールドの微妙な違い

ドキュメントは、それが入る各バケットでカウントされます

範囲は複数の値を表すため、範囲フィールドに対してバケット集計を実行すると、同じドキュメントが複数のバケットに入る可能性があります。これにより、バケットのカウントの合計が一致したドキュメントの数を超えるなど、驚くべき動作が発生することがあります。たとえば、次のインデックスを考えてみてください:

Python

  1. resp = client.indices.create(
  2. index="range_index",
  3. settings={
  4. "number_of_shards": 2
  5. },
  6. mappings={
  7. "properties": {
  8. "expected_attendees": {
  9. "type": "integer_range"
  10. },
  11. "time_frame": {
  12. "type": "date_range",
  13. "format": "yyyy-MM-dd||epoch_millis"
  14. }
  15. }
  16. },
  17. )
  18. print(resp)
  19. resp1 = client.index(
  20. index="range_index",
  21. id="1",
  22. refresh=True,
  23. document={
  24. "expected_attendees": {
  25. "gte": 10,
  26. "lte": 20
  27. },
  28. "time_frame": {
  29. "gte": "2019-10-28",
  30. "lte": "2019-11-04"
  31. }
  32. },
  33. )
  34. print(resp1)

Ruby

  1. response = client.indices.create(
  2. index: 'range_index',
  3. body: {
  4. settings: {
  5. number_of_shards: 2
  6. },
  7. mappings: {
  8. properties: {
  9. expected_attendees: {
  10. type: 'integer_range'
  11. },
  12. time_frame: {
  13. type: 'date_range',
  14. format: 'yyyy-MM-dd||epoch_millis'
  15. }
  16. }
  17. }
  18. }
  19. )
  20. puts response
  21. response = client.index(
  22. index: 'range_index',
  23. id: 1,
  24. refresh: true,
  25. body: {
  26. expected_attendees: {
  27. gte: 10,
  28. lte: 20
  29. },
  30. time_frame: {
  31. gte: '2019-10-28',
  32. lte: '2019-11-04'
  33. }
  34. }
  35. )
  36. puts response

Js

  1. const response = await client.indices.create({
  2. index: "range_index",
  3. settings: {
  4. number_of_shards: 2,
  5. },
  6. mappings: {
  7. properties: {
  8. expected_attendees: {
  9. type: "integer_range",
  10. },
  11. time_frame: {
  12. type: "date_range",
  13. format: "yyyy-MM-dd||epoch_millis",
  14. },
  15. },
  16. },
  17. });
  18. console.log(response);
  19. const response1 = await client.index({
  20. index: "range_index",
  21. id: 1,
  22. refresh: "true",
  23. document: {
  24. expected_attendees: {
  25. gte: 10,
  26. lte: 20,
  27. },
  28. time_frame: {
  29. gte: "2019-10-28",
  30. lte: "2019-11-04",
  31. },
  32. },
  33. });
  34. console.log(response1);

コンソール

  1. PUT range_index
  2. {
  3. "settings": {
  4. "number_of_shards": 2
  5. },
  6. "mappings": {
  7. "properties": {
  8. "expected_attendees": {
  9. "type": "integer_range"
  10. },
  11. "time_frame": {
  12. "type": "date_range",
  13. "format": "yyyy-MM-dd||epoch_millis"
  14. }
  15. }
  16. }
  17. }
  18. PUT range_index/_doc/1?refresh
  19. {
  20. "expected_attendees" : {
  21. "gte" : 10,
  22. "lte" : 20
  23. },
  24. "time_frame" : {
  25. "gte" : "2019-10-28",
  26. "lte" : "2019-11-04"
  27. }
  28. }

次の集計では範囲が間隔よりも広いため、ドキュメントは複数のバケットに入ります。

Python

  1. resp = client.search(
  2. index="range_index",
  3. size="0",
  4. aggs={
  5. "range_histo": {
  6. "histogram": {
  7. "field": "expected_attendees",
  8. "interval": 5
  9. }
  10. }
  11. },
  12. )
  13. print(resp)

Ruby

  1. response = client.search(
  2. index: 'range_index',
  3. size: 0,
  4. body: {
  5. aggregations: {
  6. range_histo: {
  7. histogram: {
  8. field: 'expected_attendees',
  9. interval: 5
  10. }
  11. }
  12. }
  13. }
  14. )
  15. puts response

Js

  1. const response = await client.search({
  2. index: "range_index",
  3. size: 0,
  4. aggs: {
  5. range_histo: {
  6. histogram: {
  7. field: "expected_attendees",
  8. interval: 5,
  9. },
  10. },
  11. },
  12. });
  13. console.log(response);

コンソール

  1. POST /range_index/_search?size=0
  2. {
  3. "aggs": {
  4. "range_histo": {
  5. "histogram": {
  6. "field": "expected_attendees",
  7. "interval": 5
  8. }
  9. }
  10. }
  11. }

間隔が5(オフセットはデフォルトで0)であるため、バケット1015、および20が期待されます。私たちの範囲ドキュメントは、これらの3つのバケットすべてに入ります。

コンソール-結果

  1. {
  2. ...
  3. "aggregations" : {
  4. "range_histo" : {
  5. "buckets" : [
  6. {
  7. "key" : 10.0,
  8. "doc_count" : 1
  9. },
  10. {
  11. "key" : 15.0,
  12. "doc_count" : 1
  13. },
  14. {
  15. "key" : 20.0,
  16. "doc_count" : 1
  17. }
  18. ]
  19. }
  20. }
  21. }

ドキュメントはバケット内に部分的に存在することはできません。たとえば、上記のドキュメントは、上記の3つのバケットのそれぞれで三分の一としてカウントされることはありません。この例では、ドキュメントの範囲が複数のバケットに入ったため、そのドキュメントの完全な値も各バケットのサブ集計でカウントされます。

クエリの境界は集計フィルターではありません

集計されているフィールドでクエリを使用してフィルタリングすると、別の予期しない動作が発生する可能性があります。この場合、ドキュメントはクエリに一致する可能性がありますが、範囲の一方または両方の端点がクエリの外にあることがあります。上記のドキュメントに対する次の集計を考えてみてください:

Python

  1. resp = client.search(
  2. index="range_index",
  3. size="0",
  4. query={
  5. "range": {
  6. "time_frame": {
  7. "gte": "2019-11-01",
  8. "format": "yyyy-MM-dd"
  9. }
  10. }
  11. },
  12. aggs={
  13. "november_data": {
  14. "date_histogram": {
  15. "field": "time_frame",
  16. "calendar_interval": "day",
  17. "format": "yyyy-MM-dd"
  18. }
  19. }
  20. },
  21. )
  22. print(resp)

Ruby

  1. response = client.search(
  2. index: 'range_index',
  3. size: 0,
  4. body: {
  5. query: {
  6. range: {
  7. time_frame: {
  8. gte: '2019-11-01',
  9. format: 'yyyy-MM-dd'
  10. }
  11. }
  12. },
  13. aggregations: {
  14. november_data: {
  15. date_histogram: {
  16. field: 'time_frame',
  17. calendar_interval: 'day',
  18. format: 'yyyy-MM-dd'
  19. }
  20. }
  21. }
  22. }
  23. )
  24. puts response

Js

  1. const response = await client.search({
  2. index: "range_index",
  3. size: 0,
  4. query: {
  5. range: {
  6. time_frame: {
  7. gte: "2019-11-01",
  8. format: "yyyy-MM-dd",
  9. },
  10. },
  11. },
  12. aggs: {
  13. november_data: {
  14. date_histogram: {
  15. field: "time_frame",
  16. calendar_interval: "day",
  17. format: "yyyy-MM-dd",
  18. },
  19. },
  20. },
  21. });
  22. console.log(response);

コンソール

  1. POST /range_index/_search?size=0
  2. {
  3. "query": {
  4. "range": {
  5. "time_frame": {
  6. "gte": "2019-11-01",
  7. "format": "yyyy-MM-dd"
  8. }
  9. }
  10. },
  11. "aggs": {
  12. "november_data": {
  13. "date_histogram": {
  14. "field": "time_frame",
  15. "calendar_interval": "day",
  16. "format": "yyyy-MM-dd"
  17. }
  18. }
  19. }
  20. }

クエリが11月の日のみを考慮しているにもかかわらず、集計は8つのバケット(10月に4つ、11月に4つ)を生成します。これは、集計がすべての一致したドキュメントの範囲に対して計算されるためです。

コンソール-結果

  1. {
  2. ...
  3. "aggregations" : {
  4. "november_data" : {
  5. "buckets" : [
  6. {
  7. "key_as_string" : "2019-10-28",
  8. "key" : 1572220800000,
  9. "doc_count" : 1
  10. },
  11. {
  12. "key_as_string" : "2019-10-29",
  13. "key" : 1572307200000,
  14. "doc_count" : 1
  15. },
  16. {
  17. "key_as_string" : "2019-10-30",
  18. "key" : 1572393600000,
  19. "doc_count" : 1
  20. },
  21. {
  22. "key_as_string" : "2019-10-31",
  23. "key" : 1572480000000,
  24. "doc_count" : 1
  25. },
  26. {
  27. "key_as_string" : "2019-11-01",
  28. "key" : 1572566400000,
  29. "doc_count" : 1
  30. },
  31. {
  32. "key_as_string" : "2019-11-02",
  33. "key" : 1572652800000,
  34. "doc_count" : 1
  35. },
  36. {
  37. "key_as_string" : "2019-11-03",
  38. "key" : 1572739200000,
  39. "doc_count" : 1
  40. },
  41. {
  42. "key_as_string" : "2019-11-04",
  43. "key" : 1572825600000,
  44. "doc_count" : 1
  45. }
  46. ]
  47. }
  48. }
  49. }

使用ケースによっては、CONTAINSクエリが、クエリされた範囲に完全に含まれるドキュメントのみに制限することがあります。この例では、1つのドキュメントは含まれず、集計は空になります。集計後にバケットをフィルタリングすることもオプションであり、ドキュメントがカウントされるべきであるが、範囲外のデータは安全に無視できる使用ケースに適しています。