# MongoDB 基础

<nav>
<a href="#一数据类型">一、数据类型</a><br/>
<a href="#二新增数据">二、新增数据</a><br/>
<a href="#三查询数据">三、查询数据</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#31-基本-API">3.1 基本 API</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#32-等值查询">3.2 等值查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#33-范围查询">3.3 范围查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#34-逻辑查询">3.4 逻辑查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#35-集合查询">3.5 集合查询</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#36-额外操作">3.6 额外操作</a><br/>
<a href="#四修改数据">四、修改数据</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#41-基本-API">4.1 基本 API</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#42-常规修改器">4.2 常规修改器</a><br/>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<a href="#43-数组修改器">4.3 数组修改器</a><br/>
<a href="#五删除数据">五、删除数据</a><br/>
</nav>

## 一、数据类型

### 1.1 BSON

文档 (Documents) 是 MongoDB 中数据的基本单元，它是键值对的有序集合，在数据结构上类似于 JSON，但是由于 JSON 只支持基本的 6 种数据类型：null，布尔，数字，字符串，数组和对象，因此其表达能力有限，所以 MongoDB 基于 JSON 拓展出新的数据格式 BSON 。BSON (Binary JSON) 是类似 JSON 文档的二进制编码序列化格式，与 JSON 类似，但支持更多的数据类型。当前 MongoDB 4.0 的 BSON 支持以下所有的数据类型：

| Type （类型）           | Alias （别名）        | Notes （注释）                                               |
| :---------------------- | :-------------------- | :----------------------------------------------------------- |
| Double                  | “double”              | 双精度型                                                     |
| String                  | “string”              | 字符串类型，默认的编码类型为 UTF-8                           |
| Object                  | “object”              | 对象类型                                                     |
| Array                   | “array”               | 数据类型                                                     |
| Binary data             | “binData”             | 二进制数据                                                   |
| Undefined               | “undefined”           | 该数据类型已经废弃                                           |
| ObjectId                | “objectId”            | 对象 id                                                      |
| Boolean                 | “bool”                | 布尔值                                                       |
| Date                    | “date”                | 日志类型                                                     |
| Null                    | “null”                | 空                                                           |
| Regular Expression      | “regex”               | 正则表达式                                                   |
| DBPointer               | “dbPointer”           | 该数据类型已经废弃                                           |
| JavaScript              | “javascript”          |                                                              |
| Symbol                  | “symbol”              | 该数据类型已经废弃                                           |
| JavaScript (with scope) | “javascriptWithScope” |                                                              |
| 32-bit integer          | “int”                 | 32 位整型                                                    |
| Timestamp               | “timestamp”           | 时间戳类型                                                   |
| 64-bit integer          | “long”                | 64 位整型                                                    |
| Decimal128              | “decimal”             | 3.4 版本新增的数据类型，类似 Java 中的 BigDecimal 类型，<br/>用于解决浮点型丢失精度的问题 |
| Min key                 | “minKey”              |                                                              |
| Max key                 | “maxKey”              |                                                              |

### 1.2 ObjectId

在上面的表格中有一个比较重要的数据类型是：ObjectId 。存储在 MongoDB 集合中的每个文档都有一个唯一的 `_id` 字段作为主键，可以在插入的时候手动指定，或者由程序自动生成，这个字段可以是任何的数据类型，默认是 ObjectId 类型。ObjectId 使用 12 字节的存储空间，是一个由 24 个十六进制数字组成的字符串，每个字符串可以存储两个十六进制数字：

+ 前 4 个字节是从标准纪元开始的时间戳，单位为秒；

+ 中 5 个字节是由两个部分组成：前 3 个字节是所在主机的唯一标识，通常是主机名的散列值，用于保证不同主机生成不同 ObjectId ，后两个字节是进程标识符 (PID) ，用于保证同一主机上不同进程产生不同的 ObjectId。

+ 最后 3 个字节是一个自动增加的计数器，确保相同主机上相同进程在同一秒产生的 ObjectId 也是不同的，即每个进程一秒最多可以拥有 16777216 个不同的 ObjectId (16777216 =  (2\*\*8)\*\*3，1 个字节等于 8 位二进制) 。


