본문 바로가기
WIKI 기술 지식 베이스

Sort results

원문 보기 위키 갱신

출처: 문서

본문

결과 정렬

정렬 기능을 사용하면 사용자가 자신에게 가장 의미 있는 방식으로 결과를 정렬할 수 있어요.

기본적으로 전체 텍스트 쿼리는 관련성 점수를 기준으로 결과를 정렬해요. order 파라미터를 asc 또는 desc로 설정하면 어떤 필드 값으로든 결과를 오름차순 또는 내림차순으로 정렬할 수 있어요.

예를 들어 line_id 값을 기준으로 내림차순 정렬하려면 다음 쿼리를 사용하세요:

GET shakespeare/_search
{
  "query": {
    "term": {
      "play_name": {
        "value": "Henry IV"
      }
    }
  },
  "sort": [
    {
      "line_id": {
        "order": "desc"
      }
    }
  ]
}

{% include copy-curl.html %}

결과는 line_id를 기준으로 내림차순 정렬돼요:

{
  "took" : 24,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3205,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "shakespeare",
        "_id" : "3204",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3205,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Exeunt"
        },
        "sort" : [
          3205
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3203",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3204,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.45",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Let us not leave till all our own be won."
        },
        "sort" : [
          3204
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3202",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3203,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.44",
          "speaker" : "KING HENRY IV",
          "text_entry" : "And since this business so fair is done,"
        },
        "sort" : [
          3203
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3201",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3202,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.43",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Meeting the cheque of such another day:"
        },
        "sort" : [
          3202
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3200",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3201,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.42",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Rebellion in this land shall lose his sway,"
        },
        "sort" : [
          3201
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3199",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3200,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.41",
          "speaker" : "KING HENRY IV",
          "text_entry" : "To fight with Glendower and the Earl of March."
        },
        "sort" : [
          3200
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3198",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3199,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.40",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Myself and you, son Harry, will towards Wales,"
        },
        "sort" : [
          3199
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3197",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3198,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.39",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Who, as we hear, are busily in arms:"
        },
        "sort" : [
          3198
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3196",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3197,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.38",
          "speaker" : "KING HENRY IV",
          "text_entry" : "To meet Northumberland and the prelate Scroop,"
        },
        "sort" : [
          3197
        ]
      },
      {
        "_index" : "shakespeare",
        "_id" : "3195",
        "_score" : null,
        "_source" : {
          "type" : "line",
          "line_id" : 3196,
          "play_name" : "Henry IV",
          "speech_number" : 8,
          "line_number" : "5.5.37",
          "speaker" : "KING HENRY IV",
          "text_entry" : "Towards York shall bend you with your dearest speed,"
        },
        "sort" : [
          3196
        ]
      }
    ]
  }
}

sort 파라미터는 배열이므로 우선순위 순서대로 여러 필드 값을 지정할 수 있어요.

line_id 값이 서로 같은 필드가 두 개 있으면 OpenSearch는 두 번째 정렬 옵션인 speech_number를 사용해요:

GET shakespeare/_search
{
  "query": {
    "term": {
      "play_name": {
        "value": "Henry IV"
      }
    }
  },
  "sort": [
    {
      "line_id": {
        "order": "desc"
      }
    },
    {
      "speech_number": {
        "order": "desc"
      }
    }
  ]
}

{% include copy-curl.html %}

원하는 순서로 결과를 얻을 때까지 필드 값을 몇 개든 추가해 가며 정렬할 수 있어요. 꼭 숫자 값일 필요는 없어요—날짜나 타임스탬프 필드로 정렬할 수도 있어요:

"sort": [
    {
      "date": {
        "order": "desc"
      }
    }
  ]

{% include copy-curl.html %}

분석된 텍스트 필드는 문서 정렬에 사용할 수 없어요. 역색인에는 문자열 전체가 아니라 개별 토큰화된 용어만 저장되기 때문이에요. 그래서 예를 들어 play_name으로는 정렬할 수 없어요.

이 제한을 우회하려면 keyword 타입으로 매핑된 텍스트 필드의 raw 버전을 사용하면 돼요. 다음 예제에서 play_name.keyword는 분석되지 않으므로 정렬 목적으로 원본 전체 문자열의 복사본을 갖게 돼요:

GET shakespeare/_search
{
  "query": {
    "term": {
      "play_name": {
        "value": "Henry IV"
      }
    }
  },
  "sort": [
    {
      "play_name.keyword": {
        "order": "desc"
      }
    }
  ]
}

{% include copy-curl.html %}

결과는 play_name 필드를 기준으로 알파벳 순으로 정렬돼요.

더 효율적인 스크롤링을 위해서는 sort를 search_after 파라미터와 함께 사용하세요. 결과는 search_after 배열에 지정한 정렬 값 다음에 오는 문서부터 시작해요.

search_after 배열에 sort 배열과 같은 개수의 값을, 같은 순서로 넣었는지 꼭 확인하세요. 이 경우 line_id = 3202, speech_number = 8 다음에 오는 문서부터 시작하는 결과를 요청하는 거예요:

GET shakespeare/_search
{
  "query": {
    "term": {
      "play_name": {
        "value": "Henry IV"
      }
    }
  },
  "sort": [
    {
      "line_id": {
        "order": "desc"
      }
    },
    {
      "speech_number": {
        "order": "desc"
      }
    }
  ],
  "search_after": [
    "3202",
    "8"
  ]
}

{% include copy-curl.html %}

정렬 모드

정렬 모드는 배열 또는 다중 값 필드로 정렬할 때 적용돼요. 문서를 정렬할 때 어떤 배열 값을 선택할지 지정하는 역할을 해요. 숫자 배열을 담고 있는 숫자 필드는 avg, sum, median 모드로 정렬할 수 있어요. 최솟값이나 최댓값으로 정렬하려면 숫자와 문자열 데이터 타입 모두에서 동작하는 min 또는 max 모드를 사용하세요.

기본 모드는 오름차순 정렬에서는 min, 내림차순 정렬에서는 max예요.

다음 예제는 정렬 모드를 사용해 배열 필드로 정렬하는 방법을 보여줘요.

학생 성적을 담고 있는 인덱스가 있다고 가정해 보세요. 인덱스에 문서 두 개를 색인하세요:

PUT students/_doc/1
{
   "name": "John Doe",
   "grades": [70, 90]
}

{% include copy-curl.html %}

PUT students/_doc/2
{
   "name": "Mary Major",
   "grades": [80, 100]
}

{% include copy-curl.html %}

avg 모드를 사용해 모든 학생을 성적 평균이 가장 높은 순으로 정렬하세요:

GET students/_search
{
   "query" : {
      "match_all": {}
   },
   "sort" : [
      {"grades" : {"order" : "desc", "mode" : "avg"}}
   ]
}

{% include copy-curl.html %}

응답에는 grades를 기준으로 내림차순 정렬된 학생들이 담겨 있어요:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "students",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "name" : "Mary Major",
          "grades" : [
            80,
            100
          ]
        },
        "sort" : [
          90
        ]
      },
      {
        "_index" : "students",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "John Doe",
          "grades" : [
            70,
            90
          ]
        },
        "sort" : [
          80
        ]
      }
    ]
  }
}

중첩 객체 정렬

nested 객체를 정렬할 때는 정렬할 필드의 경로를 지정하는 path 파라미터를 제공하세요.

예를 들어 students 인덱스에서 first_sem 변수를 nested로 매핑하세요:

PUT students
{
  "mappings" : {
    "properties": {
      "first_sem": { 
        "type" : "nested"
      }
    }
  }
}

{% include copy-curl.html %}

중첩 필드가 있는 문서 두 개를 색인하세요:

PUT students/_doc/1
{
   "name": "John Doe",
   "first_sem" : {
     "grades": [70, 90]
   }
}

{% include copy-curl.html %}

PUT students/_doc/2
{
  "name": "Mary Major",
  "first_sem": {
    "grades": [80, 100]
  }
}

{% include copy-curl.html %}

성적 평균으로 정렬할 때는 중첩 필드의 경로를 제공하세요:

GET students/_search
{
 "query" : {
    "match_all": {}
 },
 "sort" : [
    {"first_sem.grades": {
      "order" : "desc", 
      "mode" : "avg",
      "nested": {
        "path": "first_sem"
     }
    }
    }
 ]
}

{% include copy-curl.html %}

누락된 값 처리

