MongoDB
MongoDB 학습 및 정리
1. 몽고DB 시작
몽고DB는 강력하고 유연하며 확장성 높은 범용 데이터베이스다. 보조 인덱스(secondary index), 범위 쿼리(range query), 정렬(sorting), 집계(aggregation), 공간 정보 인덱스(geospatial index) 등을 확장 기능과 결합했다.
1.1. 손쉬운 사용
몽고DB는 관계형 데이터베이스가 아니라 도큐먼트 지향 데이터베이스다. 관계형 모델을 사용하지 않는 주된 이유는 분산 확장(scale-out)을 쉽게 하기 위함이지만 다른 이점도 있다.
도큐먼트 지향 데이터베이스에서는 도큐먼트를 사용하여 복잡한 계층 관계를 하나의 레코드로 표현할 수 있다. 이는 객체 지향 언어 관점에 매우 적합하다.
또한 몽고DB에서는 도큐먼트의 키와 값을 미리 정의하지 않는다. 따라서 고정된 스키마가 없으므로 쉽게 필드를 추가하거나 제거할 수 있다.
1.2. 확장 가능한 설계
분산 확장은 저장 공간을 늘리거나 처리량을 높이고 서버를 구매해서 클러스터에 추가하는 방법이다. 따라서 경제적이고 확장이 용이하다. 하지만 하나의 장비만 관리할 때보다 관리가 어렵다.
몽고DB는 분산 확장을 염두에 두고 설계됐다. 도큐먼트를 자동으로 재분배하고 사용자 요청을 올바르게 장비에 라우팅함으로써 클러스터 내 데이터 양과 부하를 조절할 수 있다.
1.3. 다양한 기능
- 인덱싱
- 일반적인 보조 인덱스를 지원하며 고유(unique), 복합(compound), 공간 정보, 전문(full-text) 인덱싱 기능도 제공한다. 중첩된 도큐먼트(nested document) 및 배열과 같은 계층 구조의 보조 인덱스도 지원한다.
- 집계
- 데이터 처리 파이프라인 개념을 기반으로 한 집계 프레임워크를 제공한다.
- 특수한 컬렉션 유형
- 최신 데이터를 유지하고자 세션이나 고정 크기 컬렉션과 같이 특정 시간에 만료해야 하는 데이터에 대해 유효 시간(TTL, Time-To-Live) 컬렉션을 지원한다. 또한 기준 필터(criteria filter)와 일치하는 도큐먼트에 한정된 부분 인덱스를 지원함으로써 효율성을 높이고 필요한 저장 공간을 줄인다.
- 파일 스토리지
- 큰 파일과 파일 메타데이터를 편리하게 저장하는 프로토콜을 지원한다.
몽고DB는 3.2에 도입된 $lookup
집계 연산자를 사용하면 매우 제한된 방식으로 조인하는 것이 가능하다. 3.6 버전에서는 관련 없는 서브쿼리 뿐만 아니라 여러 조인 조건으로 보다 복잡한 조인도 할 수 있다.
1.4. 고성능
몽고DB에서는 동시성(concurrency)과 처리량을 극대화하기 위해 와이어드타이거(WiredTiger) 스토리지 엔진에 기회적 락(oppertunistic locking) 을 사용했다. 따라서 캐시처럼 제한된 용량의 램으로 쿼리에 알맞은 인덱스를 자동으로 선택 할 수 있다.
1.5. 몽고DB의 철학
몽고DB 프로젝트의 주 관심사는 확장성이 높으며 유연하고 빠른 데이터 스토리지를 만드는 것이다.
2. 몽고DB 기본
- 몽고DB 데이터의 기본 단위는 도큐먼트 이며, RDB의 행과 유사하다.
- 컬렉션 은 동적 스키마가 있는 테이블과 같다.
- 몽고DB의 단일 인스턴스는 자체적인 컬렉션을 갖는 여러 개의 독립적인 데이터베이스 를 호스팅한다.
- 모든 도큐먼트는 컬렉션 내에서 고유한 특수키인
"_id"
를 갖는다. - 몽고DB는 몽고 쉘 이라는 도구와 함께 배포된다. 몽고 셸은 인스턴스를 관리하고 쿼리 언어로 데이터를 조작하기 위한 내장 지원을 제공한다. 또한 사용자가 자신의 스크립트를 만들고 로드할 수 있다.
2.1. 도큐먼트
몽고DB의 핵심은 정렬된 키와 연결된 값의 집합으로 이뤄진 도큐먼트다.
1
{"greeting" : "Hello, world!"}
대부분의 도큐먼트는 이보다 복잡한 다중 키/값 쌍을 가진다.
도큐먼트의 키는 문자열이다. 다음 예외 몇 가지를 제외하면 어떤 UTF-8 문자든 쓸 수 있다.
- 키는
\0(null 문자)
을 포함하지 않는다.\0
은 키의 끝을 나타내는 데 사용된다. .
과$
문자들은 예약어이며 부적절하게 사용하면 경고가 발생한다.
몽고DB는 데이터형과 대소문자를 구별한다. 또한, 키가 중복될 수 없다.
2.2. 컬렉션
컬렉션은 도큐먼트의 모음이다. RDB의 테이블에 대응된다고 볼 수 있다.
2.2.1. 동적 스키마
컬렉션은 동적 스키마를 가진다. 하나의 컬렉션 내 도큐먼트들이 모두 다른 구조를 가질 수 있다는 의미다. 예를 들어 다음 도큐먼트들을 하나의 컬렉션에 저장할 수 있다.
1
2
{"greeting" : "Hello, world!", "views": 3}
{"signoff" : "Good night, and good luck"}
도큐먼트들의 키, 키의 개수, 데이터형의 값은 모두 다르다. 도큐먼트에 별도의 스키마가 필요 없지만 하나 이상의 컬렉션이 필요한 이유는 다음과 같다.
- 같은 컬렉션에 다른 종류의 도큐먼트를 저장하면 번거로운 일이 생길 수도 있다.
- 컬렉션별로 목록을 뽑으면 한 컬렉션 내 특정 데이터형별로 쿼리해서 목록을 뽑을 때보다 훨씬 빠르다.
- 같은 종류의 데이터를 하나의 컬렉션에 모아두면 데이터 지역성에도 좋다.
- 인덱스를 만들면 도큐먼트는 특정 구조를 가져야 한다.
애플리케이션 스키마는 기본적으로 필요하지는 않지만 정의하면 좋다. 몽고DB의 도큐먼트 유효성 검사 기능(document validation functionality)과 객체-도큐먼트 매핑 라이브러리(object-document mapping library)를 이용하여 사용 가능하다.
2.2.2. 네이밍
컬렉션은 이름으로 식별된다. 이는 몇 가지 제약 조건이 있다.
- 빈 문자열(““)은 유효한 이름이 아니다.
\0(null 문자)
은 컬렉션명의 끝을 나타내는 문자이므로 사용할 수 없다.system.
으로 시작하는 컬렉션명은 예약어이므로 사용할 수 없다.- 사용자가 만든 컬렉션은 이름에 예약어인
$
를 포함할 수 없다.
서브컬렉션
서브컬렉션의 네임스페이스에 .(마침표)
문자를 사용해 컬렉션을 체계화한다. (ex. blog.posts
와 blog.authors
)
서브컬렉션은 특별한 속성은 없지만 여러 몽고DB 툴에서 지원하므로 유용하다.
- 큰 파일을 저장하는 프로토콜인 GridFS는 콘텐츠 데이터와 별도로 메타데이터를 저장하는 데 서브 컬렉션을 사용한다.
- 대부분의 드라이버는 특정 컬렉션의 서브컬렉션에 접근하는 몇 가지 편리한 문법을 제공한다.
2.3. 데이터베이스
몽고DB는 컬렉션에 도큐먼트를 그룹화할 뿐 아니라 데이터베이스에 컬렉션을 그룹 지어 놓는다. 몽고DB의 단일 인스턴스는 여러 데이터베이스를 호스팅할 수 있으며, 각 데이터베이스를 완전히 독립적으로 취급할 수 있다.
데이터베이스는 컬렉션과 마찬가지로 이름으로 식별된다. 이는 몇 가지 제약 조건이 있다.
- 빈 문자열(““)은 유효한 이름이 아니다.
/
,\
,.
,' '
,*
,<
,>
,:
,|
,?
,$
,\0
문자들을 포함할 수 없다.- 대소문자를 구별한다.
- 최대 64바이트다.
직접 접근할 수는 있지만 특별한 의미를 갖는 예약된 데이터베이스 이름도 있다.
- admin
- 인증과 권한 부여 역할을 한다.
- local
- 단일 서버에 대한 데이터를 저장한다.
- config
- 각 샤드의 정보를 저장한다.
컬렉션을 저장하는 데이터베이스의 이름을 컬렉션명 앞에 붙이면 네임스페이스를 얻는다. 예를 들어 cms 데이터베이스의 blog.posts 컬렉션을 사용한다면 컬렉션의 네임스페이스는 cms.blog.posts가 된다.
2.4. 몽고DB 시작
docker-compose를 활용하여 몽고DB를 실행하는 과정이다.
data
폴더 생성1
$ mkdir data
docker-compose.yml
파일 생성1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
version: "3.8" services: mongodb: image: mongo container_name: mongodb ports: - 27017:27017 volumes: - ./data:/data/db environment: - MONGO_INITDB_ROOT_USERNAME=root - MONGO_INITDB_ROOT_PASSWORD=root mongo-express: image: mongo-express container_name: mongo-express restart: always ports: - 8081:8081 environment: - ME_CONFIG_MONGODB_ADMINUSERNAME=root - ME_CONFIG_MONGODB_ADMINPASSWORD=root - ME_CONFIG_MONGODB_SERVER=mongodb
- docker-compose 실행
1
$ docker-compose up -d && docker-compose logs -f
localhost:8081
로 접속한 후에 로그인- id :
admin
- pw :
pass
- id :
- 셸로 접속
1 2 3
$ docker exec -it mongodb /bin/bash # 컨테이너로 접속 후 $ mongosh
2.5. 몽고DB 셸 소개
셸은 관리 기능이나, 실행 중인 인스턴스를 점검하거나 간단한 기능을 시험하는 데 매우 유용하다.
2.5.1. 셸 실행
mongosh을 통해 셸을 시작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ mongosh
Current Mongosh Log ID: 676e45c5d74e662877fc0420
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+2.3.4
Using MongoDB: 8.0.4
Using Mongosh: 2.3.4
For mongosh info see: https://www.mongodb.com/docs/mongodb-shell/
To help improve our products, anonymous usage data is collected and sent to MongoDB periodically (https://www.mongodb.com/legal/privacy-policy).
You can opt-out by running the disableTelemetry() command.
test>
셸은 완전한 자바스크립트 해석기이며 임의의 자바스크립트 프로그램을 실행한다.
1
2
3
4
test> x = 200;
200
test> x / 5;
40
또한 표준 자바스크립트 라이브러리의 모든 기능을 활용할 수 있다.
1
2
3
4
5
6
test> Math.sin(Math.PI / 2)
1
test> new Date("2019/1/1");
ISODate('2019-01-01T00:00:00.000Z')
test> "Hello, world!".replace("World", "MongoDB");
Hello, world!
심지어 자바스크립트 함수를 정의하고 호출할 수도 있다.
1
2
3
4
5
6
7
test> function factorial(n) {
... if (n <= 1) return 1;
... return n * factorial(n - 1);
... }
[Function: factorial]
test> factorial(5);
120
2.5.2. 몽고DB 클라이언트
셸은 시작할 때 몽고DB 서버의 test
데이터베이스에 연결하고, 데이터베이스 연결을 전역 변수 db
에 할당한다. 셸에서는 주로 이 변수를 통해 몽고DB에 접근한다.
현재 db에 할당된 데이터베이스를 확인하려면 db
를 입력한다.
1
2
test> db
test
데이터베이스 선택을 살펴보자.
1
2
3
test> use video
switched to db video
video>
- 이와 같이 db 변수는 video 데이터베이스를 가리킨다.
자바스크립트 셸이므로 변수 이름을 입력하면 표현식으로 평가된다. 그런 다음 값이 출력된다. 다음과 같이 db 변수에서 컬렉션에 접근한다.
1
2
video> db.movies
video.movies
- 현재 데이터베이스의 movies 컬렉션을 반환한다.
2.5.3. 셸 기본 작업
셸에서 CRUD 작업을 할 수 있다.
인증
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
db> use admin
switched to db admin
admin> db.auth('root','root')
{ ok: 1 }
admin> show users
[
{
_id: 'admin.root',
userId: UUID('9a65918d-12f8-471c-b59b-51815739c9e7'),
user: 'root',
db: 'admin',
roles: [ { role: 'root', db: 'admin' } ],
mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
}
]
생성
insertOne
함수는 컬렉션에 도큐먼트를 추가한다. 우선 도큐먼트를 나타내는 자바스크립트 객체인 movie라는 지역 변수를 생성한다.
1
2
3
4
5
6
7
8
video> movie = {"title" : "Star Wars: Episode IV - A New Hope",
... "director" : "George Lucas",
... "year" : 1977}
{
title: 'Star Wars: Episode IV - A New Hope',
director: 'George Lucas',
year: 1977
}
insertOne
함수를 이용해 movies 컬렉션에 저장할 수 있다.
1
2
3
4
5
admin> db.movies.insertOne(movie)
{
acknowledged: true,
insertedId: ObjectId('676e4a4046b45663fdfc0422')
}
- 다음과 같은 인증 에러가 발생하여 인증을 수행한 후에 movie를 저장했다.
1
MongoServerError[Unauthorized]: Command insert requires authentication
컬렉션에 find
를 호출해서 확인하자.
1
2
3
4
5
6
7
8
9
admin> db.movies.find().pretty()
[
{
_id: ObjectId('676e4a4046b45663fdfc0422'),
title: 'Star Wars: Episode IV - A New Hope',
director: 'George Lucas',
year: 1977
}
]
_id
키가 추가됐고 다른 키/값 쌍들은 입력한 대로 저장됐다.
읽기
find
와 findOne
은 컬렉션을 쿼리하는 데 사용한다. 단일 도큐먼트를 읽으려면 findOne
을 사용하면 된다.
1
2
3
4
5
6
7
admin> db.movies.findOne()
{
_id: ObjectId('676e4a4046b45663fdfc0422'),
title: 'Star Wars: Episode IV - A New Hope',
director: 'George Lucas',
year: 1977
}
find
와findOne
은 쿼리 도큐먼트 형태로 조건 전달도 가능하다.
갱신
데이터를 갱신하려면 updateOne
을 사용한다. updateOne
의 매개변수는 최소 두 개다. 첫 번째는 수정할 도큐먼트를 찾는 기준이고, 두 번째는 갱신 작업을 설명하는 도큐먼트이다. 갱신하려면 갱신 연산자인 set
을 이용하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
admin> db.movies.updateOne({title: "Star Wars: Episode IV - A New Hope"},
... {$set : {reviews: []}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.movies.find().pretty()
[
{
_id: ObjectId('676e4a4046b45663fdfc0422'),
title: 'Star Wars: Episode IV - A New Hope',
director: 'George Lucas',
year: 1977,
reviews: []
}
]
review
키가 생긴 것을 확인할 수 있다.
삭제
deleteOne
과 deleteMany
는 도큐먼트를 데이터베이스에서 영구적으로 삭제한다. 두 함수 모두 필터 도큐먼트로 삭제 조건을 지정한다.
1
2
3
4
admin> db.movies.deleteOne({title : "Star Wars: Episode IV - A New Hope"})
{ acknowledged: true, deletedCount: 1 }
admin> db.movies.find().pretty()
- 필터와 일치하는 모든 도큐먼트를 삭제하려면
deleteMany
를 사용한다.
2.6. 데이터형
몽고DB는 도큐먼트의 값으로 다양한 데이터형을 지원한다.
2.6.1. 기본 데이터형
몽고DB에서 도큐먼트는 JSON과 닮았다고 생각할 수 있다. 이는 여섯 가지 데이터형만을 열거하다. 데이터형이 null, 불리언, 숫자, 문자열, 배열, 객체만 지원하기 때문에 표현력은 제한적이다.
JSON은 날짜형이 없어 날짜를 다루기가 까다롭다. 부동소수점형과 정수형을 표현하는 방법이 없고, 32비트와 64비트도 구별되지 않는다.
몽고DB는 JSON의 키/값 쌍 성질을 유지하면서 추가적인 데이터형을 지원한다. 가장 일반적인 데이터형은 다음과 같다.
- null
- null 값과 존재하지 않는 필드를 표현하는 데 사용한다.
- 불리언
- 참과 거짓 값에 사용한다.
- 숫자
- 셸은 64비트 부동소수점 수를 기본으로 사용한다. 4바이트 혹은 8바이트의 부호 정수는 각각
NumberInt
혹은NumberLong
클래스를 사용한다. - 문자열
- 어떤 UTF-8 문자열이든 표현할 수 있다.
- 날짜
- 몽고DB는 1970년 1월 1일부터의 시간을 1/1000초 단위로 나타내는 64비트 정수로 날짜를 저장한다.
- 정규표현식
- 쿼리는 자바스크립트의 정규 표현식 문법을 사용할 수 있다.
- 배열
- 값의 셋이나 리스트를 배열로 표현할 수 있다.
- 내장 도큐먼트
- 도큐먼트는 부모 도큐먼트의 값으로 내장된 도큐먼트 전체를 포함할 수 있다.
- 객체 ID
- 객체 ID는 도큐먼트용 12바이트 ID다. (ex
ObjectId()
)
다음은 상대적으로 덜 사용되는 데이터형 목록이다.
- 이진 데이터
- 임의의 바이트 문자열이며 셸에서는 조작이 불가능하다.
- 코드
- 쿼리와 도큐먼트는 임의의 자바스크립트 코드를 포함할 수 있다. (ex.
function () { /* ... */ }
)
2.6.2. 날짜
자바스크립트에서 Date 클래스는 몽고DB의 날짜를 표현하는 데 사용한다. 새로운 Date 객체를 생성할 때는 new Date()
를 호출해야 한다. 셸에서는 날짜가 현지 시간대 설정을 이용해 표시되며, 표준 시간대 정보는 없다.
2.6.3. 배열
배열은 정렬 연산(리스트, 스택, 큐)과 비정렬 연산(셋)에 호환성 있게 사용 가능한 값이다.
1
{"thighs" : ["pie", 3.14]}
- 서로 다른 데이터형을 값으로 포함할 수 있다.
- 심지어는 중첩 배열도 될 수 있다.
도큐먼트 내 배열의 장점으로 몽고DB가 배열의 구조를 ‘이해한다’는 점과, 배열의 내용에 작업을 수행하기 위해 내부에 도달하는 방법을 안다는 점이 있다. 따라서 배열에 쿼리하거나 배열의 내용을 이용해 인덱스를 만들 수 있다.
2.6.4. 내장 도큐먼트
도큐먼트는 키에 대한 값이 될 수 있는데 이를 내장 도큐먼트라고 한다.
1
2
3
4
5
6
7
8
{
"name" : "John Doe",
"address" : {
"street" : "123 Park Street",
"city" : "Anytown",
"state" : "NY"
}
}
- address는 내장 도큐먼트이다.
- 이러한 특성으로 몽고DB는 데이터 반복이 생길 수 있다는 단점이 있다.
2.6.5. _id와 ObjectId
몽고DB에 저장된 모든 도큐먼트는 _id
키를 가진다. _id
키 값은 어떤 데이터형이어도 상관없지만 ObjectId
가 기본이다. 하나의 컬렉션에서 모든 도큐먼트는 고유한 _id
값을 가지며, 이 값은 컬렉션 내 모든 도큐먼트가 고유하게 식별되게 한다.
ObjectIds
ObjectId
는 _id
의 기본 데이터형이다. ObjectId
클래스는 가벼우면서도, 전역적으로 고유하게 생성하기 쉽게 설계됐다. ObjectId
를 사용하는 이유는 몽고DB의 분산 특성 때문이다.
ObjectId
는 12바이트 스토리지를 사용하며 24자리 16진수 문자열 표현이 가능하다. 바이트당 2자리를 사용한다.ObjectId
가 16진수 문자열로 표현되긴 하지만 실제로 문자열은 저장된 데이터의 두 배만큼 길다는 점을 알아두자.
ObjectId
의 첫 4바이트는 타임스탬프다. 다음 5바이트는 랜덤 값이다. 최종 3바이트는 서로 다른 시스템에서 충돌하는 ObjectId
들을 생성하지 않도록 랜덤 값으로 시작하는 카운터다. ObjectId
는 프로세스당 1초에 256^3(1677만7216)개까지 생성된다.
_id 자동 생성
도큐먼트를 입력할 때 _id
키를 명시하지 않으면 입력된 도큐먼트에 키가 자동으로 추가된다.
2.7. 몽고DB 셸 사용
다른 장비나 포트에 mongod를 연결하려면 셸을 시작할 때 호스트명, 포트, 데이터베이스를 명시해야 한다.
1
$ mongosh some-host:30000/myDB
- db는 이제 some-host:30000의 myDB 데이터베이스를 참조한다.
--nodb
옵션을 사용하면 어디에도 연결되지 않은 채 셸을 시작할 수 있다.- 시작한 후 원하는 때에
new Mongo(호스트명)
를 실행함으로써 연결할 수 있다.
2.7.1. 셸 활용 팁
mongo는 자바스크립트 문서를 참조하면 유용하다. 또한 help를 입력하면 셸에 내장된 도움말을 볼 수 있다. 함수의 기능을 알고 싶으면 함수명을 괄호 없이 입력하면 된다.
2.7.2. 셸에서 스크립트 실행하기
다음과 같이 자바스크립트 파일을 셸로 전달해 실행할 수도 있다.
1
$ mongosh test.js
또한 load
함수를 사용해 대화형 셸에서 스크립트를 실행할 수도 있다.
1
2
test> load('test.js')
true
스크립트에 주어진 포트에서 로컬로 실행되는 데이터베이스에 연결하고 해당 연결에 db를 설정하는 connectTo
함수를 정의하여 사용할 수 있다. 자주 하는 작업이나 관리 작업을 자동화하는 데 셸을 사용한다.
2.7.3. .mongorc.js
만들기
자주 로드되는 스크립트를 .mongorc.js
파일에 넣을 수 있다.
1
2
3
4
5
6
// .mongorc.js
var compliment = ["attractive", "intelligent", "like Batman"];
var index = Math.floor(Math.random()*3);
print("Hello, " + compliment[index] + " today!");
- 그러면 셸을 시작할 때마다 다음과 같은 문구가 뜬다.
.mongorc.js
스크립트로 사용하고 싶은 전역 변수를 설정하고, 긴 별칭을 짧게 만들고, 내장 함수를 재정의한다. .mongorc.js
의 일반적인 용법 중 하나는 위험한 명령어를 제거하는 것이다. dropDatabase
나 deleteIndexes
같은 함수가 아무것도 수행하지 않게 재정의하거나 모두 선언 해제한다.
1
2
3
4
5
6
7
8
9
10
11
12
var no = function() {
print("Not on my watch.");
}
// 데이터베이스 삭제 방지
db.dropDatabase = DB.prototype.dropDatabase = no;
// 컬렉션 삭제 방지
DBCollection.prototype.drop = no;
// 인덱스 삭제 방지
DBCollection.prototype.dropIndex = no;
2.7.4. 프롬프트 커스터마이징하기
기본 셸 프롬프트는 문자열이나 함수에 prompt
변수를 설정해 재정의한다.
1
2
3
admin> prompt = function() { return (new Date()) + "> "; };
[Function: prompt]
Fri Dec 27 2024 08:51:16 GMT+0000 (Coordinated Universal Time)>
일반적으로 프롬프트 함수는 getLastError
호출을 포함해야 한다. 그러면 셸의 연결이 끊겼을 때 쓰기에서의 오류를 감지해서 자동으로 다시 연결해준다.
2.7.5. 복잡한 변수 수정하기
셸에서 다중행 지원은 제한적이며 이전 행들을 편집할 수 없다. 따라서 코드나 객체 블록이 크면 편집기에서 편집하면 좋다. 셸에서 EDITOR 변수를 설정하자.
2.7.6. 불편한 컬렉션명
예약어로 되어 있는 컬렉션명을 사용한다면 getCollection
함수를 사용해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
admin> db.version
[Function: version] AsyncFunction {
apiVersions: [ 0, 0 ],
returnsPromise: true,
serverVersions: [ '0.0.0', '999.999.999' ],
topologies: [ 'ReplSet', 'Sharded', 'LoadBalanced', 'Standalone' ],
returnType: { type: 'unknown', attributes: {} },
deprecated: false,
platforms: [ 'Compass', 'Browser', 'CLI' ],
isDirectShellCommand: false,
acceptsRawInput: false,
shellCommandCompleter: undefined,
help: [Function (anonymous)] Help
}
admin> db.getCollection("version")
admin.version
- 또한 유효하지 않은 문자로 컬렉션명을 만들었을 때도 사용된다.
또 다른 방법으로 배열 접근 구문을 사용해 유효하지 않은 속성명을 피할 수 있다. 자바스크립트에서 x.y
는 x['y']
와 동일하다.
1
2
3
4
5
6
7
8
admin> var collections = ["posts", "comments", "authors"];
admin> for (var i in collections) {
... print(db.blog[collections[i]]);
... }
admin.blog.posts
admin.blog.comments
admin.blog.authors
3. 도큐먼트 생성, 갱신, 삭제
데이터베이스에서의 기본적인 데이터 입출력을 다룬다.
3.1. 도큐먼트 삽입
삽입은 몽고DB에 데이터를 추가하는 기본 방법이다.
1
2
3
4
5
6
7
admin> db.movies.insertOne({"title" : "Stand by Me"})
{
acknowledged: true,
insertedId: ObjectId('676e6d44501ea8f45afc0421')
}
admin> db.movies.findOne()
{ _id: ObjectId('676e6d44501ea8f45afc0421'), title: 'Stand by Me'
- 도큐먼트에
_id
키가 추가되고 도큐먼트가 저장된다.
3.1.1. insertMany
여러 도큐먼트를 컬렉션에 삽입하려면 insertMany
로 도큐먼트 배열을 데이터베이스에 전달한다. 도큐먼트를 대량 삽입하므로 훨씬 더 효율적이다.
1
2
3
4
5
6
7
8
admin> db.movies.insertMany([{"title" : "Ghostbusters"}, {"title": "E.T."}]);
{
acknowledged: true,
insertedIds: {
'0': ObjectId('676e6de3501ea8f45afc0422'),
'1': ObjectId('676e6de3501ea8f45afc0423')
}
}
- 48MB보다 큰 메시지를 허용하지 않으므로 이보다 클 경우에는 일괄 삽입 여러 개로 분할한다.
insertMany
를 사용해 대량 삽입할 때 배열 중간에서 오류가 발생하는 경우, 정렬 연산을 선택했는지 혹은 비정렬 연산을 선택했는지에 따라 달라진다. insertMany
에 대한 두 번째 매개변수로 옵션 도큐먼트를 지정할 수 있다. 옵션 도큐먼트에 ordered
키에 true
를 지정한다. false
를 지정하면 몽고DB가 성능을 개선하려고 삽입을 재배열할 수 있다. 정렬되지 않은 삽입의 경우 일부 삽입이 오류를 발생시키는지 여부에 관계없이 모든 도큐먼트 삽입을 시도한다.
다음 예에서는 정렬된 삽입이 기본값이므로 처음 두 개의 도큐먼트만 삽입된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
admin> db.movies.insertMany([
... {"_id" : 0, "title" : "test1"},
... {"_id" : 1, "title" : "test2"},
... {"_id" : 1, "title" : "test3"},
... {"_id" : 2, "title" : "test4"}])
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: admin.movies index: _id_ dup key: { _id: 1 }
Result: BulkWriteResult {
insertedCount: 2,
matchedCount: 0,
modifiedCount: 0,
deletedCount: 0,
upsertedCount: 0,
upsertedIds: {},
insertedIds: { '0': 0, '1': 1 }
}
Write Errors: [
WriteError {
err: {
index: 2,
code: 11000,
errmsg: 'E11000 duplicate key error collection: admin.movies index: _id_ dup key: { _id: 1 }',
errInfo: undefined,
op: { _id: 1, title: 'test3' }
}
}
]
admin> db.movies.find()
[
{ _id: ObjectId('676e6d44501ea8f45afc0421'), title: 'Stand by Me' },
{ _id: ObjectId('676e6de3501ea8f45afc0422'), title: 'Ghostbusters' },
{ _id: ObjectId('676e6de3501ea8f45afc0423'), title: 'E.T.' },
{ _id: 0, title: 'test1' },
{ _id: 1, title: 'test2' }
다음과 같이 정렬되지 않은 삽입을 지정하면 배열의 첫 번째, 두 번째, 네 번째 도큐먼트가 삽입된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
admin> db.movies.insertMany([ { "_id": 0, "title": "test1" }, { "_id": 1, "title": "test2" }, { "_id": 1, "title": "test3" }, { "_id": 2, "title": "test4" }], {"ordered" : false})
Uncaught:
MongoBulkWriteError: E11000 duplicate key error collection: admin.movies index: _id_ dup key: { _id: 1 }
Result: BulkWriteResult {
insertedCount: 3,
matchedCount: 0,
modifiedCount: 0,
deletedCount: 0,
upsertedCount: 0,
upsertedIds: {},
insertedIds: { '0': 0, '1': 1, '3': 2 }
}
Write Errors: [
WriteError {
err: {
index: 2,
code: 11000,
errmsg: 'E11000 duplicate key error collection: admin.movies index: _id_ dup key: { _id: 1 }',
errInfo: undefined,
op: { _id: 1, title: 'test3' }
}
}
]
admin> db.movies.find()
[
{ _id: ObjectId('676e6d44501ea8f45afc0421'), title: 'Stand by Me' },
{ _id: ObjectId('676e6de3501ea8f45afc0422'), title: 'Ghostbusters' },
{ _id: ObjectId('676e6de3501ea8f45afc0423'), title: 'E.T.' },
{ _id: 0, title: 'test1' },
{ _id: 1, title: 'test2' },
{ _id: 2, title: 'test4' }
]
3.1.2. 삽입 유효성 검사
몽고DB는 삽입된 데이터에 최소한의 검사를 수행한다. 도큐먼트의 기본 구조를 검사해 _id
필드가 존재하지 않으면 새로 추가하고, 모든 도큐먼트는 16MB보다 작아야 하므로 크기를 검사한다. doc
라는 도큐먼트의 Binary JSON 크기를 보려면 셸에서 object.bsonsize(doc)
를 실행한다.
3.2. 도큐먼트 삭제
deleteOne
과 deleteMany
를 통해 데이터를 삭제할 수 있다. 두 메서드 모두 필터 도큐먼트를 첫 번째 매개변수로 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
admin> db.movies.find()
[
{ _id: ObjectId('676e6d44501ea8f45afc0421'), title: 'Stand by Me' },
{ _id: ObjectId('676e6de3501ea8f45afc0422'), title: 'Ghostbusters' },
{ _id: ObjectId('676e6de3501ea8f45afc0423'), title: 'E.T.' },
{ _id: 0, title: 'test1' },
{ _id: 1, title: 'test2' },
{ _id: 2, title: 'test4' }
]
admin> db.movies.deleteOne({"_id" : 0})
{ acknowledged: true, deletedCount: 1 }
admin> db.movies.find()
[
{ _id: ObjectId('676e6d44501ea8f45afc0421'), title: 'Stand by Me' },
{ _id: ObjectId('676e6de3501ea8f45afc0422'), title: 'Ghostbusters' },
{ _id: ObjectId('676e6de3501ea8f45afc0423'), title: 'E.T.' },
{ _id: 1, title: 'test2' },
{ _id: 2, title: 'test4' }
]
- 여러 도큐먼트와 일치할 경우 첫 번째 도큐먼트를 삭제한다.
필터와 일치하는 모든 도큐먼트를 삭제하려면 deleteMany
를 사용한다.
1
2
3
4
# 조건에 맞는 도큐먼트 목록 제거
admin> db.movies.deleteMany({"year" : 1984})
# 모든 도큐먼트 제거
admin> db.movies.deleteMany({})
3.2.1. drop
전체 컬렉션을 삭제하려면 drop
을 사용하는 편이 더 빠르다.
1
2
admin> db.movies.drop()
true
- 이는 전부 삭제한 후에 빈 컬렉션에 인덱스를 재생성한다.
3.3. 도큐먼트 갱신
도큐먼트를 데이터베이스에 저장한 후에는 updateOne
, updateMany
, replaceOne
과 같은 갱신 메서드를 사용해 변경한다. updateOne
과 updateMany
는 필터 도큐먼트를 첫 번째 매개변수로, 변경 사항을 설명하는 수정자 도큐먼트를 두 번째 매개변수로 사용한다. replaceOne
는 두 번째 매개변수가 필터와 일치하는 도큐먼트를 교체할 도큐먼트로 지정된다. 갱신은 원자적으로 이뤄진다. 갱신 요청 두 개가 동시에 발생하면 먼저 도착한 요청이 먼저 적용된다.
3.3.1. 도큐먼트 치환
replaceOne
은 도큐먼트를 새로운 것으로 완전히 치환한다. 예를 들어 사용자 도큐먼트를 다음과 같이 변경한다고 가정하자.
1
2
3
4
5
6
{
"_id" : ObjectId("4b93819219asdsab"),
"name" : "joe",
"friends" : 32,
"enemies" : 2
}
friends
와enemies
필드를relationships
라는 서브도큐먼트로 옮겨보자.
셸에서 도큐먼트의 구조를 수정한 후 replaceOne
을 사용해 데이터베이스의 버전을 교체한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
admin> var joe = db.users.findOne({"name" : "joe"});
admin> joe.relationships = {"friends" : joe.friends, "enemies" : joe.enemies};
{ friends: 32, enemies: 2 }
admin> joe.username = joe.name;
joe
admin> delete joe.friends;
true
admin> delete joe.friends;
true
admin> delete joe.enemies;
true
admin> delete joe.name;
true
admin> db.users.replaceOne({"name" : "joe"}, joe);
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe'
}
]
갱신할 경우, 고유한 도큐먼트를 갱신 대상으로 지정하는 것이 좋다.
1
admin> db.users.replaceOne({"_id" : ObjectId('676e7528501ea8f45afc0424')}, joe)
_id
값이 컬렉션 기본 인덱스의 기초를 형성하므로 필터에_id
를 사용해도 효율적이다.
3.3.2. 갱신 연산자
부분 갱신에는 원자적 갱신 연산자를 사용한다. 갱신 연산자는 키를 변경, 추가, 제거하고, 심지어 배열과 내장 도큐먼트를 조작하는 복잡한 갱신 연산을 지정하는 데 사용한다.
다음과 같은 예시가 있다고 가정하자.
1
2
3
4
5
6
7
8
9
10
11
12
13
admin> db.pageviews.insertOne({"url" : "www.example.com", "pageviews" : 52})
{
acknowledged: true,
insertedId: ObjectId('676e76cf501ea8f45afc0425')
}
admin> db.pageviews.find()
[
{
_id: ObjectId('676e76cf501ea8f45afc0425'),
url: 'www.example.com',
pageviews: 52
}
]
누군가가 페이지를 방문할 때마다 URL로 페이지를 찾고 pageviews
키의 값을 증가시키려면 $inc
제한자를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
admin> db.pageviews.updateOne({"url" : "www.example.com"},
... {"$inc" : {"pageviews" : 1}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.pageviews.find()
[
{
_id: ObjectId('676e76cf501ea8f45afc0425'),
url: 'www.example.com',
pageviews: 53
}
]
- 연산자를 사용할 때
_id
값은 변경할 수 없다.
$set
제한자 사용하기
$set
은 필드 값을 설정한다. 필드가 존재하지 않으면 새 필드가 생성된다. 이 기능은 스키마를 갱신하거나 사용자 정의 키를 추가할 때 편리하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe'
}
]
admin> db.users.updateOne({"username" : "joe"}, {"$set" : {"favorite book" : "War and Peace"}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
'favorite book': 'War and Peace'
}
]
$set
은 키의 데이터형도 변경할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
admin> db.users.updateOne({ "username": "joe" }, { "$set": { "favorite book": ["Cat's Cradle", "Foundation Trilogy", "Ender's Game"] } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
'favorite book': [ "Cat's Cradle", 'Foundation Trilogy', "Ender's Game" ]
}
]
$unset
으로 키와 값을 모두 제거할 수도 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
admin> db.users.updateOne({ "username": "joe" }, { "$unset": { "favorite book": 1 } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe'
}
$set
은 내장 도큐먼트 내부의 데이터를 변경할 때도 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
admin> var joe = db.users.findOne({"username" : "joe"});
admin> joe
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe'
}
admin> joe.author = {"name": "joe"}
{ name: 'joe' }
admin> db.users.replaceOne({"username":"joe"}, joe);
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
author: { name: 'joe' }
}
]
admin> db.users.updateOne({"username" : "joe"}, {"$set" : { "author.name" : "joe2" } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
author: { name: 'joe2' }
}
]
- 키를 추가, 변경, 삭제할 때는 항상
$
제한자를 사용해야 한다. - 갱신 연산자를 포함하지 않고 값을 수정하려 하면 오류가 발생한다.
증가와 감소
$inc
연산자는 이미 존재하는 키의 값을 변경하거나 새 키를 생성하는 데 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
admin> db.games.insertOne({"game" : "pinball", "user" : "joe"})
{
acknowledged: true,
insertedId: ObjectId('67722441b151887453fc0421')
}
admin> db.games.updateOne({"game" : "pinball", "user" : "joe"},
... {"$inc" : { "score" : 50 }});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.games.find()
[
{
_id: ObjectId('67722441b151887453fc0421'),
game: 'pinball',
user: 'joe',
score: 50
}
]
admin> db.games.updateOne({ "game": "pinball", "user": "joe" }, { "$inc": { "score": 10000 } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.games.find()
[
{
_id: ObjectId('67722441b151887453fc0421'),
game: 'pinball',
user: 'joe',
score: 10050
}
]
$inc
는int
,long
,double
,decinal
타입 값에만 사용할 수 있다.null
, 불리언, 문자열과 같은 데이터형의 값에는 사용할 수 없다.
배열 연산자
배열을 다루는 데 갱신 연산자를 사용할 수 있다.
요소 추가하기
$push
는 배열이 이미 존재하면 배열 끝에 요소를 추가하고, 존재하지 않으면 새로운 배열을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
admin> db.blogs.posts.insertOne({"title" : "A blog post", "content" : "contents"})
{
acknowledged: true,
insertedId: ObjectId('677225b0b151887453fc0422')
}
admin> db.blogs.posts.findOne()
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents'
}
admin> db.blogs.posts.updateOne({"title" : "A blog post"},
... {"$push" : {"comments" : { "name" : "joe", "email" : "joe@example.com", "content" : "nice posts."}}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.blogs.posts.findOne()
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents',
comments: [ { name: 'joe', email: 'joe@example.com', content: 'nice posts.' } ]
}
$push
에 $each
제한자를 사용하면 작업 한 번으로 값을 여러 개 추가할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
admin> db.blogs.posts.updateOne({"title" : "A blog post"},
... {"$push" : {"reviews" : {"$each" : [ 1.0, 2.0, 3.0] }}});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.blogs.posts.findOne()
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents',
comments: [ { name: 'joe', email: 'joe@example.com', content: 'nice posts.' } ],
reviews: [ 1, 2, 3 ]
}
배열을 특정 길이로 늘이려면 $slice
를 $push
와 결합해 사용한다. 배열이 특정 크기 이상으로 늘어나지 않게 하고 효과적으로 ‘top N’ 목록을 만들 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
admin> db.blogs.posts.updateOne({ "title": "A blog post" }, { "$push": { "top10": { "$each": [1.0, 2.0, 3.0], "$slice" : - 1 } } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.blogs.posts.findOne()
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents',
comments: [ { name: 'joe', email: 'joe@example.com', content: 'nice posts.' } ],
reviews: [ 1, 2, 3 ],
top10: [ 3 ]
}
- 요소의 개수를 1개로 제한한다.
- 따라서
$slice
는 도큐먼트 내에 큐를 생성하는 데 사용할 수 있다.
마지막으로, 트리밍하기 전에 $sort
제한자를 $push
작업에 적용할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
admin> db.blogs.posts.updateOne({ "title": "A blog post" },
... {"$push" : {"related" : {"$each" : [{"name" : "post1", "rating" : 2.0},
... {"name" : "post2", "rating" : 1.0},
... {"name" : "post3", "rating" : 3.0}],
... "$slice" : -2,
... "$sort" : {"rating" : -1}}}});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.blogs.posts.findOne()
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents',
comments: [ { name: 'joe', email: 'joe@example.com', content: 'nice posts.' } ],
reviews: [
1, 2, 3, 4, 5,
6, 4, 5, 6, 4,
5, 6
],
top10: [ 3 ],
related: [ { name: 'post1', rating: 2 }, { name: 'post2', rating: 1 } ]
}
배열을 집합으로 사용하기
특정 값이 배열에 존재하지 않을 때 해당 값을 추가하면서, 배열을 집합처럼 처리하려면 쿼리 도큐먼트에 $ne
를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
admin> db.blogs.posts.updateOne({"title": {"$ne" : "post2"}}, {"$push": {"reviewContent" : "contents3"}});
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.blogs.posts.find({})
[
{
_id: ObjectId('677225b0b151887453fc0422'),
title: 'A blog post',
content: 'contents',
comments: [
{ name: 'joe', email: 'joe@example.com', content: 'nice posts.' }
],
reviews: [
1, 2, 3, 4, 5,
6, 4, 5, 6, 4,
5, 6
],
top10: [ 3 ],
related: [
{ name: 'post3', rating: 5 },
{ name: 'post3', rating: 5 },
{ name: 'post3', rating: 5 },
{ name: 'post1', rating: 6 },
{ name: 'post2', rating: 7 }
],
reviewContent: [ 'contents3' ]
},
{
_id: ObjectId('677229dcb151887453fc0426'),
title: 'post2',
content: 'contents2'
}
]
$addToSet
을 사용할 수도 있다. $addToSet
을 사용하면 중복을 피할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
admin> db.users.updateOne({"_id" : ObjectId('676e7528501ea8f45afc0424')}, {"$addToSet" : {"emails" : "joe@gmail.com"}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.users.findOne()
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
author: { name: 'joe2' },
emails: [ 'joe@gmail.com' ]
}
- 고유한 값을 여러 개 추가하려면
$addToSet
과$each
를 결합해 사용한다.
중복을 피해서 두 개 이상의 값을 추가하려면 다음과 같이 연산자를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
author: { name: 'joe2' },
emails: [ 'joe@gmail.com', 'joe@email.com', 'joe2@email.com' ]
}
]
admin> db.users.updateOne({ "_id": ObjectId('676e7528501ea8f45afc0424') }, { "$addToSet": { "emails": { "$each": ["joe@email.com", "joe2@email.com"] } } });
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 0,
upsertedCount: 0
}
admin> db.users.find()
[
{
_id: ObjectId('676e7528501ea8f45afc0424'),
relationships: { friends: 32, enemies: 2 },
username: 'joe',
author: { name: 'joe2' },
emails: [ 'joe@gmail.com', 'joe@email.com', 'joe2@email.com' ]
}
]
요소 제거하기
배열의 양쪽 끝에서 요소를 제거하는 $pop
을 사용한다. {"$pop" : {"key" : 1}}
은 배열의 마지막부터 요소를 제거하고, {"$pop" : {"key" : -1}}
은 배열의 처음부터 요소를 제거한다. $pull
은 주어진 조건에 맞는 배열 요소를 제거하는 데 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
admin> db.lists.insertOne({"todo" : ["dishes", "laundry", "dry cleaning"]});
{
acknowledged: true,
insertedId: ObjectId('677388feb151887453fc0427')
}
admin> db.lists.find()
[
{
_id: ObjectId('677388feb151887453fc0427'),
todo: [ 'dishes', 'laundry', 'dry cleaning' ]
}
]
admin> db.lists.updateOne({}, {"$pull" : {"todo" : "laundry"}})
{
acknowledged: true,
insertedId: null,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 0
}
admin> db.lists.find()
[
{
_id: ObjectId('677388feb151887453fc0427'),
todo: [ 'dishes', 'dry cleaning' ]
}
]
$pull
은 도큐먼트에서 조건과 일치하는 요소를 모두 제거한다.- 배열 연산자는 배열을 값으로 갖는 키에만 사용한다.
- 스칼라값을 변경하려면
$set
이나$inc
를 사용한다.
배열의 위치 기반 변경
배열 내 여러 값을 다루려면 위치를 이용하거나 위치 연산자를 사용하면 된다. 배열 인덱스는 기준이 0이며, 배열 요소는 인덱스를 도큐먼트의 키처럼 사용한다.
배열의 첫 번째의 값을 변경하려면 다음과 같이 변경하면 된다.
1