## 二、新增数据

```shell
db.collection.insert()
```

在 MongoDB 3.2 之前，插入数据的语法如上，可以用于插入单条或者多条数据。在 3.2 之后，MongoDB 为了增强 API 的语义，增加了如下两个 API ，分别用于显示表达插入单条数据和多条数据的行为。

```shell
db.collection.insertOne() 
db.collection.insertMany() 
```

新增单条数据的示例如下。额外需要说明的是，在插入文档前，文档所属的集合不必预先创建，程序会自动创建：

```shell
db.user.insertOne({
    name: "heibai",
    age: 26,
    birthday: new Date(1998,08,23),
    createTime: new Timestamp(),
    Hobby: ["basketball", "football", "tennis"]
})
```

新增多条数据的示例如下：

```shell
db.user.insertMany([
    {
        name: "hei",
        age: 32,
        birthday: new Date(1989,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    },
    {
        name: "ying",
        age: 46,
        birthday: new Date(1978,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    }
])

# 此时会返回新插入的数据的ObjectId
{
	"acknowledged" : true,
	"insertedIds" : [
		ObjectId("5d3d0489ad38cd3becc7b03b"),
		ObjectId("5d3d0489ad38cd3becc7b03c")
	]
}
```

## 三、查询数据

### 3.1 基本 API

查询数据的基本语法如下：

```shell
db.collection.find(<query>, <projection>)
```

+ `<query>`：用于指定查询条件，不加任何条件则默认查询集合中全部数据；
+ `<projection>`：可选操作，用于自定查询的返回字段，1 表示该字段包含在返回结果中，0 表示不返回，示例如下：

```shell
db.user.find({},{name: 1, ObjectId:-1})}
```

### 3.2 等值查询

想要查询某个字段等于指定值的数据，可以使用如下语法：

```shell
db.user.find({name:"heibai"})
```

上面的语法实际上是 `$eq` 操作的简写形式，如下：

```shell
db.user.find({name: {$eq: "heibai"}})
```

所以如果你想要进行非等值查询，则可以使用 `$ne` 操作符，代表 not equal ，示例如下：

```shell
db.user.find({name: {$ne: "heibai"}})
```

特别的，如果你想允许某个字段等于多个值，可以使用 `$in` 操作符，示例如下：

```shell
db.user.find({name: {$in:["heibai","ying"]} })
```

### 3.3 范围查询

Mongodb 提供了比较操作符 `$lt`、`$lte`、`$gt` 和 `$gte` ，分别对应 <、 <=、 >和 >= ，主要用于范围查查询，示例如下：

```shell
db.user.find({age: {$gt: 20, $lt: 40}})
```

### 3.4 逻辑查询

Mongodb 提供了逻辑操作符 `$or`、`$and`、`$not`、`$nor` ，用于处理多个条件间的逻辑关系，示例如下：

查询姓名为 heibai 或者年龄大于 30 岁的所有用户，此时可以使用 $or 操作符：

```shell
db.user.find( { $or: [{ name: "heibai" }, { age: { $gt: 30 } }] })
```
查询所有姓名不是以 hei 开头的所有用户，此时可以使用 $not 操作符来配合正则表达式：

```shell
db.user.find({name: {$not: /^hei*/}})
```

如果文档中存在 name 字段，则它的值不能为 heibai，如果文档中存在 age 字段，则它的值不能大于 30 ，只有满足以上两个条件的文档才会被查询出来，示例如下：

```shell
db.user.find( { $nor: [{ name: "heibai" }, { age: { $gt: 30 } }] })
```

$and 操作符的使用率比较低，因为此时更好的方式是把多个条件写到同一个对象中，从而减少额外嵌套。

### 3.5 集合查询

如果需要查询个人爱好中有 football 的所有用户，即只要集合 Hobby 中存在 football 即可，对应的查询方法如下：

