MongoDB 101
그 동안 다소 소홀했던 MongoDB에 대해 좀 더 공부해보기 위해 정리해본다.
MongoDB Getting Started
MongoDB는 Database -> Collection -> Document로 이루어져있다. RDB로 따지면 Schema -> Table -> Row 정도로 해석될 것 같다. JSON과 유사한 형식으로 저장되기 때문에 데이터 처리가 굉장히 용이하다는 장점을 가지고 있다.
use examples
db.inventory.insertMany([
{ item: "journal", qty: 25, status: "A", size: { h: 14, w: 21, uom: "cm" }, tags: [ "blank", "red" ] },
{ item: "notebook", qty: 50, status: "A", size: { h: 8.5, w: 11, uom: "in" }, tags: [ "red", "blank" ] },
{ item: "paper", qty: 10, status: "D", size: { h: 8.5, w: 11, uom: "in" }, tags: [ "red", "blank", "plain" ] },
{ item: "planner", qty: 0, status: "D", size: { h: 22.85, w: 30, uom: "cm" }, tags: [ "blank", "red" ] },
{ item: "postcard", qty: 45, status: "A", size: { h: 10, w: 15.25, uom: "cm" }, tags: [ "blue" ] }
]);
db.inventory.find({})
db.inventory.find( { tags: "red" } )
db.inventory.find( { size: { h: 14, w: 21, uom: "cm" } } )
위와 같이 현재 지정된 db examples
에 collection inventory
를 생성한 뒤, 아래 쿼리에 해당하는 documents 들을 저장한다.
내부에 nesting된 항목들에 대해서도 검색이 가능하며, 아래와 같이 array 내부에 있는 특정 요소에 대해서 역시 검색이 가능하다.
db.inventory.find( { tags: "red" } )
DB의 결과값 중 반환할 값만을 지정하여 반환할 수 있다. 아래와 같이, find
명령어 뒤에 조건을 넣음으로써 MySQL의 SELECT item, status FROM examples
와 같은 효과를 도출해낼 수 있다.
db.inventory.find( { }, { item: 1, status: 1 } );
여기서 _id
필드가 자동으로 반환이 되는데, 별도로 해당 필드에 0 숫자를 할당하면 반환하지 않을 수 있다.
Databases & Collections
Database나 Collection은 존재하지 않는 경우 MongoDB가 첫 번째 데이터 저장시에 생성을 같이 한다.
use myNewDB
db.myNewCollection1.insertOne( { x: 1 } )
이 상황에서는, insertOne()
호출시에 동시에 존재하지 않는 myNewDB
와 myNewCollection1
을 생성한다.
db.createCollection()
와 같은 메서드를 사용하면 명시적으로 만들 수 있기는 하다
Document Validation
일반적으로 Collection은 Document들이 동일한 스키마를 가지는 것을 요구하지 않는다. 다만 Document 검증 규칙을 적용할 수 있다.
Documents
MongoDB는 BSON Document 형식으로 데이터를 저장한다. JSON과 비슷하긴 한데, 수용하는 데이터 타입이 더 많다.
예를들어, 기본으로 추가되는 _id
필드의 경우 ObjectId
데이터 타입을 갖는다. ObjectId
는 12 bytes로 이루어진 유니크한 값이다.
구성은 (4-bytes unix timestamp) + (5-bytes random value) + (3-bytes counter, 랜덤값)으로 이루어져있다.
이외에도 Double
, Binary data
, Regular expression
등 기존 JSON에서 사용할 수 없었던 데이터 타입들을 사용할 수 있다.
MongoDB CRUD
Create Operation
collection
에 새로운 document
를 추가하는 작업을 진행한다. MongoDB에서 insert 명령은 하나의 collection
을 대상으로 한다. MongoDB에서 모든 쓰기 작업들은 하나의 Document에 대해 원자성을 유지한다. (All write operations in MongoDB are atomic on the level of a single document.)
db.collection.insertOne();
db.collection.insertMany();
insert*()
명령을 사용하여 값을 생성한 경우 _id
필드 값이 추가가 된다.
Read Operation
읽기 작업은 collection
으로부터 documents
를 가져오는 작업을 진행한다. find()
와 같은 항목을 사용할 수 있다.
db.users.find(
{ age: { $gt: 18 }}, # 검색 쿼리
{ name: 1, address: 1} # 가져올 칼럼
)
NodeJS의 경우, find()
의 결과물은 Cursor
객체를 반환한다.
조건부로 가져올 경우, 해당 조건을 쿼리 연산자를 이용하여 조건을 지정할 수 있다.
const cursor = db.collection('inventory').find({
status: { $in: ['A', 'D'] }
});
Query Operators
쿼리 연산자에는 다음과 같은 목록이 있다.
Query and Projection Operators
$eq
: equals to (반대는$ne
)$gt
: greater than (반대는$lt
)$gte
: greater than or equal to-
$in
: 값이 행렬 안에 있는지 확인 (반대는$nin
) $and
/$not
/$nor
/$or
: 논리 연산자$exists
: 필드의 존재 유무 (boolean 값을 넘기면된다). 아래 코드는,qty
필드가 존재하며, 5와 15가 아닌 값을 반환한다.
db.inventory.find({ qty: { $exists: true, $nin: [5, 15] } });
-
$type
: BSON type을 넘겨서 매치되는 항목을 반환한다. -
$expr
/$regex
/$text
/$where
: 정규식과 같은 방법으로 검색되는 경우 -
$jsonSchema
: JSON Schema에 매칭되는 도큐먼트를 반환한다. 해당 값을 통해 스키마 검증을 진행할 수 있다. 내용이 길다보니 도큐먼트 참조 필요.
JSON Schema
JSON Schema는 JSON data의 구조를 정의하는 JSON 포멧으로 “application/schema+json” 미디어타입에 해당한다. JSON Schema는 검증, 도큐멘테이션, 하이퍼링크 네비게이션, 그리고 JSON data에 대하여 상호작용을 위해 의도되었다.
검증을 위한 키워드는 아래 링크에서 확인이 가능하다
MongoDB JSON Schema available keywords
{ $jsonSchema: <JSON Schema Object> }
{
$jsonSchema: {
required: [ "name", "major", "gpa", "address" ],
properties: {
name: {
bsonType: "string",
description: "must be a string and is required"
},
year: {
bsonType: "int",
minimum: 2017,
maximum: 3017,
description: "must be an integer in between [ 2017, 3017 ] and is required"
},
major: {
enum: [ "Math", "English", "Computer Science", "History", null ],
description: "can only use one of the enum values and is required"
},
gpa: {
bsonType: [ "double" ],
description: "must be a double if value exists"
},
address: {
bsonType: "object",
required: [ "city" ],
properties: {
street: {
bsonType: "string",
description: "must be a string if the field exists"
},
city: {
bsonType: "string",
description: "must be a string and is required"
}
}
}
}
}
}
위의 코드에서 볼 수 있듯이, bsonType
항목을 지정해주면, 해당 값만 받게 된다. “string”으로 설정되면 문자열만 들어가게 되고, [ “double” ]과 같은 경우는 Typescript union type과 유사하다고 생각하면 될듯하다. 이 안에 “int”도 넣게 되면 동시에 “int” 타입도 “받을 수는 있다”. 다만 이런식으로 할거면 굳이 스키마 만들 필요 없으니 그냥 하나만 쓰도록 하자.
또한, 스키마 객체 안에 bsonType: "object"
가 있으면 또 한 번 네스팅을 할 수 있다.
일반적으로 type
키워드와 bsonType
키워드는 거의 동일한데, MongoDB판은 “integer” 타입을 지원하지 않기 때문에, bsonType: "int"
혹은 bsonType: "long"
을 사용해줘야 한다.
- 그러면 NodeJS driver에서는 어떻게 사용해야할까?: 간단하다. MongoDB 패키지에 있는
Long
이나Int
를 가져다가 사용하면 된다.
import { Long } from "mongodb";
let seq = this.db.collection("seq");
seq.insertOne({
value: Long.fromInt(1);
}, () => {});
Update Operation
기존에 collection
에 존재하는 document
(들)을 수정한다.
db.collection.updateOne();
db.collection.updateMany();
db.collection.replaceOne();
Create 작업과 동일하게 MongoDB에 있는 모든 쓰기 작업들은 single document
단계에서 원자성을 갖는다.
db.users.updateMany(
{ age: { $lt: 18 }}, # update filter
{ $set: { status: "reject" }} # update action
)
여기에 있는 $set
과 같은 연산자들은 필드가 존재하지 않는 경우 필드 생성을 병행한다.
{
"_id": 100,
"sku": "abc123",
"quantity": 250,
"instock": true,
"reorder": false,
"details": { "model": "14Q2", "make": "xyz" },
"tags": ["apparel", "clothing"],
"ratings": [{ "by": "ijk", "rating": 4 }]
}
위와 같은 도큐먼트가 있다고 가정하였을 때, 아래와 같이 최상단에 존재하는 필드들의 값을 수정한다고 가정해보자.
db.products.update(
{ _id: 100 },
{
$set: {
quantity: 500,
details: { model: '14Q3', make: 'xyz' },
tags: ['coats', 'outerwear', 'clothing']
}
}
)
이렇게 될 경우, quantity
와 details
, tags
필드의 값들이 모두 새 값으로 변경된다.
도큐먼트 내에 내재되어 있는 도큐먼트나 array를 특정하여 수정하고자 하는 경우, dot notation
을 사용한다.
예를들어, 위의 details
도큐먼트 내의 make
필드를 수정하고자 하는 경우, 아래와 같이 점으로 구분된 문자열 값을 전달한다.
db.products.update(
{ _id: 100 },
{ $set: { "details.make": "zzz" } }
)
이와 더불어 array 내의 특정 요소 값을 수정하고자 하는 경우, 번호를 지정해줄 수 있다.
db.products.update(
{ _id: 100 },
{ $set:
{
"tags.1": "rain gear",
"ratings.0.rating": 2
}
}
)
Delete Operation
말 그대로 document
를 collection
으로부터 제거한다.
db.collection.deleteOne();
db.collection.deleteMany();
db.users.deleteMany(
{ status: "reject" } # delete filter
)
Aggregation (집합)
집합 연산은 데이터 레코드를 제작하고, 계산된 결과를 반환한다. MySQL의 GROUP_BY
와 같은 역할을 해준다고 볼 수도 있을듯하다.
Aggregation Pipeline
이 방법은 도큐먼트들로 하여금 다단계의 파이프라인을 통해 종합한 결과로 도출해내도록 한다.
db.orders.aggregate([
{ $match: { status: "A" } },
{ $group: { _id: "$cust_id", total: { $sum: "$amount" } } }
])
$match
키워드는status
필드의 값이 “A”인 도큐먼트들을 색인한 다음, 다음 번 스테이지로 전달한다.$group
키워드는 1번의 결과값 중에서_id
값을$cust_id
로 묶고 (여기서 해당 id 앞에 달러 표시가 붙은 것을 유의하자), 이에 해당하는 묶인 total을$sum
으로 묶어준다.
Aggregation pipeline operators
Map-Reduce
향후에 공부하는 것으로
Single Purpose Aggregation Operations
db.collection.estimatedDocumentCount()
db.collection.count()
db.collection.distinct()
이 연산자들은 한 개의 컬렉션으로부터 도큐먼트들을 집합하는 역할을 한다. 일반적으로 하나의 목적을 가지고 사용된다.