missing 파라미터는 누락된 값의 처리 방식을 지정해요. 내장 유효 값은 _last(누락된 값이 있는 문서를 마지막에 나열)와 _first(누락된 값이 있는 문서를 맨 처음에 나열)예요. 기본값은 _last예요. 누락된 문서의 정렬 값으로 사용할 사용자 지정 값을 지정할 수도 있어요.

예를 들어 average 필드가 있는 문서 하나와 average 필드가 없는 문서 하나를 색인할 수 있어요:

PUT students/_doc/1
{
   "name": "John Doe",
   "average": 80
}

{% include copy-curl.html %}

PUT students/_doc/2
{
   "name": "Mary Major"
}

{% include copy-curl.html %}

필드가 누락된 문서가 먼저 오도록 문서를 정렬하세요:

GET students/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "average": {
        "order": "desc",
        "missing": "_first"
      }
    }
  ]
}

{% include copy-curl.html %}

응답에는 문서 2가 먼저 나열돼요:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "students",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "name" : "Mary Major"
        },
        "sort" : [
          9223372036854775807
        ]
      },
      {
        "_index" : "students",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "John Doe",
          "average" : 80
        },
        "sort" : [
          80
        ]
      }
    ]
  }
}

매핑되지 않은 필드 무시

필드가 매핑되지 않으면 기본적으로 이 필드로 정렬하는 검색 요청은 실패해요. 이를 피하려면 unmapped_type 파라미터를 사용하면 돼요. 이 파라미터는 OpenSearch에게 해당 필드를 무시하라고 알려줘요. 예를 들어 unmapped_type을 long으로 설정하면 필드가 long 타입으로 매핑된 것처럼 처리돼요. 또한 인덱스 안에서 unmapped_type 필드가 있는 모든 문서는 이 필드에 값이 없는 것처럼 취급되므로 이 필드를 기준으로 정렬되지 않아요.

예를 들어 인덱스 두 개가 있다고 가정해 보세요. 첫 번째 인덱스에는 average 필드가 포함된 문서를 색인하세요:

PUT students/_doc/1
{
   "name": "John Doe",
   "average": 80
}

{% include copy-curl.html %}

두 번째 인덱스에는 average 필드가 포함되지 않은 문서를 색인하세요:

PUT students_no_map/_doc/2
{
   "name": "Mary Major"
}

{% include copy-curl.html %}

두 인덱스의 모든 문서를 검색하고 average 필드를 기준으로 정렬해 보세요:

GET students*/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "average": {
        "order": "desc"
      }
    }
  ]
}

{% include copy-curl.html %}

기본적으로는 average 필드가 매핑되지 않았기 때문에 두 번째 인덱스에서 오류가 발생해요:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 1,
    "failures" : [
      {
        "shard" : 0,
        "index" : "students_no_map",
        "node" : "cam9NWqVSV-jUIkQ3tRubw",
        "reason" : {
          "type" : "query_shard_exception",
          "reason" : "No mapping found for [average] in order to sort on",
          "index" : "students_no_map",
          "index_uuid" : "JgfRkypKSUSpyU-ZXr9kKA"
        }
      }
    ]
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "students",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "John Doe",
          "average" : 80
        },
        "sort" : [
          80
        ]
      }
    ]
  }
}

매핑되지 않은 필드가 무시되도록 unmapped_type 파라미터를 지정할 수 있어요:

GET students*/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "average": {
        "order": "desc",
        "unmapped_type": "long"
      }
    }
  ]
}

{% include copy-curl.html %}

응답에는 두 문서가 모두 포함돼요:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "students",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "name" : "John Doe",
          "average" : 80
        },
        "sort" : [
          80
        ]
      },
      {
        "_index" : "students_no_map",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "name" : "Mary Major"
        },
        "sort" : [
          -9223372036854775808
        ]
      }
    ]
  }
}

점수 추적

기본적으로 필드를 기준으로 정렬할 때는 점수가 계산되지 않아요. track_scores를 true로 설정하면 점수를 계산하고 추적할 수 있어요:

GET students/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "average": {
        "order": "desc"
      }
    }
  ],
  "track_scores": true
}

{% include copy-curl.html %}

지리 거리 정렬

문서를 _geo_distance 기준으로 정렬할 수 있어요. 다음 파라미터가 지원돼요.