```shell
db.user.find({Hobby: "football"})
```
如果想要获取集合中指定位置等于指定值的文档，对应的查询方法如下：

```shell
db.user.find({"Hobby.2": "football"})
```
如果想要约束集合必须包含多个指定值，此时可以使用 $all 操作符：

```shell
db.user.find({Hobby:{ $all: ["football", "tennis"]}})
```
查询时如果只想返回集合的部分内容，则可以使用 $slice ，$slice 接收一个参数 n，正数表示获取集合的前 n 个参数，负数表示获取集合末尾的 n 个参数，示例如下：

```shell
db.user.find({name: "heibai"},{Hobby:{$slice: 2}})
```

### 3.6 额外操作

在查询操作之后，MongoDB 还提供了三个额外的函数 `skip`、`sort` 和 `limit` ，分别用于表示跳过一定量的数据、按照指定规则对数据进行排序和限制返回数据的量，示例如下： 

```shell
db.user.find({})
   .skip(1)
   .sort({_id:-1})
   .limit(10)
```

## 四、修改数据

### 4.1 基本 API

MongoDB 提供了以下三个 API，分别用于修改单条数据、多条数据和执行单条数据替换：

```shell
db.collection.replaceOne(<filter>, <update>, <options>)
db.collection.updateOne(<filter>, <update>, <options>)
db.collection.updateMany(<filter>, <update>, <options>)
```

+ `<filter>`：过滤条件，用于查询需要修改的数据；
+ `<update>`：更改操作或新文档数据；
+ ` <options>`：可选操作，常用的可选操作是 `upsert` ，当其为 true 时，代表如果按照过滤条件没有找到对应的文档，则将待更改的数据插入到集合中；当其为 false 时，如果没有找到数据，则不执行任何操作。示例如下：

```shell
db.user.replaceOne(
    { _id: ObjectId("5d3d00a4ad383d3becc7b03a")},
    {
        name: "danrenying",
        age: 32,
        birthday: new Date(1995,08,23),
        createTime: new Timestamp(),
        Hobby: ["basketball", "football", "tennis"]
    },
    {upsert : true}
)
```

### 4.2 常规修改器

想要对数据进行修改，必须明确表达修改行为，在 MongoDB 中这是通过修改器来实现的，常用的修改器如下：

#### 1. $set 

用于修改具体的字段，如果待修改的字段不存在，则会新增该字段。示例如下：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $set: {age: 66} }
)
```

#### 2. $inc 

用于对指定字段的值进行增加或减少，示例如下：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $inc: {age: -10} }
)
```

### 4.3 数组修改器

在修改操作中，比较复杂的是对数组数据的修改，为了解决这个问题，MongoDB 提供了一系列的修改器，用于数组操作：

#### 1. $push

用于往数组中新增数据，示例如下。使用 `$each` 可以一次添加多个元素：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $push: {"Hobby": {$each: ["film","music"]}} }
)
```

#### 2. $addToSet

该修改器可以把数组当做集 (set) 来使用，即只能添加当前数组中不存在的数据，示例如下：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $addToSet: {"Hobby": {$each: ["film","music"]}} }
)
```

#### 3. $pop

该修改器可以从数组任意一端删除元素，`-1` 代表从数组头删除元素，`1` 代表从数组尾删除元素，示例如下：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $pop: {"Hobby": -1} }
)
```

#### 4. index

对于数组还可以直接修改指定下标位置的元素，示例如下：

```shell
db.user.updateOne(
    { name: "danrenying"},
    { $set: {"Hobby.0": "Cooking"} }
)
```

## 五、删除数据

MongoDB 提供了以下两个 API 用于删除操作：

```shell
db.collection.deleteMany()
db.collection.deleteOne()
```

使用示例如下：

```shell
db.user.deleteOne(
    { name: "danrenying"}
)
```



## 参考资料

+ 官方文档：[MongoDB CRUD Operations](https://docs.mongodb.com/manual/crud/)

+ Kristina Chodorow . MongoDB权威指南（第2版）. 人民邮件出版社 . 2014-01