MongoDB는 대표적인 NoSQL 도큐먼트 데이터베이스다
도큐먼트 데이터베이스는 데이터를 테이블이 아닌, 문서처럼 저장하는 데이터베이스를 의미한다
일반적으로 도큐먼트 데이터베이스에서는 JSON 유사 형식으로 데이터를 문서화하며, 각각의 도큐먼트는 데이터를 필드-값
의 형태로 가지고 있고, 컬렉션이라고 하는 그룹으로 묶어서 관리한다
MongoDB는 NoSQL
데이터베이스다
NoSQL은 매우 넓은 범위에서 사용하는 용어로, 관계형 테이블의 레거시한 방법을 사용하지 않는 데이터 저장소를 말한다
NoSQL
데이터베이스에서는 데이터를 행과 열이 아닌, 체계적인 방식으로 저장한다
NoSQL
이 의미하는 데이터베이스의 범위가 넓다
MongoDB는 NoSQL 도큐먼트 데이터베이스다, MongoDB는 데이터를 도큐먼트의 형태로 저장한다
도큐먼트는 컬렉션에 저장되며, 이것이 MongoDB가 NoSQL 도큐먼트 데이터베이스로 분류되는 이유다
NoSQL 데이터베이스는 관계에 중점을 둔 SQL 데이터베이스보다 자유로운 형태로 데이터를 저장할 수 있으므로 필요에 따라서 새로운 데이터 유형을 추가할 수 있다
소프트웨어 개발에 정형화되지 않은 많은 양의 데이터가 필요한 경우, NoSQL이 효율적일 수 있다
NoSQL 데이터베이스는 데이터베이스를 클라우드 기반으로 쉽게 분리 할 수 있도록 지원하여, 저장 공간을 효율적으로 사용한다
시스템이 커지면서 DB를 증설해야 하는 시점이 오면, SQL 데이터베이스에서는 수직적 확장 의 형태로 DB를 증설한다
수직적으로 확장된 데이터베이스는 관리가 어려워질 수 있는데에 반해, NoSQL은 수평적 확장의 형태로 증설하므로, 이론상 무한대로 서버를 계속 분산시켜 DB를 증설할 수 있다
NoSQL 데이터베이스의 경우 스키마를 미리 준비할 필요가 없어서, 개발을 빠르게 해야하는 경우에 매우 적합하다
시장에 빠르게 프로토타입을 출시해야 하는 경우나, 소프트웨어 버전별로 많은 다운타임(데이터베이스의 서버를 오프라인으로 전환하여 작업하는 시간) 없이 데이터 구조를 자주 업데이트 해야하는 경우에는 일일이 스키마를 수정해주어야 하는 관계형 데이터베이스 보다 NoSQL 기반의 비관계형 데이터베이스가 더 효율적이다
MongoDB에서는 아틀라스(Atlas)로 클라우드에 데이터베이스를 설정한다
아틀라스는 GUI와 CLI로 데이터를 시각화, 분석, 내보내기, 그리고 빌드하는 데에 사용할 수 있습니다. 아틀라스 사용자는 클러스터를 배포 할 수 있으며, 클러스터는 그룹화된 서버에 데이터를 저장합니다.
클러스터 배포(Cluster Deployment)
인스턴스들의 모임을 클러스터라고 하며 하나의 시스템처럼 작동한다
단일 클러스터에서 각각의 인스턴스는 동일한 복제본을 가지고 있으며 이 모음을 레플리카 세트
라고 한다
클러스터를 이용하여 배포할 경우, 이는 자동으로 레플리카 세트를 생성한다
이 서버는 레플리카 세트(Replica set)로 구성되어 있으며, 레플리카 세트는 동일한 데이터를 저장하는 몇 개의 연결된 MongoDB 인스턴스의 모음이다
인스턴스는 특정 소프트웨어를 실행하는 로컬 또는 클라우드의 단일 머신이다, 이 경우에서 인스턴스는 클라우드에서 실행되는 MongoDB 데이터베이스다
레플리카 세트는 데이터의 사본을 저장하는 인스턴스의 모음이다
인스턴스 중 하나에 문제가 발생하더라도, 데이터는 그대로 유지되며, 나머지 레플리카 세트의 인스턴스에 저장된 데이터로 작업 가능하다
도큐먼트나 컬렉션을 변경할 경우, 변경된 데이터의 중복 사본이 레플리카 세트에 저장된다
이 설정 덕분에 레플리카 세트의 인스턴스 중 하나에 문제가 발생하더라도 데이터는 그대로 유지되며, 레플리카 세트의 애플리케이션에서 나머지 작업을 할 수 있다
이 과정을 위해 클러스터(서버 그룹)를 배포하면, 자동으로 레플리카 세트가 구성된다
레플리카 세트
동일한 데이터를 저장하는 소수의 연결된 머신을 뜻한다, 레플리카 세트 중 하나에 문제가 발생하더라도, 데이터를 그대로 유지할 수 있다
인스턴스
로컬 또는 클라우드에서 특정 소프트웨어를 실행하는 단일 머신, MongoDB에서는 데이터베이스다
클러스터
데이터를 저장하는 서버 그룹으로 여러 대의 컴퓨터를 네트워크를 통해 연결하여 하나의 단일 컴퓨터처럼 동작하도록 제작한 컴퓨터를 뜻한다
도큐먼트는 객체와 같이 데이터를 필드-값 쌍(Field - Value pair)
으로 저장하고 구성한다
도큐먼트에서 필드는 데이터의 고유한 식별자이고, 값은 주어진 식별자와 관련된 데이터를 뜻한다
위 예제에서 name이란 필드의 값은 kimcoding이다
이러한 도큐먼트의 모음을 컬렉션이라고 한다
그리고 데이터베이스는 여러개의 컬렉션으로 구성된다
도큐먼트(Document)
필드 - 값 쌍으로 저장된 데이터
필드(Field)
데이터 포인트를 위한 고유한 식별자
값(Value)
주어진 식별자와 연결된 데이터
컬렉션(Collection)
MongoDB의 도큐먼트로 구성된 저장소다
일반적으로 도큐먼트 간의 공통 필드가 있다
데이터베이스 당 많은 컬렉션이 있고, 컬렉션 당 많은 도큐먼트가 있을 수 있다
shell을 이용하여 도큐먼트를 조회하거나 업데이트 할 때, 도큐먼트는 JSON(JavaScript Object Notation) 형식으로 출력된다
JSON 형식으로 도큐먼트를 작성하기 위해서는, 다음과 같은 조건을 만족해야 한다
{}
중괄호로 도큐먼트가 시작하고, 끝나야 한다:
)으로 분리되어야 하며, 필드와 값을 포함하는 쌍은 쉼표(,
)로 구분된다""
)로 감싸야 한다하기의 이미지는 JSON 형식으로 도큐먼트를 작성할 때의 올바른 예시이다
도큐먼트는
{ }
)로 시작하고 끝나며, 각 필드와 값의 쌍은 쉼표(,
)로 구분된다:
)이 작성되어 있고, 각 필드는 쌍따옴표(""
)를 사용하여 문자열로 표현한다SON의 형태로 데이터를 저장할 때 장점
JSON 형식은 읽기 쉽고, 많은 개발자들이 사용하기 편리한 형태를 가지고 있다 그렇기 때문에 JSON 형식은 데이터를 저장하는 좋은 방법 중 하나다
SON의 형태로 데이터를 저장할 때 단점
이런 문제점을 해결하기 위한 방안으로 BSON(Binary JSON) 형식을 도입하였다
BSON
BSON은 컴퓨터의 언어에 가까운 이진법에 기반을 둔 표현법이다, 따라서 JSON 보다 메모리 사용이 효율적이며 빠르고, 가볍고, 유연한다
뿐만 아니라, BSON의 사용으로 더 많은 데이터 타입을 사용할 수 있다
MongoDB는 JSON형식으로 작성된 것은 무엇이든 데이터베이스에 추가할 수 있고, 쉽게 조회할 수 있다
그러나 그 내부에서는 속도, 효율성, 유연성의 장점이 있는 BSON으로 데이터를 저장, 사용하고 있다
데이터를 가져오거나(import), 내보내는(export) 경우에 따라 효율적인 데이터 형식이 존재한다
MongoDB의 데이터는 BSON의 형태로 저장이 되고, 보통 읽기 쉬운 JSON의 형태로 출력된다
두 형식의 특징이 다르기 때문이다
만약 단순히 백업 저장
을 하기 위해서라면 가볍고 빠른 BSON
의 형태를 사용하는 편이 좋다
그러나 데이터를 내보낸 후, 조회를 하거나 출력을 해야한다면 사람이 읽기 쉬운 JSON
의 형식이 바람직 하다
그래서 조건에 따라, 가져오거나 내보낼 때 사용 가능한 명령어가 각각 존재한다
먼저 JSON 형식으로 데이터를 가져오고 내보내기 위한 명령어인 `mongoimport와 mongoexport가 있고,
BSON 형식으로 가져오고 내보내기 위한 명령어 mongorestore와 mongodump가 있다
Exports data in BSON
<Atlas Cluster URI>
"Exports data in JSON
Atlas Cluster URI
>"collection name
>filename
>.json앞서 간단히 보았듯이, BSON의 형식의 데이터를 내보내기 위한 mongodump 명령어와 JSON의 형식인 데이터를 내보내기 위한 mongoexport
사용하기 위해서 Atlas Cluster URI가 필요하다
해당 URI는 일반 웹의 URI와 형식이 같고, username, password, cluster 주소로 이루어져있다
mongodump를 하는 경우에는 별다른 쿼리가 없지만,
mongoexport를 하는 경우에는 해당 데이터베이스의 컬렉션 이름, 파일 이름까지 정확하게 작성해줘야 한다
Import data in BSON dump
Atlas Cluster URI
>"*Import data in JSON
Atlas Cluster URI
>"filename
>.jsonBSON 형식의 데이터를 가져올 경우에는 mongorestore를 사용
JSON 형식의 데이터를 가져올 경우, mongoimport를 사용
모든 MongoDB 도큐먼트는 모든 도큐먼트가 _id 필드를 기본값으로 반드시 가지고 있어야 한다는 공통점이 있다
_id 필드의 값은 각 도큐먼트를 구별하는 역할을 한다
도큐먼트 내 필드와 값이 똑같다 할지라도, _id 값이 다르면 서로 다른 도큐먼트로 간주한다
도큐먼트 내 필드와 값이 다르다고 하더라도, _id값이 같다고 하면 서로 같은 도큐먼트로 여겨 에러를 발생시킨다
따라서 각 도큐먼트는 고유한 _id 값을 가지고 있어야 한다
새로운 도큐먼트를 추가할 때, _id 값에 임의적으로 고유한 값을 생성해서 사용할 수도 있지만 보통은 ObjectId 타입(12byte, 24char)의 값으로 사용한다
또한 도큐먼트를 추가할 때, _id 필드와 값을 특정하지 않았다면, 자동적으로 _id 필드가 생성되고 값에 ObjectId 타입이 할당된다
mongo shell을 사용하여 컬렉션에 새로운 도큐먼트를 추가
이를 위해서 먼저 터미널을 사용해 아틀라스 클러스터에 연결하고, MongoDB에서 제공하는 샘플 데이터를 받아온다
그리고 그 데이터베이스 중 사용하려는 데이터베이스로 이동을 한다
화면과 같이 작성된 도큐먼트를 보면, 앞서 설명한 ObjectId 타입(12byte, 24char)의 값이 _id로 작성되어 있는 것을 확인 할 수 있다
삽입을 하기 위해서는 insert라는 명령어를 사용한다
샘플 데이터베이스 중 하나인 zips 삽입
insert를 이용하여 도큐먼트를 삽입 하기 위해서는 shell에 작성된 것과 같이 insert( )의 괄호 안에 삽입하고자 하는 도큐먼트를 작성한다
그리고 이 명령어에 따른 결과물이 하단에 WriteResult로 출력된다
살펴보면 “nInserted” 라는 항목이 존재하고, 이 항목은 삽입된 도큐먼트의 수를 의미한다
그런데 이 부분이 0인 것으로 보아, 삽입된 도큐먼트가 없다는 뜻이므로 도큐먼트 추가에 실패했다는 것을 알 수 있다
더불어 그 아래 writeError라는 부분을 통해 duplicate key 에러라고 하는 이유로 추가가 되지 않았음을 알 수 있다
duplicate key 에러는 이미 같은 _id값을 가지는 도큐먼트가 컬렉션 내부에 존재하기 때문에 중복된 데이터는 삽입 할 수 없다는 것을 의미 한다
같은 데이터이지만 _id 값을 지운 도큐먼트를 zips 컬렉션에 추가
결과를 보기 전에, 주황색 블록으로 작성된 부분에서는 _id 값을 볼 수 있다
그러나 같은 도큐먼트의 내용을 가지고 있지만, 아래 파란색 블록의 부분에서는 _id 값을 삭제한 후 삽입 작업을 실행
이에 대한 결과 WriteResult({“nInserted” : 1})로 zips 컬렉션에 우리가 작성한 1개의 도큐먼트가 삽입되었다는 것을 알 수 있다
해당 필드를 사용하여 데이터를 찾아보기
같은 필드와 값으로 find 명령어로 검색
데이터를 조회하는 명령어
이를 통해 검색을 해보니 2개의 결과가 출력된다
하나는 기존 데이터베이스에 존재하던 도큐먼트이고, 또 다른 하나는 방금 추가한 도큐먼트
두 결과의 유일한 차이점은 _id 값이고, 두 번째에 삽입한 도큐먼트에는 _id 필드값을 추가하지 않았음에도 도큐먼트가 삽입 될 때, 자동적으로 해당 값이 추가 되었다
그리고 기본 값으로 ObjectId를 생성하여 할당하였다는 것을 알 수 있다
따라서 _id 값에 따라 도큐먼트가 구별된다는 것을 duplicate key 에러를 통해 다시 한 번 확인하고, insert를 통해 컬렉션에 도큐먼트를 추가 할 수 있게 되었다
한 번에 다수의 도큐먼트를 삽입하는 방법
같은 데이터베이스 내의 inspections라는 컬렉션을 예로 들어 다수의 도큐먼트를 삽입
한 번에 다수의 도큐먼트를 삽입하기 위해서는 배열 안에 해당하는 도큐먼트를 담아줘야 한다
배열 안 요소인 도큐먼트는 test라는 1개의 필드를 가지고 있고, _id 값은 주어지지 않았다
따라서 inspections 컬렉션 내의 다른 도큐먼트와 구조상으로도, 내용상으로도 중복되지 않은 도큐먼트들 이다
이에 따른 결과는 주황색 블록과 같고, writeErrors가 빈배열인 것으로 보아 에러는 발생하지 않은 것으로 보인다
더불어 nInserted 필드에 3이라는 숫자를 보아 3개의 도큐먼트가 모두 다 추가 되었다
추가하려는 도큐먼트에 모두 _id 값을 지정
첫번째 도큐먼트와 두번째 도큐먼트에는 동일한 _id 값을 주었고, 이에 따라 duplicate key 에러가 발생
그러나 주황색 블록 안의 nInserted 항목을 보니, 1개의 도큐먼트가 삽입된 것을 알 수 있다
어떤 도큐먼트가 에러 없이 삽입 되었을까 ?
하늘색 블록을 확인 하니, 첫번째 도큐먼트인 {“test” : “1” }이 삽입된 것으로 보인다, 그렇다면 왜 그 중 1개만 삽입이 되었을까?
파란색으로 블록처리 된 에러부분을 보면, 문제가 되는 사항은 두 번째 도큐먼트인 {“test” : “2”}였고, 세번째 도큐먼트인 {“test” : “3”}에 대해서는 따로 주어진 정보가 없다
이러한 에러가 출력되는 이유는 다량의 도큐먼트가 삽입 될 때, 삽입이라는 작업을 수행하기 위한 기본 프로세스가 배열 안의 리스트된 순서대로 진행되기 때문이다
따라서 1번 인덱스에 해당하는 도큐먼트인 {“test” : “2”}에서 duplicate key 에러가 발생함으로 2번 인덱스인 {“test” : “3”}은 실행이 되지 않았기 때문에 에러 메세지에는 1번 인덱스에 대한 에러메세지만 확인 가능하다
순서를 바꾸는 방법
순서를 바꾸기 위해서는 insert 명령어의 2번째 인자에 순서 옵션인 ordered를 추가해주면 된다
이번에도 이전과 똑같이 [{“_id" : "1", "test": "1"}, {"_id" : "1", "test": "2"}, {"_id" : "3", "test": “3"}] 도큐먼트를 삽입하는 쿼리문을 작성
그러자 마찬가지로 duplicate key 에러가 발생하게 되고, nInserted 항목으로 미뤄보아 1개의 도큐먼트가 추가 된 것을 알 수 있다
하늘색으로 표기된 에러메세지를 보니, 우선 0번 인덱스를 가지는 도큐먼트의 경우 이미 이전에 삽입한 도큐먼트이기 때문에 duplicate key 에러가 발생하였고,
1번 인덱스의 도큐먼트는 0번 인덱스의 도큐먼트와 _id 값이 같기 때문에 마찬가지로 duplicate key에러가 발생
따라서 이번 insert 명령어를 통해 inspections 컬렉션에 추가된 도큐먼트는 마지막인 인덱스가 2번인 도큐먼트라는 것을 알 수 있다
기본적으로 내장된 순서(주어진 도큐먼트 배열의 인덱스 순서)로 삽입되는 경우, duplicate key 에러가 나오는 순간, 작업은 중단 된다
나머지 도큐먼트들이 고유한 _id를 가진다고 해도 {"_id" : "3", "test": “3”} 처럼 작업이 진행될 기회가 오지 않는다
그러나 삽입하는 작업에 순서가 없다면, 고유한 _id를 가지는 모든 도큐먼트는 컬렉션에 추가 될 것이다
따라서 ordered 옵션을 추가함으로 삽입 순서를 바꿀 수 있다
Insert 명령어를 사용하면, 주어진 도큐먼트 배열의 인텍스 순서로 작업이 진행된다
그러나 ordered를 추가하면, 순서에 상관 없이 고유한 _id를 가진 도큐먼트는 모두 컬렉션에 삽입된다
존재하지 않는 inspection 이라는 컬렉션에 도큐먼트를 삽입
그런데 없는 컬렉션에 데이터를 삽입하였음에도 불구하고 아무런 에러가 나지 않았다
MongoDB는 사용자가 쉽게 새로운 컬렉션이나 데이터베이스를 생성하기를 원했다
그래서 만약 사용자가 존재하지 않는 컬렉션에 도큐먼트를 넣는 경우, 그와 동시에 컬렉션이 만들어지게 된다
실제로 컬렉션이 생성되었는지 확인해보니, 주황색 블록처럼 inspection이 생성된 것을 확인할 수 있다
노란색 블록으로 작성된 부분과 같이 터미널로 아틀라스 클러스터에 접속
접속을 하고 나서 미리 받아둔 MongoDB에서 샘플로 제공하는 데이터베이스 리스트를 터미널에서 확인
이 작업을 위해서는 show dbs
라는 명령어를 사용
지금 하늘색 블록에 보이는 데이터베이스 중 sample_ 로 시작하는 데이터베이스는 MongoDB가 테스트용으로 제공하는 더미(dummy)이며,
만약 sample_training 데이터베이스를 사용한다고 하면, use sample_training
라는 명령어를 입력
이 때, sample_training 자리에는 사용하기를 원하는 데이터베이스의 이름이 작성되어야 한다
그리고 이 데이터베이스가 어떤 컬렉션을 가지고 있는지 보기를 원한다면 앞서 데이터베이스 리스트를 확인하기 위해서 사용했던 show
라는 명령어를 다시 사용
show collections
라는 명령어를 작성하고 나면, sample_training 데이터베이스 안의 컬렉션 리스트가 나타나는 것을 확인 할 수 있다
zips라는 컬렉션은 미국의 우편번호 관련 데이터를 저장해두었다
따라서 뉴욕 주의 우편번호 관련 데이터를 조회하려고 한다
이 때, find 라는 명령어를 사용
find를 사용한 전체 쿼리 형식은 슬라이드에 보라색으로 작성한 부분과 같다
여기에 뉴욕 주 라고 하는 특정 조건을 만족하는 데이터를 찾으려고 하기 때문에, 쿼리문 부분에 조건을 넣어, 터미널에 작성된 것과 같은 명령어를 작성할 수 있다
db.zips.find({"state":"NY"})
이 명령어를 사용할 때, 이미 필요한 데이터베이스 공간으로 이동했기 때문에 사용할 데이터베이스의 이름을 특정해서 작성할 필요가 없다
따라서 위 명령어에서 db는 sample_training 데이터베이스를 가리키고 있다
db.zips.find({“state” : “NY”})의 결과물
이 때 find 명령어에 따른 실제 결과물은 화면에 출력된 것보다 훨씬 많지만 화면에는 랜덤하게 선택된 총 20개 결과물만 출력된다
해당 조건에 맞는 다음 20개의 도큐먼트를 조회하기 위해서는 화면의 주황색 블록과 같이 iterate의 줄임말인 it
명령어를 사용해야 한다
우리는 지금 한 가지 조건을 충족하는 결과물을 출력
두 가지의 조건을 주고 싶다면, 그 조건을 find(<쿼리문1, 쿼리문2>)의 형태로 적어주면 된다
따라서 터미널의 실행예시인 db.zips.find({“state” : “NY”, “city” : “ALBANY”}) 같이 작성
그 결과로 두 쿼리문을 만족하는 결과가 출력되고, 총 7개 데이터이기 때문에 it을 타이핑 하라는 문구는 뜨지 않는다
만약 zips 데이터베이스의 모든 데이터를 조회하고 싶다면, 다음 터미널에 작성된 코드와 같이 find 명령어를 조건 쿼리문 없이 사용하면 된다
db.zips.find()
그러면 앞서 뉴욕 주의 우편번호를 조회 했던 것과 같이 전체 미국의 우편번호 데이터 중 정렬이 되지 않은 무작위의 20개의 데이터가 출력된다
데이터를 보니 ADAMSVILLE, CHELSEA 등의 다양한 지역의 우편번호가 정리 되어 있는 것을 볼 수 있다
그런데 이렇게 출력을 하게 되면 보기가 조금 불편하다
따라서 조금 더 예쁘게 출력되도록 pretty()라고 하는 명령어를 덧붙여 사용
pretty( )는 도큐먼트의 구조와 각 필드, 값의 쌍을 조금 더 읽기 편하게 만들어 준다
데이터의 수를 조회하기 위해서는 count( ) 라고 하는 명령어를 사용
db.zips.find().count()
그러면 연두색 블록과 같이, zips 컬렉션 안 모든 데이터의 수가 출력이 된다
현재 zips 안의 미국 우편번호 관련 데이터는 총 29,470개가 있다는 것을 알게 되었다
이번에는 특정한 1개의 데이터만을 조회
따라서 초록색 블록과 같이 findOne이라는 명령어를 사용하고, MongoDB의 도큐먼트를 구별하는 고유한 값인 _id를 조건을 주어 데이터를 특정
그리고 그 결과로 주황색 블록과 같이 데이터 1개가 출력되는 것을 볼 수 있다
지금은 1개의 데이터를 고유한 값으로 선택해 조회했지만, 무작위의 데이터 1개만 가져올 수도 있다
이 경우에는 앞에서 find를 사용하여 전체 데이터를 가져온 것과 같이 조건 쿼리문을 작성하지 않은 상태의 findOne( )을 사용
updateOne
주어진 기준에 맞는 다수의 도큐먼트 중 첫 번째 도큐먼트 하나만 업데이트
updateMany
퀴문과 일치하는 모든 도큐먼트를 업데이트
mongo shell에서 도큐먼트를 업데이트 하기 위한 방법에는 updateOne, updateMany 2가지가 있다
이전에 findOne을 사용 할 때, 주어진 쿼리문에 맞는 첫번째 도큐먼트를 리턴하는 예시를 보았다 updateOne도 이와 같다다
주어진 기준에 맞는 다수의 도큐먼트가 있다면, 그 중 작업에 맞는 첫번째 도큐먼트 하나만 업데이트가 된다
반면에 updateMany를 사용하면 명령어 자체에서도 알 수 있듯, 쿼리문과 일치하는 모든 도큐먼트를 업데이트 하게 된다
미국의 우편번호 관련 데이터를 가지고 있는 zips라는 컬렉션에서 데이터를 가지고 왔다
그리고 살펴보니 이 데이터에는 단순히 우편번호만 기재 되어 있는 것이 아니라, 우편 번호가 속한 도시이름, 주 이름 등이 기재되어 있고, 그 중에서도 pop이라는 필드에 해당 지역의 인구수를 기록해두었다
그러나 이 데이터들은 몇 년 전의 데이터다
다시 말하면 기록되어 있는 인구수와 현재의 인구수는 일치하지 않을 수도 있다는 말이다
그래서 이 컬렉션의 대부분의 도시들 인구 수가 최소 10명정도 증가 했다고 가정한다면, 이때는 다수의 데이터를 업데이트 해야하기 때문에 updateMany 명령어가 적합하다
먼저 {“city”:”ALPINE”}인 도큐먼트를 찾아보니, 총 9개의 도큐먼트가 확인 된다
해당 9개 도큐먼트의 pop필드인 인구수를 각 10명씩 늘려 한 번에 업데이트를 해보려고 한다
우편번호로 구분된 ALPINE 시의 지역 인구 수를 업데이트 하기 위해서는 우선 updateMany 명령어를 사용한다
그리고 이 명령어의 첫번째 인자는 어떤 도큐먼트를 업데이트 할지 결정하는 쿼리문이 작성된다
우리는 {“city”: “ALPINE”} 이라는 조건의 지역 인구수 데이터를 업데이트 할 것이기 때문에, 첫번째 인자에 {“city”: “ALPINE”} 쿼리문을 작성했다
그리고 두번째 인자는 발생할 업데이트 내용을 특정한다
여기서 처음보는 연산자가 등장한다
MQL(MongoDB Query Language) 업데이트 연산자인 $inc를 슬라이드에 표현된 형태로 작성하면, 특정 필드의 값을 원하는 만큼 증가시킬 수 있다
이 경우에서는 우리가 ALPINE이라는 값을 가진 모든 도큐먼트의 pop 필드를 10만큼 씩 증가시킬 것이기 때문에, {"$inc" : {"pop" :10}} 와 같은 쿼리문을 작성했다
작업에 대한 응답을 살펴보면, matchedCount와 modifiedCount로 나뉘어진다
해당 부분은 명령어에 들어가는 쿼리문 2개에 대한 응답이다
matchedCount는 첫번째 인자로 들어간 조건을 충족하는 도큐먼트의 수를 의미한다
여기서는 우리가 앞서 찾았던 도큐먼트의 수와 같이 9가 출력되었다
그리고 modifiedCount는 두 번째 인자로 들어간 업데이트 연산자 $inc로 인해 수정된 도큐먼트의 수를 의미한다
matchedCount와 modifiedCount가 동일한 값으로 출력되었고, 실제로 도큐먼트의 pop필드가 성공적으로 업데이트 된 것을 확인할 수 있다
updateOne도 updateMany와 사용법은 같다
유일한 차이점은 업데이트 하는 데이터의 수 다
updateOne 같은 경우는 앞서도 언급했듯이 findOne과 같다
따라서 업데이트를 하기 위한 데이터로 zips 컬렉션의 zip 필드가 12534 값을 가지는 데이터 1개를 가져온다면,
해당 데이터는 HUDSON이라는 미국의 한 도시다, 해당 도시의 현재 인구수를 검색해보자
그리고 데이터에는 21,205명이라고 기재되어 있지면 현재는 6,235명으로 인구 수가 감소한 것으로 확인되었다
만약 이것을 $inc 연산자를 사용하여 업데이트 한다고 가정해보자
그러기 위해서는 우선 기존 데이터에 있던 인구 수에서 현재 인구수를 뺀 결과(-14,970)를 증가시켜줘야 한다
그러나 이 경우 $inc 연산자를 사용하는 것은 너무 복잡하지 않을까?
이러한 불편함 때문에 우리는 $inc
대신에 $set
연산자를 사용해보자
$set 연산자를 사용하면 주어진 필드에 지정된 값을 업데이트 한다
슬라이드 화면과 같이 기존의 pop 필드는 21205 값을 가지고 있었다
그리고 $set 연산자를 사용하여 바꾸고 싶은 값을 pop 필드에 작성했다
그러자 첫번째 인자인 {“zips”: “12534”}에 해당하는 도큐먼트의 pop 필드가 $set 연산자를 사용해 지정한 값인 6235로 변경된 것을 확인 할 수 있다
이와 같이 updateOne도 updateMany와 동일한 방식으로 작동한다는 것을 알 수 있다
그런데 만약, 여기서 우리가 필드이름을 잘못 작성한다면 어떤 일이 발생하게 될까?
zips 컬렉션에 인구수를 의미하는 필드는 pop이라고 작성되어 있다
그러나 만약 이 부분에서 population이라고 잘못 작성했다고 가정해보자
보통 에러가 발생할 것이라고 예상하지만, 슬라이드와 같이 해당 도큐먼트 안에 잘못 작성된 필드가 추가되는 것을 볼 수 있다
MongoDB는 필드가 기존에 존재하지 않았음에도 업데이트 작업을 하였고, 곧 도큐먼트에 해당 필드를 추가하고 싶다는 사용자의 의지로 여겨졌다
따라서 현재 이 도큐먼트 안에는 pop과 population 두 개의 필드가 도큐먼트 내에 존재하고, 그 둘은 같은 값을 가지고 있다
뿐만 아니라 $set 연산자도 $inc 연산자와 같이 다양한 필드에 값을 지정하기 위해서 사용할 수 있다
이번에는 sample_training 데이터베이스에서 grades 라고 하는 컬렉션을 사용해자
왜냐하면 슬라이드와 같이 score 필드에 대한 값이 배열로 이루어져 있기 때문에, 이제부터 배우게 되는 $push 연산자를 연습해보기에 적합하다
$push 연산자는 간단하게 말하자면, 배열로 이루어진 필드의 값에 요소를 추가하기 위한 연산자다
먼저 extra credit은 class_id가 339이고, student_id가 250인 학생에게 준다고 가정하자
따라서 해당 도큐먼트를 {“student_id” : 250, “class_id” : 339} 조건으로 지정한다
그리고 {“type” : “extra credit”, “score” : 100} 이러한 형태의 서브 도큐먼트를 scores 필드의 값인 배열에 추가해야하기 때문에
배열로 이루어진 필드의 값에 해당 서브 도큐먼트를 추가하기 위한 연산자인 $push를 써서 작성한다
슬라이드 상단의 응답 메세지와 같이 첫번째 인자에 해당되는 도큐먼트가 1개 존재하고, 두번째 인자에서 준 형태로 업데이트한 도큐먼트가 1개 존재한다는 것을 알게 되었다
확인해보기 위해서 다시 해당 도큐먼트를 find 명령어로 찾아보니, scores 필드의 값인 배열 안에 서브 도큐먼트가 삽입된 것을 알 수 있었다
해당 연산자는 의미와 사용법이 조금씩 다르기 때문에 잘 익혀두자
mongo shell을 사용하여 도큐먼트 및 컬렉션을 삭제하려고 한다면, deleteOne( ) 과 deleteMany( ) 를 사용할 수 있다
기본적으로 앞서 배웠던 updateOne( )과 updateMany( )와 작동방식은 비슷하다
deleteOne( )은 주어진 기준에 맞는 다수의 도큐먼트 중, 첫번째 도큐먼트 하나를 삭제한다
그렇기 때문에 deleteOne( )을 사용하는 경우, _id 값으로 쿼리해 온 도큐먼트를 삭제하는 것이 좋은 접근법이다
우리가 만약 _id 값으로 쿼리를 하지 않는다면, 검색 쿼리문에 다양한 도큐먼트가 적합할 수 있기 때문이다
혹은 기준을 충족하는 도큐먼트가 많을 경우에는 deleteMany를 사용하여 다수의 도큐먼트를 삭제할 수도 있다
이번에는 이전에 inspections 컬렉션에 임의적으로 삽입하였던 테스트 도큐먼트를 삭제해 보wk
컬렉션 안에는 임의적으로 삽입하였던 5개의 도큐먼트가 존재한다
이런 경우 각각 기준에 맞는 도큐먼트가 많다는 것을 우리가 이미 알고 있기 때문에 deleteMany( )를 사용하도록 하자
주황색 블록의 삭제한 도큐먼트의 수를 나타내는 deletedCount 항목의 숫자와 하늘색 블록의 find 명령어로 찾은 도큐먼트의 수를 비교해보니, {“test” : “1”}, {“test” : “3”} 이라는 기준에 맞는 도큐먼트를 한 번에 삭제한 것을 확인 할 수 있다
우선 deleteOne( )을 하기 위해서 알아보기 쉬운 고유한 _id값을 갖는 도큐먼트를 inspections 컬렉션에 삽입하자
그리고 실제로 {“_id”:”fordeleting”} 을 가지는 도큐먼트가 제대로 삽입되었음을 확인했다
그러면 이제 이 도큐먼트를 deleteOne을 사용해서 삭제하니, 삭제된 도큐먼트의 수를 의미하는 deletedCount에 1이 나타나는 것을 볼 수 있다
db.collection_name.drop()
컬렉션을 삭제하기 위해서는 drop이라는 명령어를 사용해야 한다
drop은 가져오기(importing)을 할 때에도 기존의 컬렉션과 가져오는 데이터 사이의 duplicate key 에러를 방지하기 위해서 사용하기도 했었다
drop 명령어를 작성하는 방법은 db.collection_name.drop() 와 같다
이 때 collection_name에는 삭제하고자 하는 컬렉션의 이름을 적어주면 된다
첫번째 주황색 블록을 보면, inspection과 inpections은 잘못 만들어졌기 때문에 두 컬렉션를 삭제해보자
따라서 첫번째로 inspection을 삭제하기 위해서 첫번째 연두색 블록처럼 db.inspection.drop( ) 명령어를 작성한다
그러자 결과로 true라고 출력되고, 실제로 컬렉션 리스트를 보니, inspection이 사라진 것을 볼 수 있다
마찬가지로 inpections도 drop 명령어를 사용하여 삭제하니, true 명령어와 함께 해당 컬렉션이 삭제된 모습을 볼 수 있다
지정된 값이 서로 같거나 같지 않은지 여부를 확인할 수 있는 $eq(Equal to) 와 $ne(Not Equal to) 가 있으며,
$gt(Greater Than)와 $lt(Less Than)로 주어진 값
보다 큰 지, 작은 지를 비교할 수 있다
$gte(Greater Than or Equal to)와 $lte(Less Than or Equal to)로는 크거나 동일한 지, 작거나 동일한 지를 비교할 수 있다
{ field : { operator : value } }와 같은 문법으로 작성하여 사용한다
실제로 작동하는 모습을 통해 비교연산자를 사용하는 방법에 대해 알아자
MongoDB에서 제공하는 sample_training 데이터베이스에는 trips 컬렉션이 있다
이는 뉴욕시의 자전거 대여 업체인 Citi Bike의 오픈 소스 데이터 샘플이다
해당 컬렉션 도큐먼트의 tripduration 필드에는 이동 시간이 초 단위로 표시되어 있다
{"tripduration" : {"$lte" : 70 } } 쿼리로 70초 동안 자전거를 타는 사람이 있는지 살펴 보자
1분 10초도 안되는 시간동안 자전거를 빌린 사람이 10명이나 있다
Citi Bike의 경우, 구독을 하면 훨씬 저렴하다
그럼 10명 중 구독자가 아닌 사람이 있는 지 알아보자
usertype에 "$ne": "Subscriber"라는 조건을 추가하여 이 10명의 라이더 중 구독자가 아닌 사람이 몇 명인지 알 수 있다
한 사람만 연간 구독자가 아니다
같은 장소에서 대여하고 반납한 이 분은 매우 짧은 시간 동안 Citi Bike를 이용했다
shell에서 쿼리해보자
GUI에서 입력한 쿼리문 앞에 find를 추가한다
Mongo shell에서는 find 명령을 사용해야하기 때문이다
또한 쿼리문 뒤에 pretty()를 추가해 출력을 보기 좋기 만든다
GUI에서 시도한 것과 같은 결과를 가져온다
이 컬렉션에는 두 가지 usertype만 있으므로
$ne : subscriber 쿼리를 $eq : customer로 작성하여도 동일한 결과를 얻을 수 있다
또한 usertype : customer를 사용한 쿼리도 동일한 결과를 반환한다
$eq는 이와 같은 상황에서 함축되어 있다
따라서 이와 같은 경우 $eq를 생략하여 사용할 수 있다
쿼리 연산자는 데이터베이스 내에서 데이터를 찾는 다양한 방법을 제공한다
비교 연산자를 사용하면 특정 범위 내에서 데이터를 찾을 수 있다
비교 연산자의 구문은 field : 뒤에 중괄호로 묶인 연산자, 콜론, 값이 온다
비교 연산자를 지정하지 않으면 $eq가 기본 연산자로 사용된다
MQL(MongoDB Query Language)에는 $and, $or, $nor, 그리고 $not이라는 네 가지 논리 연산자가 있다
$and는 지정된 모든 쿼리 절을 충족하는 도큐먼트를 반환한다
$or는 쿼리 절 중 하나라도 일치하는 도큐먼트가 있다면 해당 도큐먼트를 반환한다
$nor는 모든 절과 일치하지 않는 도큐먼트를 반환한다
마지막으로 $not은 $nor의 단일 버전같이 뒤의 조건을 만족하지 않는 모든 도큐먼트를 반환한다
$and, $or 및 $nor는 유사한 구문을 사용하며 연산자가 작동할 절의 배열 앞에 위치한다
$not은 조금 다른 방식으로 단순히 뒤에 오는 조건을 부정하기 때문에 배열 구문이 필요하지 않는다
실제로 작동하는 모습을 통해 논리연산자를 사용하는 방법에 대해 알아보자
MongoDB에서 제공하는 sample_training 데이터베이스에는 inspections 컬렉션이 있다
Inspections 컬렉션을 살펴보면 inspection result는 주로 "Violation issued"나 "No violation issued"인 것을 알 수 있다
그 외 "Pass" 라는 result도 있다
"Violation issued"와 "No violation issued"를 필터링하고 다른 result 값이 있는지 알아보려 한다
그래서 $nor 문을 사용한다
해당 쿼리를 날리면 ”Pass”, "Unable to locate", "Warning", "Fail" 및 기타 여러 유형의 결과를 찾을 수 있다
$nor이 위치한 절 배열에 “Pass”와 “Fail”을 추가하여 "Pass"와 "Fail"도 결과에서 제외할 수 있다
따라서 결과에는 "Pass", "Fail" 또는 "Violation issued", "No violation issued"이 포함되지 않는다
이것은 본질적으로 $not, $and, $or의 조합인 $nor을 사용해 한 번에 세 가지 논리 연산자를 다룬 것과 같다
이제 $and 연산자에 대해 이야기해보자
$ and가 특별한 이유는 기본적으로 쿼리에 이미 존재하기 때문이다
쿼리를 실행할 때 기본적으로 $eq가 함축되어 있는 것처럼 유사한 논리가 $and에도 있다
예를 들어, 두번째 쿼리는 실제로 $and문으로 읽는다고 하자
$and는 쿼리에 대해 참이어야 하는 여러 기준이 있는 경우 기본적으로 쿼리에 이미 함축되어 있다
함축된 $and의 또 다른 예는 동일한 필드에 여러 조건을 적용할 때 볼 수 있다
Companies 컬렉션에서 총 임직원의 수가 25보다 크고 100보다 작은 지 알아보기 위해 다음과 같이 $and 쿼리를 사용하여 작성할 수 있다
하지만 동일한 필드에 대해 쿼리하므로 $and를 제거해 두번째 쿼리처럼 더 단순하게 작성할 수 있다
그런 다음 두 조건을 하나로 결합하여 세번째 쿼리처럼 더더욱 단순하게 작성할 수 있다
그러면 $and를 언제 명시적으로 포함해야 할까?
일반적인 규칙으론 쿼리에 동일한 연산자를 두 번 이상 포함해야 할 때 $and를 명시한다
예를 들어, Routes 컬렉션에서 ICN을 통과하는 CR2 및 A81 비행기의 수를 확인해야 할 때 ICN을 통과하는 모든 비행기를 얻으려면 출발지 공항 또는 목적지 공항이 ICN이어야 한다
또한 특정 비행기 유형을 얻으려면 비행기 유형이 CR2 또는 A81이여야 한다는 조건이 필요하다
두 조건을 모두 충족하는 비행기의 수를 얻기위해 첫 부분에 $and를 명시적으로 추가한 다음, 두 개의 $or 조건을 배열안에 포함한다
이렇게 동일한 연산자를 두 번 이상 포함해야 할 때는 $and를 명시한다
요약하자면,
논리 연산자를 사용하면 데이터 검색을 보다 세분화 할 수 있다
논리 연산자의 구문은 $와 연산자 이름, 절로 구성된 배열이다
$not의 경우에는 배열이 아닌 절이 온다
논리 연산자가 지정되지 않은 경우 $and가 기본 연산자로 사용되며,
동일한 연산자를 쿼리에 두 번 이상 포함해야 하는 경우, $and를 명시적으로 사용해야 한다
$expr이라는 이름에서 알 수 있듯이 이 연산자는 다양성을 가지고 있다
이 연산자는 표현력이 풍부하여 하나 이상의 작업을 수행 할 수 있다
첫번째로 쿼리 내에서 집계 표현식(Aggregation Expression)을 사용할 수 있으며 { $expr : { expression } } 구문을 사용한다
두번째로 $expr를 이용해 변수와 조건문을 사용할 수 있다
마지막으로 $expr를 이용해 같은 도큐먼트 내의 필드들을 서로 비교할 수 있다
Trips 컬렉션에서 얼마나 많은 사람들이 자전거를 대여한 곳에 다시 자전거를 반납하는지 알아보자
해당 도큐먼트에서 모든 station은 그와 관련된 name 필드와 id 필드가 존재한다
이 중 id 필드를 가지고 쿼리를 작성해보자
$expr을 사용하면 해당 값이 어떤 필드와 같아야하는지 지정하지 않고도 자체적으로 동일한 도큐먼트 내에서 start station id 값과 end station id 값을 직접 비교할 수 있다
이렇게 $expr를 사용하여 같은 곳에서 대여하고 반납한 모든 결과에 대해 가져올 수 있다
그런데 이곳 저곳 붙은 $는 무엇일까?
이제 start station id와 end station id 연산자도 생긴 걸까?
$는 MQL에서 많은 초능력을 가지고 있다
그 중 하나는 연산자를 사용할 때를 나타내는 것이며,
다른 하나는 필드 이름 자체가 아니라 해당 필드의 값을 향하고 있음을 나타내는 것이다
해당 도큐먼트에서 이와 같은 표현식을 날릴 때 $start station id는 값인 439를 의미한다
$start station name을 사용하면 E 4th Street & 2nd Avenue를 의미한다
$를 사용하여 각각의 도큐먼트마다 달라지는 특정 필드의 값을 변수처럼 비교할 수 있다
이제 Trips 컬렉션에서 몇 명이 1200초 이상 자전거를 타고 다시 시작 지점에 자전거를 반납했는 지 알아보자
다음과 같이 $expr를 사용하여 쿼리를 작성할 수 있다
이제 이 쿼리를 분석하여 쿼리가 실제로 수행하는 작업을 자세히 살펴 보자
먼저 "$and"안에 $eq과 $gt를 추가했다
이는 우리가 비교 연산자를 사용하는 방법을 배운 것과는 약간 다르다
MQL에서 비교 연산자 구문은 먼저 필드를 작성하고 나중에 적용되는 비교 연산자를 작성한다
그러나 이 구문은 집계 표현식을 사용한다
비슷해 보이지만 문법이 약간 다르다
집계 구문에서는 연산자를 먼저 작성하고 그 다음 필드와 값을 작성한다
$push
지금까지 문자열, 숫자 및 Boolean 값이 있는 필드를 쿼리했다
MQL로 배열 필드를 쿼리하는 방법에 대해 살펴보자
우리는 이미 배열 연산자 중 하나인 $push를 update 명령어 컨텐츠에서 배웠다
이 연산자를 사용하면 배열에 요소를 추가하거나 이전에 다른 유형의 값이었던 경우 해당 필드를 배열 타입의 필드로 바꿀 수 있다
앞에서 다뤘던 배열이 아닌 다른 유형의 필드에 대해 쿼리하는 방법을
배열 필드의 값을 쿼리할 때 사용하면 어떤 일이 발생하는지 살펴보자
여행을 시작한다고 생각하고 Airbnb 데이터베이스를 살펴보자
MongoDB에서 제공하는 sample_airbnb 데이터베이스에는 배열 필드인 amenities가 포함되어 있다
각 Airbnb listings 도큐먼트에 있는 amenities 필드에서 Shampoo가 없는 Airbnb 숙소는 피하기로 해보자
“amenities” : “Shampoo”로 문자열 필드를 쿼리하는 방법으로 배열 필드에 쿼리를 시도해보자
쿼리에서 amenities가 배열이라는 것을 지정하지 않더라도 결과로 받은 도큐먼트에는 샴푸가 배열 요소 중 포함되어 있다
그럼 어떻게 배열 필드인 amenities를 차별화하여 쿼리할 수 있을까?
shampoo를 대괄호로 묶어 배열로 만들어 쿼리를 작성해보자
이 결과 아무런 도큐먼트도 반환하지 않았다
이는 MongoDB가 배열 필드의 값으로 Shampoo만 담긴 배열을 찾고 있기 때문이다
왜냐하면 추가적인 연산자가 없을때에는 지정한 쿼리와 정확히 일치하는 도큐먼트만 찾기 때문이다
이제 배열 필드를 쿼리할 때, 배열 요소의 순서가 영향을 미치는 지 살펴보자
이를 테스트하기 위해 캐나다 몬트리올에 있는 숙소의 도큐먼트에 있는 amenities 배열 필드를 복사하고 위 방식으로 검색을 해보자
결과로 배열을 복사해온 몬트리올의 숙소가 결과로 반환되었다
이제 그 전에 작성한 쿼리에서 배열의 처음 두 요소의 순서를 바꿔 보자
순서가 중요하지 않은 경우에는 동일한 도큐먼트가 반환될 것이다
그러나 순서가 중요하면 다른 도큐먼트를 반환하거나 순서가 맞는 배열이 없다면 아무것도 반환하지 않을 것 이다
결과로 0개가 나왔다
이로 인해 순서가 중요하다는 것을 알게되었다
이제 배열 요소의 순서를 신경 쓰지 않고 특정 요소를 포함하는 모든 도큐먼트를 어떻게 찾을 수 있는지 알아볼 차례다
MQL에는 "$all"이라는 훌륭한 연산자가 있다
이 쿼리는 배열 필드에 지정한 요소가 있는 모든 도큐먼트를 반환한다
$all을 사용하면 배열 요소의 순서와 상관없이 지정된 요소가 포함된 모든 도큐먼트를 찾을 수 있다
반환된 결과를 살펴보니 amenities가 많이 포함될수록 숙소가 보통 더 비싸진다는 것을 알게되었다
이제 배열 연산자를 이용해 배열의 길이로 결과를 제한해보자
배열의 길이에 따라 결과 커서를 제한하려면 쿼리에 $size를 추가한다
$size : 20으로 정확히 20개의 amenities를 가지며 지정한 요소가 모두 배열에 포함된 도큐먼트만 반환되었다
특정한 요소를 찾지않는다면, $size만을 사용하여 배열 길이로만 쿼리할 수도 있다
요약하자면,
$size 배열 연산자는 지정된 배열 필드가 주어진 길이와 정확히 일치하는 모든 도큐먼트들이 있는 커서를 반환한다
$all 배열 연산자는 지정된 배열 필드의 배열 순서와 관계없이 지정된 모든 요소가 포함된 모든 도큐먼트들이 있는 커서를 반환한다
배열 연산자를 쓰지않고 배열 필드를 쿼리하는 경우 배열을 지정한다면 해당 배열과 정확히 일치하는 배열을 가진 도큐먼트를 찾는다
문자열로 요소를 지정한다면 해당 요소가 배열에 포함된 모든 도큐먼트를 찾는다