파라미터 설명
distance_type 거리를 계산하는 방법을 지정해요. 유효한 값은 arc와 plane이에요. plane 방식이 더 빠르지만 장거리나 극지방 근처에서는 정확도가 떨어져요. 기본값은 arc예요.
mode 지오포인트가 여러 개인 필드를 처리하는 방법을 지정해요. 기본적으로 오름차순 정렬일 때는 가장 짧은 거리, 내림차순 정렬일 때는 가장 긴 거리를 기준으로 문서가 정렬돼요. 유효한 값은 min, max, median, avg예요.
unit 정렬 값을 계산할 때 사용하는 단위를 지정해요. 기본값은 미터(m)예요.
ignore_unmapped 매핑되지 않은 필드를 처리하는 방법을 지정해요. 매핑되지 않은 필드를 무시하려면 ignore_unmapped를 true로 설정하세요. 기본값은 false예요(매핑되지 않은 필드를 만나면 오류가 발생해요).

_geo_distance 파라미터는 missing_values를 지원하지 않아요. 거리 계산에 사용하는 필드가 문서에 없으면 거리는 항상 infinity로 간주돼요. {: .note}

예를 들어 인덱스를 생성하고 point 필드를 geo_point로 매핑해 보세요:

PUT testindex1
{
  "mappings": {
    "properties": {
      "point": {
        "type": "geo_point"
      }
    }
  }
}

{% include copy-curl.html %}

지오포인트가 포함된 문서 두 개를 색인하세요:

PUT testindex1/_doc/1
{
  "point": [74.00, 40.71] 
}

{% include copy-curl.html %}

PUT testindex1/_doc/2
{
  "point": [73.77, -69.63] 
}

{% include copy-curl.html %}

모든 문서를 검색하고 제공된 지점으로부터의 거리를 기준으로 정렬해 보세요:

GET testindex1/_search
{
  "sort": [
    {
      "_geo_distance": {
        "point": [59, -54],
        "order": "asc",
        "unit": "km",
        "distance_type": "arc",
        "mode": "min",
        "ignore_unmapped": true
      }
    }
  ],
  "query": {
    "match_all": {}
  }
}

{% include copy-curl.html %}

응답에는 정렬된 문서들이 포함돼요:

{
  "took" : 864,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [
      {
        "_index" : "testindex1",
        "_id" : "2",
        "_score" : null,
        "_source" : {
          "point" : [
            73.77,
            -69.63
          ]
        },
        "sort" : [
          1891.2667493895767
        ]
      },
      {
        "_index" : "testindex1",
        "_id" : "1",
        "_score" : null,
        "_source" : {
          "point" : [
            74.0,
            40.71
          ]
        },
        "sort" : [
          10628.402240213345
        ]
      }
    ]
  }
}

좌표는 지오포인트 필드 타입이 지원하는 어떤 형식으로든 제공할 수 있어요. 모든 형식에 대한 설명은 geopoint 필드 타입 문서를 참고하세요. {: .note}

_geo_distance에 여러 지오포인트를 전달하려면 배열을 사용하세요:

GET testindex1/_search
{
  "sort": [
    {
      "_geo_distance": {
        "point": [[59, -54], [60, -53]],
        "order": "asc",
        "unit": "km",
        "distance_type": "arc",
        "mode": "min",
        "ignore_unmapped": true
      }
    }
  ],
  "query": {
    "match_all": {}
  }
}

{% include copy-curl.html %}

각 문서의 정렬 거리는 검색에 제공된 모든 지점부터 문서 안의 모든 지점까지의 거리를 최솟값, 최댓값, 또는 평균(mode에서 지정한 값)으로 계산한 값이에요.

성능 고려 사항

정렬에 사용되는 필드 값은 정렬을 위해 메모리에 로드돼요. 따라서 오버헤드를 최소화하려면 숫자 타입을 short, integer, float처럼 수용 가능한 가장 작은 타입으로 매핑하는 것을 권장해요. 문자열 타입은 정렬되는 필드가 분석되거나 토큰화되지 않도록 해야 해요.

_id 필드는 정렬 연산에서 사용이 제한돼요. 문서 ID로 정렬해야 한다면 ID 값을 doc_values가 활성화된 다른 필드에 복제하는 방법을 고려해 보세요. _id 필드 제한에 대한 자세한 내용은 ID 필드 타입을 참고하세요. {: .note}

더 알아보기 (Learn more)