연산자
여기서는 쿼리에서 사용 가능한 연산자를 설명합니다. 사용 가능한 연산자는 다음과 같습니다.
$lt
: 작은$le
: 작거나 같은$gt
: 큰$ge
: 크거나 같은$eq
: 같은$ne
: 같지 않은$in
: 포함하는$not_in
: 포함되지 않는$exists
: 존재하는$match
: 와일드카드 매칭$regex
: 정규식 매칭$and
: AND$or
: OR$nor
: NOR, Not OR$not
: NOT$project
: 열을 선택$limit
: 출력 항목의 수를 제한$skip
: 시작 위치를 정의$sort
: 정렬$unnest, $recursive_unnest
: 계층적인 데이터를 1차원으로 변환$literal
: 특정한 값을 칼럼에 추가$group
: 통합$join
: 조인$union
: union 조인$hint
: 색인을 위한 힌트$search
: 전문 검색$script
: 스크립트
비교: $lt
, $le
, $gt
, $ge
, $eq
, $ne
비교 연산자는 항목간 비교하거나 같거나 다른지를 판단합니다.
다음 예시는 key1
항목이 10보다 작은지를 검사합니다.
"key1": {
"$lt": 10
}
포함: $in
, $not_in
$in
과 $not_in
은 필드에 특정한 값들이 포함되는지 포함되지 않는지를 검사하는 연산자입니다.
다음 예시는 key1
항목에 1, 2, 5 중에 하나가 포함되어 있는지 검사합니다.
"key1": {
"$in": [1, 2, 5]
}
존재 여부: $exists
필드에 값이 존재하는지 확인합니다.
다음 예시는 key1
에 값이 존재 여부를 확인합니다.
"key1": {
"$exists": True
}
부분 일치: $match
와일드카드(wildcard)를 사용하여 일치 여부를 확인합니다.
다음 예시는 key1
에 search
로 시작하는 문자열이 있는지 확인합니다. $options
에 ignore-case
를 전달하면 대소문자 구별없이 확인할 수 있습니다.
"key1": {
"$match": "search*",
"$options": "ignore-case"
}
정규식 일치: $regex
정규식 (opens in a new tab)을 사용하여 일치 여부를 확인합니다.
다음은 key1
에 숫자가 포함된 문자열을 찾는 예시입니다.
"key1": {
"$regex": ".+\d+",
}
Boolean: $and
, $or
, $nor
, $not
불리언 연산자를 통해 조건을 조합하여 검색할 수 있습니다.
아래는 id
가 1, 2, 5 중에 하나이면서 title
이 search
가 일치하지 않는 데이터를 찾는 쿼리의 예시입니다.
"$and": [
{
"id": {
"$in": [1, 2, 5]
}
},
{
"$not": [
{
"title": {
"$match": "search*"
}
}
]
}
]
Project: $project
$project
는 필드를 선택하는 연산자입니다.
다음 예시는 key1
, key2
를 선택하는 예시입니다.
"$project": ["key1", "key2"]
필요에 따라 다음과 같이 별명을 지정할 수 있습니다. 아래의 예시에서 key1
은 key3
로 출력됩니다.
"$project": [
{"key1": "key3"},
"key2"
]
Nested field의 경우에는 다음과 같이 접근할 수 있습니다. 이 예시는 attribute
의 계층 구조를 유지하면서 attribute
의 내부 필드만 선택하게 됩니다. 따라서 출력되는 칼럼은 attribute
하나입니다.
"$project": ["attribute.author", "attribute.price"]
하지만 다음과 같이 ^
연산자를 추가하면 계층 구조가 해제되면서 attribute.author
와 attribute.price
두 개의 칼럼으로 출력되게 됩니다.
"$project": ["^attribute.author", "^attribute.price"]
갯수 제한: $limit
$limit
는 연산된 결과값에서 선택할 행의 수를 제한하는 연산자입니다.
다음 예시는 10개의 행을 선택하는 예시입니다.
"$limit": 10
시작 위치: $skip
$skip
는 연산된 결과값에서 시작할 위치를 제한하는 연산자입니다.
다음 예시는 5번째 행부터 선택하는 예시입니다.
"$skip": 5
$skip
과 $limit
를 사용하여 pagination을 구현할 수 있습니다.
정렬: $sort
$sort
는 연산된 결과값을 정렬하는 연산자입니다.
다음 예시는 key1
은 오름차순, key2
를 내림차순으로 결과값을 정렬하는 예시입니다. 나열된 순서를 우선하여 정렬됩니다.
"$sort": [
"key1",
{"key2": "desc"},
]
Flatten: $unnest
, $recursive_unnest
$unnest
는 지정된 칼럼의 데이터를 1차원 배열로 변환하는 연산자입니다.
예를 들어 다음과 같이 데이터가 있고
{
"title": "Hello Aeca!",
"attributes": {
"author": "finn",
"publisher": {
"name": "Aeca, Inc."
}
},
}
이 데이터를 다음과 같이 쿼리하면
"$unnest": "attributes"
다음과 같이 출력됩니다.
[
"title": "Hello Aeca!",
"attributes.author": "finn",
"attributes.publisher": {
"name": "Aeca, Inc."
}
]
$unnest
는 위의 예시처럼 첫번째 깊이의 해당되는 필드만 변환하기 때문에 publisher
처럼 두단계 깊이 이상의 필드를 확장하고자 한다면 $recursive_unnest
를 사용할 수 있습니다.
"$recursive_unnest": "attributes"
위 쿼리는 다음과 같이 출력됩니다.
[
"title": "Hello Aeca!",
"attributes.author": "finn",
"attributes.publisher.name": "Aeca, Inc."
]
값 지정: $literal
특정한 값을 칼럼에 추가합니다.
다음과 같은 데이터가 있을 때
data = [
{"doc_id": 1, "data": 1},
{"doc_id": 2, "data": 2},
{"doc_id": 3, "data": 3},
]
다음과 같이 실행하면
query = {
"$literal": {
"text": "test"
}
}
다음과 같은 결과를 얻을 수 있습니다.
doc_id data text
0 1 1 test
1 2 2 test
2 3 3 test
통합: $group
$group
쿼리된 결과값을 통합하여 재가공하는 연산자입니다.
여기서는 검색에서 사용하는 예시를 다시 사용하겠습니다. 우리는 6기통인 모델의 연도별 모델 수와 모델명, 해당연도 마력의 최대값을 구해보고자 합니다. 이 쿼리는 다음과 같이 작성할 수 있습니다.
query = [
{
"cylinders": 6,
},
{
"$group": {
"_id": "model_year",
"first_name": {
"$first": "name"
},
"horsepower_max": {
"$max": "horsepower"
},
"count": {
"$sum": 1
}
}
}
]
df = doc_db.find(collection_name, query)
print(df)
하나씩 살펴보면, $group
은 병합의 기준이 되는 칼럼인 _id
를 필수적으로 입력 받습니다. 이 경우 연도별 병합이기 때문에 model_year
를 선택했습니다.
"_id": "model_year"
다음 병합된 그룹에서 첫번째 모델의 name
을 가져옵니다. 여기서 first_name
은 $group
으로 만들어질 결과물 칼럼의 이름입니다.
"first_name": {
"$first": "name"
}
마지막 항목인 병합 결과물에 count
라는 칼럼을 신규로 만들고 "$sum": 1
하여 model_year
로 병합된 아이템들의 갯수를 계산합니다.
"count": {
"$sum": 1
}
실행 결과는 다음과 같습니다.
_id count first_name horsepower_max
0 70 4 plymouth duster 97.0
1 71 8 amc gremlin 110.0
2 73 8 plymouth valiant 122.0
3 74 7 plymouth duster 110.0
4 75 12 plymouth valiant custom 110.0
5 76 10 plymouth valiant 120.0
6 77 5 chevrolet concours 110.0
7 78 12 pontiac phoenix lj 165.0
8 79 6 pontiac lemans v6 115.0
9 80 2 dodge aspen 132.0
10 81 7 chevrolet citation 120.0
11 82 3 buick century limited 112.0
$group
의 연산자
병합에 사용할 수 있는 연산자는 다음과 같습니다.
$count
: 항목의 수$distinct_count
: 중복이 제외된 항목수$sum
: 합$mean
: 평균$variance
: 분산$stddev
: 표준편차$min
: 최대값$max
: 최소값$minmax
: 최대, 최소값$first
: 첫번째 항목$last
: 마지막 항목$array.push
: list로 변환$set.add
: set으로 변환$sketch.hll
: Sketchs의 HyperLogLog (opens in a new tab)를 사용하여 확률적으로 유일한 값의 수를 추정$sketch.kll
: Sketchs의 KllSketch (opens in a new tab)를 사용하여 분위수(quantile)를 계산$sketch.most_freq
: Sketchs의 Frequent Items (opens in a new tab)를 사용하여 고빈도 항목을 추정
Join: $join
, $union
두개의 컬렉션을 병합합니다.
test.join.left
와 test.join.right
를 생성하고 예제 데이터를 삽입하도록 하겠습니다.
from aeca import DocumentDB
doc_db = DocumentDB(channel)
collection_names = ["test.join.left", "test.join.right"]
indexes = [
{
"index_type": "kPrimaryKey",
"fields": ["doc_id"]
}
]
data = {
"test.join.left": [
{"doc_id": 1, "left": 1},
{"doc_id": 2, "left": 2},
{"doc_id": 3, "left": 3},
],
"test.join.right": [
{"doc_id": 1, "right": 1},
{"doc_id": 2, "right": 2},
{"doc_id": 5, "right": 5},
]
}
for name in collection_names:
doc_db.create_collection(name, indexes=indexes)
doc_db.insert(name, data[name])
$join
다음은 test.join.left
와 test.join.right
를 inner
join 하는 예시입니다.
query = {
"$join": {
"type": "inner",
"collection": "test.join.left",
"query": {},
"on": ["doc_id"]
}
}
df = doc_db.find("test.join.right", query)
print(df)
$join
에서 사용 가능한 옵션은 다음과 같습니다.
type
: 조인 방법inner
: 내부 조인, 기본값outer
: 외부 조인left
: left 조인right
: right 조인hash_inner
: 해시 내부 조인
collection
: 조인할 컬렉션query
: 조인할 컬렉션의 쿼리, 생략 가능on
: 조인할 키
위 예시의 실행 결과는 다음과 같습니다.
doc_id left right
0 1 1 1
1 2 2 2
$union
다음은 test.join.left
와 test.join.right
를 $union
하는 예시입니다.
query = {
"$union": {
"type": "all",
"collection": "test.join.left",
"query": {},
"on": ["doc_id"]
}
}
df = doc_db.find("test.join.right", query)
print(df)
$join
에서 사용 가능한 옵션은 다음과 같습니다.
- type: 조인 방법
- all: 전체
- distinct: 중복 제거
- collection: 조인할 컬렉션
- query: 조인할 컬렉션의 쿼리, 생략 가능
- on: 조인할 키
위 예시의 실행 결과는 다음과 같습니다.
doc_id left right
0 1 <NA> 1
1 2 <NA> 2
2 5 <NA> 5
3 1 1 <NA>
4 2 2 <NA>
5 3 3 <NA>
색인 힌트: $hint
색인을 위한 힌트를 제공합니다. 컬렉션에는 하나 이상의 색인이 저장될 수 있고 입력된 쿼리에 대해 쿼리 플래너는 이 색인들을 조합하여 최적의 색인을 선택합니다. 그러나 $hint
를 통해 색인이 선택되면 해당 색인을 우선적으로 사용합니다.
{
"$search": {
"query": "cat"
},
"$hint": "sk_fts"
}
전문 검색: $search
$search
는 전문 검색하는 연산자입니다.
다음은 검색 예시입니다.
"$search": {
"query": "content:(hello world)",
"fields": ["content"]
}
사용 가능한 옵션은 다음과 같습니다.
항목 | 설명 | 타입 | 기본값 |
---|---|---|---|
query | 검색 쿼리 쿼리 작성 방법은 전문 검색를 참고 | str | |
fields | 검색 대상 필드 | list[str] | 색인된 전체 필드 |
skip | 시작 위치 | int | |
limit | 후보수 | int | |
timeout | 만료 시간 | int | |
min_score | 최소 점수 | float | |
highlight | 하이라이트 | bool | dict | |
default_operator | + : Must# : Filter | str | None: Should |
script_score | 스크립트 | dict | |
custom_stop_words | 불용어 | list[str] | |
custom_synonyms | 유의어 | list[dict] | dict |
highlight
highlight
가 활성화되면 검색 결과물에 _highlights
칼럼이 추가되고 각 필드에 <em>
태그로 강조된 문장이 반환됩니다.
만약 이 태그를 변경하고자 한다면 다음과 같이 설정할 수 있습니다.
{
"$search": {
"query": "Hello Aeca",
"highlight": {
"enabled": True,
"pre_tag": "<b>",
"post_tag": "</b>"
}
}
}
script_score
script_score
는 Aeca에 내장된 Python 인터프리터를 이용하여 $search
를 통해 계산된 문서의 점수를 재계산할 수 있습니다. 여기서 내부 변수는 다음과 같이 미리 할당되어 있습니다.
_doc
: 필드 전체 값을 가지고 있는 변수_score
:$search
를 통해 계산된 기존 점수
Python으로 작성되는 아래의 코드 script_score.script.source
는 query
를 통해 검색된 결과물의 기존 점수인 _score
에 _doc["Released_Year"]
에서 연도값만 추출하여 더하는 코드로 구성되어 있습니다. 결과적으로 최신순 정렬와 유사한 동작을 하게 됩니다.
"$search": {
"script_score": {
"query": "x-men",
"script": {
"source": "_score + datetime.datetime.strptime(_doc['Released_Year'], '%Y-%m-%dT%H:%M:%S').year"
},
}
}
스크립트에서 사용 가능한 라이브러리는 Python의 builtin 라이브러리와 chrono
, datetime
, math
, numpy
입니다.
custom_synonyms
custom_synonyms
를 사용하여 검색시 유의어를 사용할 수 있습니다. 예를 들어 "애플"을 검색했을 때 "Apple"도 함께 검색 결과를 확인하고 싶다면 "애플"의 유의어로 "Apple"을 추가하는 방법을 생각할 수 있습니다.
다음은 사용 예시입니다. 여기서 hello
는 ["hello", "hi", "안녕"]
로 확장됩니다. 혹은 "cognica": ["aeca", "에이카"]
와 같이 정의하여 특정 키워드로 교체할 수도 있습니다.
{
"$search": {
"query": "Hello, Aeca!",
"custom_synonyms": [
{
"hello": ["hello", "hi", "안녕"],
"aeca": ["aeca", "cognica", "에이카", "코그니카"]
},
{
// "cognica"는 "aeca"와 "에이카"로 교체됩니다.
"cognica": ["aeca", "에이카"]
}
]
}
}
만약 특정 필드별로 유의어를 설정하고자 한다면 다음과 같이 정의할 수 있습니다.
{
"$search": {
"query": "Hello, Aeca!",
"custom_synonyms": {
"title": [
{
"hello": ["hello", "hi", "안녕"],
"aeca": ["aeca", "cognica", "에이카", "코그니카"]
},
{
"cognica": ["aeca", "에이카"]
}
],
"content": [
{
"hello": ["hello", "hi", "안녕"],
"aeca": ["aeca", "cognica", "에이카", "코그니카"]
},
{
"cognica": ["aeca", "에이카"]
}
]
}
}
}
스크립트: $script
스크립트는 Aeca에 내장된 Python 인터프리터를 이용하여 필터 조건을 계산할 수 있습니다. 여기서 내부 변수는 다음과 같이 미리 할당되어 있습니다.
_doc
: 필드 전체 값을 가지고 있는 변수_value
: 선택된 필드의 값
만약 다음과 같이 데이터를 입력하고
data = [
{"doc_id": 1, "data": ["A", "C"]},
{"doc_id": 2, "data": ["A", "B", "D"]},
{"doc_id": 3, "data": ["A"]},
]
doc_db.insert(name, data)
다음과 같이 쿼리하면 길이가 data
의 배열의 길이가 2인 doc_id
가 1인 문서만 반환됩니다.
여기에 작성되는 스크립트는 Python으로 작성되며 내부 변수인 _doc
는 행 전체의 데이터를 가지고 있으며 dict 타입처럼 필드의 값에 접근할 수 있습니다. 아래의 경우 data
필드가 선택되어 있으므로 이 경우 내부 변수인 _value
는 _doc["data"]
와 동일한 값을 가지고 있습니다. 따라서 아래의 쿼리는 len(_value) == 2
와 동일합니다.
{
"data": {
"$script": 'len(_doc["data"]) == 2'
}
}
스크립트에서 사용 가능한 라이브러리는 Python의 builtin 라이브러리와 chrono
, datetime
, math
, numpy
입니다.