연산자
여기서는 쿼리에서 사용 가능한 연산자를 설명합니다. 사용 가능한 연산자는 다음과 같습니다.
$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 Cognica!",
"attributes": {
"author": "finn",
"publisher": {
"name": "Cognica, Inc."
}
},
}이 데이터를 다음과 같이 쿼리하면
"$unnest": "attributes"다음과 같이 출력됩니다.
[
"title": "Hello Cognica!",
"attributes.author": "finn",
"attributes.publisher": {
"name": "Cognica, Inc."
}
]$unnest는 위의 예시처럼 첫번째 깊이의 해당되는 필드만 변환하기 때문에 publisher처럼 두단계 깊이 이상의 필드를 확장하고자 한다면 $recursive_unnest를 사용할 수 있습니다.
"$recursive_unnest": "attributes"위 쿼리는 다음과 같이 출력됩니다.
[
"title": "Hello Cognica!",
"attributes.author": "finn",
"attributes.publisher.name": "Cognica, 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 cognica 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 Cognica",
"highlight": {
"enabled": True,
"pre_tag": "<b>",
"post_tag": "</b>"
}
}
}script_score
script_score는 Cognica에 내장된 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": ["cognica", "코그니카"]와 같이 정의하여 특정 키워드로 교체할 수도 있습니다.
{
"$search": {
"query": "Hello, Cognica!",
"custom_synonyms": [
{
"hello": ["hello", "hi", "안녕"],
"cognica": ["cognica", "cognica", "코그니카", "코그니카"]
},
{
// "cognica"는 "cognica"와 "코그니카"로 교체됩니다.
"cognica": ["cognica", "코그니카"]
}
]
}
}만약 특정 필드별로 유의어를 설정하고자 한다면 다음과 같이 정의할 수 있습니다.
{
"$search": {
"query": "Hello, Cognica!",
"custom_synonyms": {
"title": [
{
"hello": ["hello", "hi", "안녕"],
"cognica": ["cognica", "cognica", "코그니카", "코그니카"]
},
{
"cognica": ["cognica", "코그니카"]
}
],
"content": [
{
"hello": ["hello", "hi", "안녕"],
"cognica": ["cognica", "cognica", "코그니카", "코그니카"]
},
{
"cognica": ["cognica", "코그니카"]
}
]
}
}
}스크립트: $script
스크립트는 Cognica에 내장된 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입니다.