diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5aec24f12931dc20db51640d3b3f59f798e909a9..1c0b24cfa674c1da1adbe04739b5236620c49a9b 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -34,7 +34,8 @@ const config = { algolia: { apiKey: '2fdcc4e76c8e260671ad70065e60b2e7', indexName: 'zh-uniapp', - appId: 'PQIR5NL8CZ' + appId: 'PQIR5NL8CZ', + searchParameters: { hitsPerPage: 80 } } }, markdown: { diff --git a/docs/uniCloud/cf-database-aggregate-operator.md b/docs/uniCloud/cf-database-aggregate-operator.md new file mode 100644 index 0000000000000000000000000000000000000000..74b0df1eb07d96109e4630620a45e7926fbf79a7 --- /dev/null +++ b/docs/uniCloud/cf-database-aggregate-operator.md @@ -0,0 +1,4774 @@ +# 云数据库运算方法@aggregate-operator + +**等同于mongoDB聚合操作符概念** + +## 算术操作符 + +### abs + + + +返回一个数字的绝对值。 + +#### API 说明 + +语法如下: + + +```js +db.command.aggregate.abs() +``` +`abs` 传入的值除了数字常量外,也可以是任何最终解析成一个数字的表达式。 + + 如果表达式解析为 `null` 或者指向一个不存在的字段,则 `abs` 的结果是 `null`。如果值解析为 `NaN`,则结果是 `NaN`。 + + +#### 示例代码 + 假设集合 `ratings` 有如下记录: + + +```json +{ _id: 1, start: 5, end: 8 } +{ _id: 2, start: 4, end: 4 } +{ _id: 3, start: 9, end: 7 } +{ _id: 4, start: 6, end: 7 } +``` +··· +可以用如下方式求得各个记录的 `start` 和 `end` 之间的绝对差异大小: + + +```js +const $ = db.command.aggregate +let res = await db.collection('ratings').aggregate() + .project({ + delta: $.abs($.subtract(['$start', '$end'])) + }) + .end() +``` +返回结果如下: + + +```json +{ "_id" : 1, "delta" : 3 } +{ "_id" : 2, "delta" : 0 } +{ "_id" : 3, "delta" : 2 } +{ "_id" : 4, "delta" : 1 } +``` + +### add + + + +将数字相加或将数字加在日期上。如果数组中的其中一个值是日期,那么其他值将被视为毫秒数加在该日期上。 + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.add([<表达式1>, <表达式2>, ...]) +``` +表达式可以是形如 `$ + 指定字段`,也可以是普通字符串。只要能够被解析成字符串即可。 + + +#### 示例代码 + 假设集合 `staff` 有如下记录: + + +```json +{ _id: 1, department: "x", sales: 5, engineer: 10, lastUpdate: ISODate("2019-05-01T00:00:00Z") } +{ _id: 2, department: "y", sales: 10, engineer: 20, lastUpdate: ISODate("2019-05-01T02:00:00Z") } +{ _id: 3, department: "z", sales: 20, engineer: 5, lastUpdate: ISODate("2019-05-02T03:00:00Z") } +``` + + +**数字求和** + + 可以用如下方式求得各个记录人数总数: + + +```js +const $ = db.command.aggregate +let res = await db.collection('staff').aggregate() + .project({ + department: 1, + total: $.add(['$sales', '$engineer']) + }) + .end() +``` +返回结果如下: + + +```json +{ _id: 1, department: "x", total: 15 } +{ _id: 2, department: "y", total: 30 } +{ _id: 3, department: "z", total: 25 } +``` + + +**增加日期值** + + 如下操作可以获取各个记录的 `lastUpdate` 加一个小时之后的值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('staff').aggregate() + .project({ + department: 1, + lastUpdate: $.add(['$lastUpdate', 60*60*1000]) + }) + .end() +``` +返回结果如下: + + +```json +{ _id: 1, department: "x", lastUpdate: ISODate("2019-05-01T01:00:00Z") } +{ _id: 2, department: "y", lastUpdate: ISODate("2019-05-01T03:00:00Z") } +{ _id: 3, department: "z", lastUpdate: ISODate("2019-05-02T04:00:00Z") } +``` + +### ceil + +向上取整,返回大于或等于给定数字的最小整数。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.ceil() +``` +`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 + + +#### 示例代码 + 假设集合 `sales` 有如下记录: + + +```json +{ _id: 1, sales: 5.2 } +{ _id: 2, sales: 1.32 } +{ _id: 3, sales: -3.2 } +``` +可以用如下方式取各个数字的向上取整值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('sales').aggregate() + .project({ + sales: $.ceil('$sales') + }) + .end() +``` +返回结果如下: + + +```json +{ _id: 1, sales: 6 } +{ _id: 2, sales: 2 } +{ _id: 3, sales: -3 } +``` + +### divide + +传入被除数和除数,求商。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.divide([<被除数表达式>, <除数表达式>]) +``` +表达式可以是任意解析为数字的表达式。 + + +#### 示例代码 + 假设集合 `railroads` 有如下记录: + + +```js +{ _id: 1, meters: 5300 } +{ _id: 2, meters: 64000 } +{ _id: 3, meters: 130 } +``` +可以用如下方式取各个数字转换为千米之后的值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('railroads').aggregate() + .project({ + km: $.divide(['$meters', 1000]) + }) + .end() +``` +返回结果如下: + + +```js +{ _id: 1, km: 5.3 } +{ _id: 2, km: 64 } +{ _id: 3, km: 0.13 } +``` + +### exp + +取 e(自然对数的底数,欧拉数) 的 n 次方。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.exp() +``` +`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 + + +#### 示例代码 + 假设集合 `math` 有如下记录: + + +```js +{ _id: 1, exp: 0 } +{ _id: 2, exp: 1 } +{ _id: 3, exp: 2 } +``` + +```js +const $ = db.command.aggregate +let res = await db.collection('math').aggregate() + .project({ + result: $.exp('$exp') + }) + .end() +``` +返回结果如下: + + +```js +{ _id: 1, result: 1 } +{ _id: 2, result: 2.71828182845905 } +{ _id: 3, result: 7.38905609893065 } +``` + +### floor + +向下取整,返回大于或等于给定数字的最小整数。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.floor() +``` +`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 + + +#### 示例代码 + 假设集合 `sales` 有如下记录: + + +```js +{ _id: 1, sales: 5.2 } +{ _id: 2, sales: 1.32 } +{ _id: 3, sales: -3.2 } +``` +可以用如下方式取各个数字的向下取整值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('sales').aggregate() + .project({ + sales: $.floor('$sales') + }) + .end() +``` +返回结果如下: + + +```js +{ _id: 1, sales: 5 } +{ _id: 2, sales: 1 } +{ _id: 3, sales: -4 } +``` + +### ln + +计算给定数字在自然对数值。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.ln() +``` +`` 可以是任意解析为非负数字的表达式。 + + `ln` 等价于 `log([, Math.E])`,其中 `Math.E` 是 `JavaScript` 获取 `e` 的值的方法。 + + +#### 示例代码 + +#### db.command.aggregate.ln + 聚合操作符。计算给定数字在自然对数值。 + + 语法如下: + + +```js +db.command.aggregate.ln() +``` +`` 可以是任意解析为非负数字的表达式。 + + `ln` 等价于 `log([, Math.E])`,其中 `Math.E` 是 `JavaScript` 获取 `e` 的值的方法。 + +### log + +计算给定数字在给定对数底下的 log 值。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.log([, ]) +``` +`` 可以是任意解析为非负数字的表达式。`` 可以是任意解析为大于 1 的数字的表达式。 + + 如果任一参数解析为 `null` 或指向任意一个不存在的字段,`log` 返回 `null`。如果任一参数解析为 `NaN`,`log` 返回 `NaN`。 + + +#### 示例代码 + 假设集合 `curve` 有如下记录: + + +```js +{ _id: 1, x: 1 } +{ _id: 2, x: 2 } +{ _id: 3, x: 3 } +``` +计算 `log2(x)` 的值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('curve').aggregate() + .project({ + log: $.log(['$x', 2]) + }) + .end() +``` +返回结果如下: + + +```js +{ _id: 1, log: 0 } +{ _id: 2, log: 1 } +{ _id: 3, log: 1.58496250072 } +``` + +### log10 + +计算给定数字在对数底为 10 下的 log 值。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.log() +``` +`` 可以是任意解析为非负数字的表达式。 + + `log10` 等同于 `log` 方法的第二个参数固定为 10。 + + +#### 示例代码 + +#### db.command.aggregate.log10 + 聚合操作符。计算给定数字在对数底为 10 下的 log 值。 + + 语法如下: + + +```js +db.command.aggregate.log() +``` +`` 可以是任意解析为非负数字的表达式。 + + `log10` 等同于 `log` 方法的第二个参数固定为 10。 + +### mod + +取模运算,取数字取模后的值。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.mod([, ]) +``` +第一个数字是被除数,第二个数字是除数。参数可以是任意解析为数字的表达式。 + + +#### 示例代码 + 假设集合 `shopping` 有如下记录: + + +```js +{ _id: 1, bags: 3, items: 5 } +{ _id: 2, bags: 2, items: 8 } +{ _id: 3, bags: 5, items: 16 } +``` +各记录取 `items` 除以 `bags` 的余数(`items % bags`): + + +```js +const $ = db.command.aggregate +let res = await db.collection('shopping').aggregate() + .project({ + overflow: $.mod(['$items', '$bags']) + }) + .end() +``` +返回结果如下: + + +```js +{ _id: 1, log: 2 } +{ _id: 2, log: 0 } +{ _id: 3, log: 1 } +``` + +### multiply + +取传入的数字参数相乘的结果。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.multiply([, , ...]) +``` +参数可以是任意解析为数字的表达式。 + + +#### 示例代码 + 假设集合 `fruits` 有如下记录: + + +```js +{ "_id": 1, "name": "apple", "price": 10, "quantity": 100 } +{ "_id": 2, "name": "orange", "price": 15, "quantity": 50 } +{ "_id": 3, "name": "lemon", "price": 5, "quantity": 20 } +``` +求各个水果的的总价值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('fruits').aggregate() + .project({ + name: 1, + total: $.multiply(['$price', '$quantity']), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "name": "apple", "total": 1000 } +{ "_id": 2, "name": "orange", "total": 750 } +{ "_id": 3, "name": "lemo", "total": 100 } +``` + +### pow + +求给定基数的指数次幂。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.pow([, ]) +``` +参数可以是任意解析为数字的表达式。 + + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ "_id": 1, "x": 2, "y": 3 } +{ "_id": 2, "x": 5, "y": 7 } +{ "_id": 3, "x": 10, "y": 20 } +``` +求 `x` 和 `y` 的平方和: + + +```js +const $ = db.command.aggregate +let res = await db.collection('stats').aggregate() + .project({ + sumOfSquares: $.add([$.pow(['$x', 2]), $.pow(['$y', 2])]), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "sumOfSquares": 13 } +{ "_id": 2, "sumOfSquares": 74 } +{ "_id": 3, "sumOfSquares": 500 } +``` + +### sqrt + +求平方根。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.sqrt([]) +``` +参数可以是任意解析为非负数字的表达式。 + + +#### 示例代码 + 假设直角三角形集合 `triangle` 有如下记录: + + +```js +{ "_id": 1, "x": 2, "y": 3 } +{ "_id": 2, "x": 5, "y": 7 } +{ "_id": 3, "x": 10, "y": 20 } +``` +假设 `x` 和 `y` 分别为两直角边,则求斜边长: + + +```js +const $ = db.command.aggregate +let res = await db.collection('triangle').aggregate() + .project({ + len: $.sqrt([$.add([$.pow(['$x', 2]), $.pow(['$y', 2])])]), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "len": 3.605551275463989 } +{ "_id": 2, "len": 8.602325267042627 } +{ "_id": 3, "len": 22.360679774997898 } +``` + +### subtract + +将两个数字相减然后返回差值,或将两个日期相减然后返回相差的毫秒数,或将一个日期减去一个数字返回结果的日期。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.subtract([, ]) +``` +参数可以是任意解析为数字或日期的表达式。 + + +#### 示例代码 + 假设集合 `scores` 有如下记录: + + +```js +{ "_id": 1, "max": 10, "min": 1 } +{ "_id": 2, "max": 7, "min": 5 } +{ "_id": 3, "max": 6, "min": 6 } +``` +求各个记录的 `max` 和 `min` 的差值。: + + +```js +const $ = db.command.aggregate +let res = await db.collection('scores').aggregate() + .project({ + diff: $.subtract(['$max', '$min']) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "diff": 9 } +{ "_id": 2, "diff": 2 } +{ "_id": 3, "diff": 0 } +``` + +### trunc + +将数字截断为整形。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.trunc() +``` +参数可以是任意解析为数字的表达式。 + + +#### 示例代码 + 假设集合 `scores` 有如下记录: + + +```js +{ "_id": 1, "value": 1.21 } +{ "_id": 2, "value": 3.83 } +{ "_id": 3, "value": -4.94 } +``` + +```js +const $ = db.command.aggregate +let res = await db.collection('scores').aggregate() + .project({ + int: $.trunc('$value') + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "value": 1 } +{ "_id": 2, "value": 3 } +{ "_id": 3, "value": -4 } +``` + +## 数组操作符 + +### arrayElemAt + +返回在指定数组下标的元素。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.arrayElemAt([, ]) +``` +`` 可以是任意解析为数组的表达式。 + + `` 可以是任意解析为整形的表达式。如果是正数,`arrayElemAt` 返回在 `index` 位置的元素,如果是负数,`arrayElemAt` 返回从数组尾部算起的 `index` 位置的元素。 + + +#### 示例代码 + 假设集合 `exams` 有如下记录: + + +```js +{ "_id": 1, "scores": [80, 60, 65, 90] } +{ "_id": 2, "scores": [78] } +{ "_id": 3, "scores": [95, 88, 92] } +``` +求各个第一次考试的分数和和最后一次的分数: + + +```js +const $ = db.command.aggregate +let res = await db.collection('exams').aggregate() + .project({ + first: $.arrayElemAt(['$scores', 0]), + last: $.arrayElemAt(['$scores', -1]), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "first": 80, "last": 90 } +{ "_id": 2, "first": 78, "last": 78 } +{ "_id": 3, "first": 95, "last": 92 } +``` + +### arrayToObject + +将一个数组转换为对象。 + + +#### API 说明 + 语法可以取两种: + + 第一种:传入一个二维数组,第二维的数组长度必须为 2,其第一个值为字段名,第二个值为字段值 + + +```js +db.command.aggregate.arrayToObject([ + [, ], + [, ], + ... +]) +``` +第二种:传入一个对象数组,各个对象必须包含字段 `k` 和 `v`,分别指定字段名和字段值 + + +```js +db.command.aggregate.arrayToObject([ + { "k": , "v": }, + { "k": , "v": }, + ... +]) +``` +传入 `arrayToObject` 的参数只要可以解析为上述两种表示法之一即可。 + + +#### 示例代码 + 假设集合 `shops` 有如下记录: + + +```js +{ "_id": 1, "sales": [ ["max", 100], ["min", 50] ] } +{ "_id": 2, "sales": [ ["max", 70], ["min", 60] ] } +{ "_id": 3, "sales": [ { "k": "max", "v": 50 }, { "k": "min", "v": 30 } ] } +``` +将数组转换为对象: + + +```js +const $ = db.command.aggregate +let res = await db.collection('shops').aggregate() + .project({ + sales: $.arrayToObject('$sales'), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "sales": { "max": 100, "min": 50 } } +{ "_id": 2, "sales": { "max": 70, "min": 60 } } +{ "_id": 3, "sales": { "max": 50, "min": 30 } } +``` + +### concatArrays + +将多个数组拼接成一个数组。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.concatArrays([ , , ... ]) +``` +参数可以是任意解析为数组的表达式。 + + +#### 示例代码 + 假设集合 `items` 有如下记录: + + +```js +{ "_id": 1, "fruits": [ "apple" ], "vegetables": [ "carrot" ] } +{ "_id": 2, "fruits": [ "orange", "lemon" ], "vegetables": [ "cabbage" ] } +{ "_id": 3, "fruits": [ "strawberry" ], "vegetables": [ "spinach" ] } +``` + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + list: $.concatArrays(['$fruits', '$vegetables']), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "list": [ "apple", "carrot" ] } +{ "_id": 2, "list": [ "orange", "lemon", "cabbage" ] } +{ "_id": 3, "list": [ "strawberry", "spinach" ] } +``` + +### filter + +根据给定条件返回满足条件的数组的子集。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.filter({ + input: , + as: , + cond: +}) +``` + +|字段 |说明 | +|---- |---- | +|input|一个可以解析为数组的表达式 | +|as |可选,用于表示数组各个元素的变量,默认为 this | +|cond |一个可以解析为布尔值的表达式,用于判断各个元素是否满足条件,各个元素的名字由 as 参数决定(参数名需加 $$ 前缀,如 $$this)| + +参数可以是任意解析为数组的表达式。 + + +#### 示例代码 + 假设集合 `fruits` 有如下记录: + + +```json +{ + "_id": 1, + "stock": [ + { "name": "apple", "price": 10 }, + { "name": "orange", "price": 20 } + ], +} +{ + "_id": 2, + "stock": [ + { "name": "lemon", "price": 15 }, + ], +} +``` + +```js +const _ = db.command +const $ = db.command.aggregate +let res = await db.collection('fruits').aggregate() + .project({ + stock: $.filter({ + input: '$stock', + as: 'item', + cond: $.gte(['$$item.price', 15]) + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "stock": [ { "name": "orange", "price": 20} ] } +{ "_id": 2, "stock": [ { "name": "lemon", "price": 15 } ] } +``` + +### in + +给定一个值和一个数组,如果值在数组中则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.in([, ]) +``` +`` 可以是任意表达式。 + + `` 可以是任意解析为数组的表达式。 + + +#### 示例代码 + 假设集合 `shops` 有如下记录: + + +```js +{ "_id": 1, "topsellers": ["bread", "ice cream", "butter"] } +{ "_id": 2, "topsellers": ["ice cream", "cheese", "yagurt"] } +{ "_id": 3, "topsellers": ["croissant", "cucumber", "coconut"] } +``` +标记销量最高的商品包含 `ice cream` 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + included: $.in(['ice cream', '$topsellers']) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "included": true } +{ "_id": 2, "included": true } +{ "_id": 3, "included": false } +``` + +### indexOfArray + +在数组中找出等于给定值的第一个元素的下标,如果找不到则返回 -1。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.indexOfArray([ , , , ]) +``` + +|字段 |类型 |说明 | +|---- |---- |---- | +|- |string |一个可以解析为数组的表达式,如果解析为 null,则 indexOfArray 返回 null | +|- |string |对数据各个元素应用的条件匹配表达式 | +|- |integer|可选,用于指定搜索的开始下标,必须是非负整数 | +|- |integer|可选,用于指定搜索的结束下标,必须是非负整数,指定了 时也应指定 ,否则 默认当做| + +参数可以是任意解析为数组的表达式。 + + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```json +{ + "_id": 1, + "sales": [ 1, 6, 2, 2, 5 ] +} +{ + "_id": 2, + "sales": [ 4, 2, 1, 5, 2 ] +} +{ + "_id": 3, + "sales": [ 2, 5, 3, 3, 1 ] +} +``` + +```js +const $ = db.command.aggregate +let res = await db.collection('stats').aggregate() + .project({ + index: $.indexOfArray(['$sales', 2, 2]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "index": 2 } +{ "_id": 2, "index": 4 } +{ "_id": 3, "index": -1 } +``` + +### isArray + +判断给定表达式是否是数组,返回布尔值。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.isArray() +``` +参数可以是任意表达式。 + + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ + "_id": 1, + "base": 10, + "sales": [ 1, 6, 2, 2, 5 ] +} +{ + "_id": 2, + "base": 1, + "sales": 100 +} +``` +计算总销量,如果 `sales` 是数字,则求 `sales * base`,如果 `sales` 是数组,则求数组元素之和与 `base` 的乘积。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('stats').aggregate() + .project({ + sum: $.cond({ + if: $.isArray('$sales'), + then: $.multiply([$.sum(['$sales']), '$base']), + else: $.multiply(['$sales', '$base']), + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "index": 160 } +{ "_id": 2, "index": 100 } +``` + +### map + +类似 JavaScript Array 上的 `map` 方法,将给定数组的每个元素按给定转换方法转换后得出新的数组。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.map({ + input: , + as: , + in: +}) +``` + +|字段 |说明 | +|---- |---- | +|input|一个可以解析为数组的表达式 | +|as |可选,用于表示数组各个元素的变量,默认为 this | +|in |一个可以应用在给定数组的各个元素上的表达式,各个元素的名字由 as 参数决定(参数名需加 $$ 前缀,如 $$this)| + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ + "_id": 1, + "sales": [ 1.32, 6.93, 2.48, 2.82, 5.74 ] +} +{ + "_id": 2, + "sales": [ 2.97, 7.13, 1.58, 6.37, 3.69 ] +} +``` +将各个数字截断为整形,然后求和 + + +```js +const $ = db.command.aggregate +let res = await db.collection('stats').aggregate() + .project({ + truncated: $.map({ + input: '$sales', + as: 'num', + in: $.trunc('$$num'), + }) + }) + .project({ + total: $.sum('$truncated') + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "index": 16 } +{ "_id": 2, "index": 19 } +``` + +### objectToArray + +将一个对象转换为数组。方法把对象的每个键值对都变成输出数组的一个元素,元素形如 `{ k: , v: }`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.objectToArray() +``` + +#### 示例代码 + 假设集合 `items` 有如下记录: + + +```js +{ "_id": 1, "attributes": { "color": "red", "price": 150 } } +{ "_id": 2, "attributes": { "color": "blue", "price": 50 } } +{ "_id": 3, "attributes": { "color": "yellow", "price": 10 } } +``` + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + array: $.objectToArray('$attributes') + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "array": [{ "k": "color", "v": "red" }, { "k": "price", "v": 150 }] } +{ "_id": 2, "array": [{ "k": "color", "v": "blue" }, { "k": "price", "v": 50 }] } +{ "_id": 3, "array": [{ "k": "color", "v": "yellow" }, { "k": "price", "v": 10 }] } +``` + +### range + +返回一组生成的序列数字。给定开始值、结束值、非零的步长,`range` 会返回从开始值开始逐步增长、步长为给定步长、但不包括结束值的序列。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.range([, , ]) +``` + +|字段 |说明 | +|---- |---- | +|start |开始值,一个可以解析为整形的表达式 | +|end |结束值,一个可以解析为整形的表达式 | +|non-zero step|可选,步长,一个可以解析为非零整形的表达式,默认为 1 | + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ "_id": 1, "max": 52 } +{ "_id": 2, "max": 38 } +``` + +```js +const $ = db.command.aggregate +db.collection('stats').aggregate() + .project({ + points: $.range([0, '$max', 10]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "points": [0, 10, 20, 30, 40, 50] } +{ "_id": 2, "points": [0, 10, 20, 30] } +``` + +### reduce + +类似 JavaScript 的 `reduce` 方法,应用一个表达式于数组各个元素然后归一成一个元素。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.reduce({ + input: + initialValue: , + in: +}) +``` + +|字段 |说明 | +|---- |---- | +|input |输入数组,可以是任意解析为数组的表达式 | +|initialValue |初始值 | +|in |用来作用于每个元素的表达式,在 in 中有两个可用变量,value 是表示累计值的变量,this 是表示当前数组元素的变量| + +#### 示例代码 + + +**简易字符串拼接** + + 假设集合 `player` 有如下记录: + + +```js +{ "_id": 1, "fullname": [ "Stephen", "Curry" ] } +{ "_id": 2, "fullname": [ "Klay", "Thompsom" ] } +``` +获取各个球员的全名,并加 `Player:` 前缀: + + +```js +const $ = db.command.aggregate +let res = await db.collection('player').aggregate() + .project({ + info: $.reduce({ + input: '$fullname', + initialValue: 'Player:', + in: $.concat(['$$value', ' ', '$$this']), + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "info": "Player: Stephen Curry" } +{ "_id": 2, "info": "Player: Klay Thompson" } +``` +获取各个球员的全名,不加前缀: + + +```js +const $ = db.command.aggregate +let res = await db.collection('player').aggregate() + .project({ + name: $.reduce({ + input: '$fullname', + initialValue: '', + in: $.concat([ + '$$value', + $.cond({ + if: $.eq(['$$value', '']), + then: '', + else: ' ', + }), + '$$this', + ]), + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "name": "Stephen Curry" } +{ "_id": 2, "name": "Klay Thompson" } +``` + +### reverseArray + +返回给定数组的倒序形式。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.reverseArray() +``` +参数可以是任意解析为数组表达式。 + + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ + "_id": 1, + "sales": [ 1, 2, 3, 4, 5 ] +} +``` +取 `sales` 倒序: + + +```js +const $ = db.command.aggregate +let res = await db.collection('stats').aggregate() + .project({ + reversed: $.reverseArray('$sales'), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "reversed": [5, 4, 3, 2, 1] } +``` + +### size + +返回数组长度。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.size() +``` +`` 可以是任意解析为数组的表达式。 + + +#### 示例代码 + 假设集合 `shops` 有如下记录: + + +```js +{ "_id": 1, "staff": [ "John", "Middleton", "George" ] } +{ "_id": 2, "staff": [ "Steph", "Jack" ] } +``` +计算各个商店的雇员数量: + + +```js +const $ = db.command.aggregate +let res = await db.collection('shops').aggregate() + .project({ + totalStaff: $.size('$staff') + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "totalStaff": 3 } +{ "_id": 2, "totalStaff": 2 } +``` + +### slice + +类似 JavaScritp 的 `slice` 方法。返回给定数组的指定子集。 + + +#### API 说明 + 语法有两种: + + 返回从开头或结尾开始的 `n` 个元素: + + +```js +db.command.aggregate.slice([, ]) +``` +返回从指定位置算作数组开头、再向后或向前的 `n` 个元素: + + +```js +db.command.aggregate.slice([, , ]) +``` +`` 可以是任意解析为数组的表达式。 + + `` 可以是任意解析为整形的表达式。如果是正数,则将数组的第 `` 个元素作为数组开始;如果 `` 比数组长度更长,`slice` 返回空数组。如果是负数,则将数组倒数第 `` 个元素作为数组开始;如果 `` 的绝对值大于数组长度,则开始位置即为数组开始位置。 + + `` 可以是任意解析为整形的表达式。如果 `` 有提供,则 `` 必须为正整数。如果是正数,`slice` 返回前 `n` 个元素。如果是负数,`slice` 返回后 `n` 个元素。 + + +#### 示例代码 + 假设集合 `people` 有如下记录: + + +```js +{ "_id": 1, "hobbies": [ "basketball", "football", "tennis", "badminton" ] } +{ "_id": 2, "hobbies": [ "golf", "handball" ] } +{ "_id": 3, "hobbies": [ "table tennis", "swimming", "rowing" ] } +``` +统一返回前两个爱好: + + +```js +const $ = db.command.aggregate +let res = await db.collection('fruits').aggregate() + .project({ + hobbies: $.slice(['$hobbies', 2]), + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "hobbies": [ "basketball", "football" ] } +{ "_id": 2, "hobbies": [ "golf", "handball" ] } +{ "_id": 3, "hobbies": [ "table tennis", "swimming" ] } +``` + +### zip + +把二维数组的第二维数组中的相同序号的元素分别拼装成一个新的数组进而组装成一个新的二维数组。如可将 `[ [ 1, 2, 3 ], [ "a", "b", "c" ] ]` 转换成 `[ [ 1, "a" ], [ 2, "b" ], [ 3, "c" ] ]`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.zip({ + inputs: [, , ...], + useLongestLength: , + defaults: +}) +``` +`inputs` 是一个二维数组(`inputs` 不可以是字段引用),其中每个元素的表达式(这个可以是字段引用)都可以解析为数组。如果其中任意一个表达式返回 `null`,`` 也返回 `null`。如果其中任意一个表达式不是指向一个合法的字段 / 解析为数组 / 解析为 `null`,则返回错误。 + + `useLongestLength` 决定输出数组的长度是否采用输入数组中的最长数组的长度。默认为 `false`,即输入数组中的最短的数组的长度即是输出数组的各个元素的长度。 + + `defaults` 是一个数组,用于指定在输入数组长度不一的情况下时采用的数组各元素默认值。指定这个字段则必须指定 `useLongestLength`,否则返回错误。如果 `useLongestLength` 是 `true` 但是 `defaults` 是空或没有指定,则 `zip` 用 `null` 做数组元素的缺省默认值。指定各元素默认值时 `defaults` 数组的长度必须是输入数组最大的长度。 + + +#### 示例代码 + 假设集合 `stats` 有如下记录: + + +```js +{ "_id": 1, "zip1": [1, 2], "zip2": [3, 4], "zip3": [5, 6] ] } +{ "_id": 2, "zip1": [1, 2], "zip2": [3], "zip3": [4, 5, 6] ] } +{ "_id": 3, "zip1": [1, 2], "zip2": [3] ] } +``` + + +**只传 inputs** + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + zip: $.zip({ + inputs: [ + '$zip1', // 字段引用 + '$zip2', + '$zip3', + ], + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } +{ "_id": 2, "zip": [ [1, 3, 4] ] } +{ "_id": 3, "zip": null } +``` + + +**设置 useLongestLength** + + 如果设 `useLongestLength` 为 `true`: + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + zip: $.zip({ + inputs: [ + '$zip1', // 字段引用 + '$zip2', + '$zip3', + ], + useLongestLength: true, + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } +{ "_id": 2, "zip": [ [1, 3, 4], [2, null, 5], [null, null, 6] ] } +{ "_id": 3, "zip": null } +``` + + +**设置 defaults** + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + zip: $.zip({ + inputs: [ + '$zip1', // 字段引用 + '$zip2', + '$zip3', + ], + useLongestLength: true, + defaults: [-300, -200, -100], + }) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } +{ "_id": 2, "zip": [ [1, 3, 4], [2, -200, 5], [-300, -200, 6] ] } +{ "_id": 3, "zip": null } +``` + +## 布尔操作符 + +### and + +给定多个表达式,`and` 仅在所有表达式都返回 `true` 时返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.and([, , ...]) +``` +如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 + + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "min": 10, "max": 100 } +{ "_id": 2, "min": 60, "max": 80 } +{ "_id": 3, "min": 30, "max": 50 } +``` +求 `min` 大于等于 30 且 `max` 小于等于 80 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + fullfilled: $.and([$.gte(['$min', 30]), $.lte(['$max', 80])]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "fullfilled": false } +{ "_id": 2, "fullfilled": true } +{ "_id": 3, "fullfilled": true } +``` + +### not + +给定一个表达式,如果表达式返回 `true`,则 `not` 返回 `false`,否则返回 `true`。注意表达式不能为逻辑表达式(`and`、`or`、`nor`、`not`)。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.not() +``` +如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 + + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "min": 10, "max": 100 } +{ "_id": 2, "min": 60, "max": 80 } +{ "_id": 3, "min": 30, "max": 50 } +``` +求 `min` 不大于 40 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + fullfilled: $.not($.gt(['$min', 40])) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "fullfilled": true } +{ "_id": 2, "fullfilled": false } +{ "_id": 3, "fullfilled": true } +``` + +### or + +给定多个表达式,如果任意一个表达式返回 `true`,则 `or` 返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.or([, , ...]) +``` +如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 + + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "min": 10, "max": 100 } +{ "_id": 2, "min": 60, "max": 80 } +{ "_id": 3, "min": 30, "max": 50 } +``` +求 `min` 小于 40 且 `max` 大于 60 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + fullfilled: $.or([$.lt(['$min', 30]), $.gt(['$max', 60])]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "fullfilled": true } +{ "_id": 2, "fullfilled": false } +{ "_id": 3, "fullfilled": true } +``` + +## 比较操作符 + +### cmp + +给定两个值,返回其比较值: + + +#### API 说明 + 如果第一个值小于第二个值,返回 -1 +如果第一个值大于第二个值,返回 1 +如果两个值相等,返回 0 + + 语法如下: + + +```js +db.command.aggregate.cmp([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "shop1": 10, "shop2": 100 } +{ "_id": 2, "shop1": 80, "shop2": 20 } +{ "_id": 3, "shop1": 50, "shop2": 50 } +``` +求 `shop1` 和 `shop2` 的各个物品的价格对比。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + compare: $.cmp(['$shop1', '$shop2'])) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "compare": -1 } +{ "_id": 2, "compare": 1 } +{ "_id": 3, "compare": 0 } +``` + +### eq + +匹配两个值,如果相等则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.eq([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +求 `value` 等于 50 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + matched: $.eq(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": false } +{ "_id": 2, "matched": false } +{ "_id": 3, "matched": true } +``` + +### gt + +匹配两个值,如果前者大于后者则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.gt([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +判断 `value` 是否大于 50。 + + +```js +const $ = db.command.aggregate +db.collection('price').aggregate() + .project({ + matched: $.gt(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": false } +{ "_id": 2, "matched": true } +{ "_id": 3, "matched": false } +``` + +### gte + +匹配两个值,如果前者大于或等于后者则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.gte([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +判断 `value` 是否大于或等于 50。 + + +```js +const $ = db.command.aggregate +let res = await b.collection('price').aggregate() + .project({ + matched: $.gte(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": false } +{ "_id": 2, "matched": true } +{ "_id": 3, "matched": true } +``` + +### lt + +匹配两个值,如果前者小于后者则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.lt([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +``` +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +判断 `value` 是否小于 50。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + matched: $.lt(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": true } +{ "_id": 2, "matched": false } +{ "_id": 3, "matched": false } +``` + +### lte + +匹配两个值,如果前者小于或等于后者则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.lte([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +判断 `value` 是否小于 50。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + matched: $.lte(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": true } +{ "_id": 2, "matched": false } +{ "_id": 3, "matched": true } +``` + +### neq + +匹配两个值,如果不相等则返回 `true`,否则返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.neq([, ]) +``` + +#### 示例代码 + 假设集合 `price` 有如下记录: + + +```js +{ "_id": 1, "value": 10 } +{ "_id": 2, "value": 80 } +{ "_id": 3, "value": 50 } +``` +求 `value` 不等于 50 的记录。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('price').aggregate() + .project({ + matched: $.neq(['$value', 50]) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "matched": true } +{ "_id": 2, "matched": true } +{ "_id": 3, "matched": false } +``` + +## 条件操作符 + +### cond + +计算布尔表达式,返回指定的两个值其中之一。 + + +#### API 说明 + `cond` 的使用形式如下: + + +```js +cond({ if: <布尔表达式>, then: <真值>, else: <假值> }) +``` +或者: + + +```js +cond([ <布尔表达式>, <真值>, <假值> ]) +``` +两种形式中,三个参数(`if`、`then`、`else`)都是必须的。 + + 如果布尔表达式为真,那么 `$cond` 将会返回 `<真值>`,否则会返回 `<假值>` + + +#### 示例代码 + 假设集合 `items` 的记录如下: + + +```js +{ "_id": "0", "name": "item-a", "amount": 100 } +{ "_id": "1", "name": "item-b", "amount": 200 } +{ "_id": "2", "name": "item-c", "amount": 300 } +``` +我们可以使用 `cond`,根据 `amount` 字段,来生成新的字段 `discount`: + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + name: 1, + discount: $.cond({ + if: $.gte(['$amount', 200]), + then: 0.7, + else: 0.9 + }) + }) + .end() +``` +输出如下: + + +```js +{ "_id": "0", "name": "item-a", "discount": 0.9 } +{ "_id": "1", "name": "item-b", "discount": 0.7 } +{ "_id": "2", "name": "item-c", "discount": 0.7 } +``` + +### ifNull + +计算给定的表达式,如果表达式结果为 null、undefined 或者不存在,那么返回一个替代值;否则返回原值。 + + +#### API 说明 + `ifNull` 的使用形式如下: + + +```js +ifNull([ <表达式>, <替代值> ]) +``` + +#### 示例代码 + 假设集合 `items` 的记录如下: + + +```js +{ "_id": "0", "name": "A", "description": "这是商品A" } +{ "_id": "1", "name": "B", "description": null } +{ "_id": "2", "name": "C" } +``` +我们可以使用 `ifNull`,对不存在 `desc` 字段的文档,或者 `desc` 字段为 `null` 的文档,补充一个替代值。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + _id: 0, + name: 1, + description: $.ifNull(['$description', '商品描述空缺']) + }) + .end() +``` +输出如下: + + +```js +{ "name": "A", "description": "这是商品A" } +{ "name": "B", "description": "商品描述空缺" } +{ "name": "C", "description": "商品描述空缺" } +``` + +### switch + +根据给定的 `switch-case-default` 计算返回值、 + + +#### API 说明 + `switch` 的使用形式如下: + + +```js +switch({ + branches: [ + case: <表达式>, then: <表达式>, + case: <表达式>, then: <表达式>, + ... + ], + default: <表达式> +}) +``` + +#### 示例代码 + 假设集合 `items` 的记录如下: + + +```js +{ "_id": "0", "name": "item-a", "amount": 100 } +{ "_id": "1", "name": "item-b", "amount": 200 } +{ "_id": "2", "name": "item-c", "amount": 300 } +``` +我们可以使用 `switch`,根据 `amount` 字段,来生成新的字段 `discount`: + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + name: 1, + discount: $.switch({ + branches: [ + { case: $.gt(['$amount', 250]), then: 0.8 }, + { case: $.gt(['$amount', 150]), then: 0.9 } + ], + default: 1 + }) + }) + .end() +``` +输出如下: + + +```js +{ "_id": "0", "name": "item-a", "discount": 1 } +{ "_id": "1", "name": "item-b", "discount": 0.9 } +{ "_id": "2", "name": "item-c", "discount": 0.8 } +``` + +## 日期操作符 + +**注意** + +- 以下日期操作符中`timezone`均支持以下几种形式 + +```js +timezone: "Asia/Shanghai" // Asia/Shanghai时区 +timezone: "+08" // utc+8时区 +timezone: "+08:30" // 时区偏移8小时30分 +timezone: "+0830" // 时区偏移8小时30分,同上 +``` + +### dateFromParts + +给定日期的相关信息,构建并返回一个日期对象。 + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.dateFromParts({ + year: , + month: , + day: , + hour: , + minute: , + second: , + millisecond: , + timezone: +}) +``` +你也可以使用 ISO 8601 的标准: + + +```js +db.command.aggregate.dateFromParts({ + isoWeekYear: , + isoWeek: , + isoDayOfWeek: , + hour: , + minute: , + second: , + millisecond: , + timezone: +}) +``` + +#### 示例代码 + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + date: $.dateFromParts({ + year: 2017, + month: 2, + day: 8, + hour: 12, + timezone: 'America/New_York' + }), + }) + .end() +``` +输出如下: + + +```js +{ + "date": ISODate("2017-02-08T17:00:00.000Z") +} +``` + +### dateFromString + +将一个日期/时间字符串转换为日期对象 + +#### API 说明 + 语法如下: + + +```js +db.command.aggregate.dateFromString({ + dateString: , + timezone: +}) +``` + +#### 示例代码 + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + date: $.dateFromString({ + dateString: "2019-05-14T09:38:51.686Z" + }) + }) + .end() +``` +输出如下: + + +```js +{ + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +### dateToString + +根据指定的表达式将日期对象格式化为符合要求的字符串。 + +#### API 说明 + `dateToString` 的调用形式如下: + + +```js +db.command.aggregate.dateToString({ + date: <日期表达式>, + format: <格式化表达式>, + timezone: <时区表达式>, + onNull: <空值表达式> +}) +``` +下面是四种表达式的详细说明: + +|名称 |描述 | +|---- |---- | +|日期表达式 |必选。指定字段值应该是能转化为字符串的日期。 | +|格式化表达式 |可选。它可以是任何包含“格式说明符”的有效字符串。 | +|时区表达式 |可选。指明运算结果的时区。它可以解析格式为 [UTC Offset](https://en.wikipedia.org/wiki/List_of_UTC_time_offsets) 或者 [Olson Timezone Identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) 的字符串。| +|空值表达式 |可选。当 <日期表达式> 返回空或者不存在的时候,会返回此表达式指明的值。 | + +下面是格式说明符的详细说明: + +|说明符 |描述 |合法值 | +|---- |---- |---- | +|%d |月份的日期(2位数,0填充) |01 - 31 | +|%G |ISO 8601 格式的年份 |0000 - 9999| +|%H |小时(2位数,0填充,24小时制) |00 - 23 | +|%j |一年中的一天(3位数,0填充) |001 - 366 | +|%L |毫秒(3位数,0填充) |000 - 999 | +|%m |月份(2位数,0填充) |01 - 12 | +|%M |分钟(2位数,0填充) |00 - 59 | +|%S |秒(2位数,0填充) |00 - 60 | +|%w |星期几 |1 - 7 | +|%u |ISO 8601 格式的星期几 |1 - 7 | +|%U |一年中的一周(2位数,0填充) |00 - 53 | +|%V |ISO 8601 格式的一年中的一周 |1 - 53 | +|%Y |年份(4位数,0填充) |0000 - 9999| +|%z |与 UTC 的时区偏移量 |+/-[hh][mm]| +|%Z |以分钟为单位,与 UTC 的时区偏移量|+/-mmm | +|%% |百分号作为字符 |% | + +#### 示例代码 + 假设集合 `students` 有如下记录: + + +```js +{ "date": "1999-12-11T16:00:00.000Z", "firstName": "Yuanxin", "lastName": "Dong" } +{ "date": "1998-11-10T16:00:00.000Z", "firstName": "Weijia", "lastName": "Wang" } +{ "date": "1997-10-09T16:00:00.000Z", "firstName": "Chengxi", "lastName": "Li" } +``` + + +**格式化日期** + + 下面是将 `date` 字段的值,格式化成形如 `年份-月份-日期` 的字符串: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + formatDate: $.dateToString({ + date: '$date', + format: '%Y-%m-%d' + }) + }) + .end() +``` +返回的结果如下: + + +```js +{ "formatDate": "1999-12-11" } +{ "formatDate": "1998-11-10" } +{ "formatDate": "1997-10-09" } +``` + + +**时区时间** + + 下面是将 `date` 字段值格式化为上海时区时间的例子: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + formatDate: $.dateToString({ + date: '$date', + format: '%H:%M:%S', + timezone: 'Asia/Shanghai' + }) + }) + .end() +``` +返回的结果如下: + + +```js +{ "formatDate": "00:00:00" } +{ "formatDate": "00:00:00" } +{ "formatDate": "00:00:00" } +``` + + +**缺失情况的默认值** + + 当指定的 `<日期表达式>` 返回空或者不存在的时候,可以设置缺失情况下的默认值: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + formatDate: $.dateToString({ + date: '$empty', + onNull: 'null' + }) + }) + .end() +``` +返回的结果如下: + + +```js +{ "formatDate": "null" } +{ "formatDate": "null" } +{ "formatDate": "null" } +``` + +### dayOfMonth + +返回日期字段对应的天数(一个月中的哪一天),是一个介于 1 至 31 之间的数字。 + + +#### API 说明 + +该接口有以下两种用法,语法如下: + + +```js +db.command.aggregate.dayOfMonth(<日期字段>) + +db.command.aggregate.dayOfMonth({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + 假设集合 `dates` 有以下文档: + + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` +我们使用 `dayOfMonth()` 对 `date` 字段进行投影,获取对应的日期: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + dayOfMonth: $.dayOfMonth('$date') + }) + .end() +``` +输出如下: + + +```js +{ + "dayOfMonth": 14 +} +``` + +### dayOfWeek + +返回日期字段对应的天数(一周中的第几天),是一个介于 1(周日)到 7(周六)之间的整数。 + + +#### API 说明 + +**注意:周日是每周的第 1 天** + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.dayOfWeek(<日期字段>) + +db.command.aggregate.dayOfWeek({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + 假设集合 `dates` 有以下文档: + + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` +我们使用 `dayOfWeek()` 对 `date` 字段进行投影,获取对应的天数(一周中的第几天): + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + dayOfWeek: $.dayOfWeek('$date') + }) + .end() +``` +输出如下: + + +```js +{ + "dayOfWeek": 3 +} +``` + +### dayOfYear + +返回日期字段对应的天数(一年中的第几天),是一个介于 1 到 366 之间的整数。 + + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.dayOfYear(<日期字段>) + +db.command.aggregate.dayOfYear({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + 假设集合 `dates` 有以下文档: + + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` +我们使用 `dayOfYear()` 对 `date` 字段进行投影,获取对应的天数(一年中的第几天): + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + dayOfYear: $.dayOfYear('$date') + }) + .end() +``` +输出如下: + + +```js +{ + "dayOfYear": 134 +} +``` + +### hour + +返回日期字段对应的小时数,是一个介于 0 到 23 之间的整数。 + + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.hour(<日期字段>) + +db.command.aggregate.hour({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + 假设集合 `dates` 有以下文档: + + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` +我们使用 `hour()` 对 `date` 字段进行投影,获取对应的小时数: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + hour: $.hour('$date') + }) + .end() +``` +输出如下: + + +```js +{ + "hour": 9 +} +``` + +### isoDayOfWeek + +返回日期字段对应的 ISO 8601 标准的天数(一周中的第几天),是一个介于 1(周一)到 7(周日)之间的整数。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.isoDayOfWeek(<日期字段>) + +db.command.aggregate.isoDayOfWeek({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` +我们使用 `isoDayOfWeek()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的天数(一周中的第几天): + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + isoDayOfWeek: $.isoDayOfWeek('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "isoDayOfWeek": 2 +} +``` + +### isoWeek + +返回日期字段对应的 ISO 8601 标准的周数(一年中的第几周),是一个介于 1 到 53 之间的整数。 + + +#### API 说明 + +根据 ISO 8601 标准,周一到周日视为一周,本年度第一个周四所在的那周,视为本年度的第 1 周。 + +例如:2016 年 1 月 7 日是那年的第一个周四,那么 2016.01.04(周一)到 2016.01.10(周日) 即为第 1 周。同理,2016 年 1 月 1 日的周数为 53。 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.isoWeek(<日期字段>) + +db.command.aggregate.isoWeek({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `isoWeek()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的周数(一年中的第几周): + + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + isoWeek: $.isoWeek('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "isoWeek": 20 +} +``` + +### isoWeekYear + +返回日期字段对应的 ISO 8601 标准的天数(一年中的第几天)。 + +#### API 说明 + +此处的“年”以第一周的周一为开始,以最后一周的周日为结束。 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.isoWeekYear(<日期字段>) + +db.command.aggregate.isoWeekYear({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `isoWeekYear()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的天数(一年中的第几天): + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + isoWeekYear: $.isoWeekYear('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "isoWeekYear": 2019 +} +``` + +### millisecond + +返回日期字段对应的毫秒数,是一个介于 0 到 999 之间的整数。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.millisecond(<日期字段>) + +db.command.aggregate.millisecond({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `millisecond()` 对 `date` 字段进行投影,获取对应的毫秒数: + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + millisecond: $.millisecond('$date'), + }) + .end() +``` + +输出如下: + +```js +{ + "millisecond": 686 +} +``` + +### minute + +返回日期字段对应的分钟数,是一个介于 0 到 59 之间的整数。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.minute(<日期字段>) + +db.command.aggregate.minute({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `minute()` 对 `date` 字段进行投影,获取对应的分钟数: + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + minute: $.minute('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "minute": 38 +} +``` + +### month + +返回日期字段对应的月份,是一个介于 1 到 12 之间的整数。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.month(<日期字段>) + +db.command.aggregate.month({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + 假设集合 `dates` 有以下文档: + + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `month()` 对 `date` 字段进行投影,获取对应的月份: + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + month: $.month('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "month": 5 +} +``` + +### second + +返回日期字段对应的秒数,是一个介于 0 到 59 之间的整数,在特殊情况下(闰秒)可能等于 60。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.second(<日期字段>) + +db.command.aggregate.second({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `second()` 对 `date` 字段进行投影,获取对应的秒数: + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + second: $.second('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "second": 51 +} +``` + +### week + +返回日期字段对应的周数(一年中的第几周),是一个介于 0 到 53 之间的整数。 + +#### API 说明 + +每周以周日为开头,**每年的第一个周日**即为 `week 1` 的开始,这天之前是 `week 0`。 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.week(<日期字段>) + +db.command.aggregate.week({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `week()` 对 `date` 字段进行投影,获取对应的周数(一年中的第几周): + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + week: $.week('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "week": 19 +} +``` + +### year + +返回日期字段对应的年份。 + +#### API 说明 + +该接口有以下两种用法,语法如下: + +```js +db.command.aggregate.year(<日期字段>) + +db.command.aggregate.year({date:<日期字段>,timezone:<时区>}) +``` + +#### 示例代码 + +假设集合 `dates` 有以下文档: + +```js +{ + "_id": 1, + "date": ISODate("2019-05-14T09:38:51.686Z") +} +``` + +我们使用 `year()` 对 `date` 字段进行投影,获取对应的年份: + +```js +const $ = db.command.aggregate +let res = await db + .collection('dates') + .aggregate() + .project({ + _id: 0, + year: $.year('$date') + }) + .end() +``` + +输出如下: + +```js +{ + "year": 2019 +} +``` + +### subtract + +见[subtract](#subtract) + +## 常量操作符 + +### literal + +直接返回一个值的字面量,不经过任何解析和处理。 + + +#### API 说明 + `literal` 使用形式如下: + + +```js +literal(<值>) +``` +如果 `<值>` 是一个表达式,那么 `literal` **不会**解析或者计算这个表达式,而是直接返回这个表达式。 + + +#### 示例代码 + 比如我们有一个 `items` 集合,其中数据如下: + + +```js +{ "_id": "0", "price": "$1" } +{ "_id": "1", "price": "$5.60" } +{ "_id": "2", "price": "$8.90" } +``` + + +**以字面量的形式使用 $** + + 下面的代码使用 `literal`,生成了一个新的字段 `isOneDollar`,表示 `price` 字段是否严格等于 `"$1"`。 + + 注意:我们这里无法使用 `eq(['$price', '$1'])`,因为 `"$1"` 是一个表达式,代表 `"1"` 字段对应的值,而不是字符串字面量 `"$1"`。 + + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .project({ + isOneDollar: $.eq(['$price', $.literal('$1')]) + }) + .end() +``` +输出如下: + + +```js +{ "_id": "0", "isOneDollar": true } +{ "_id": "1", "isOneDollar": false } +{ "_id": "2", "isOneDollar": false } +``` + + +**投影一个字段,对应的值为 1** + + 下面的代码使用 `literal`,投影了一个新的字段 `amount`,其值为 `1`。 + + +```js +const $ = db.command.aggregate +db.collection('items').aggregate() + .project({ + price: 1, + amount: $.literal(1) + }) + .end() +``` +输出如下: + + +```js +{ "_id": "0", "price": "$1", "amount": 1 } +{ "_id": "1", "price": "$5.60", "amount": 1 } +{ "_id": "2", "price": "$8.90", "amount": 1 } +``` + +## 对象操作符 + +### mergeObjects + +将多个文档合并为单个文档。 + +#### API 说明 + 使用形式如下: +在 `group()` 中使用时: + + +```js +mergeObjects() +``` +在其它表达式中使用时: + + +```js +mergeObjects([, , ...]) +``` + +#### 示例代码 + + +**搭配 `group()` 使用** + + 假设集合 `sales` 存在以下文档: + + +```js +{ "_id": 1, "year": 2018, "name": "A", "volume": { "2018Q1": 500, "2018Q2": 500 } } +{ "_id": 2, "year": 2017, "name": "A", "volume": { "2017Q1": 400, "2017Q2": 300, "2017Q3": 0, "2017Q4": 0 } } +{ "_id": 3, "year": 2018, "name": "B", "volume": { "2018Q1": 100 } } +{ "_id": 4, "year": 2017, "name": "B", "volume": { "2017Q3": 100, "2017Q4": 250 } } +``` +下面的代码使用 `mergeObjects()`,将用相同 `name` 的文档合并: + + +```js +const $ = db.command.aggregate +let res = await db.collection('sales').aggregate() + .group({ + _id: '$name', + mergedVolume: $.mergeObjects('$volume') + }) + .end() +``` +输出如下: + + +```js +{ "_id": "A", "mergedVolume": { "2017Q1": 400, "2017Q2": 300, "2017Q3": 0, "2017Q4": 0, "2018Q1": 500, "2018Q2": 500 } } +{ "_id": "B", "mergedVolume": { "2017Q3": 100, "2017Q4": 250, "2018Q1": 100 } } +``` + + +**一般用法** + +假设集合 `test` 存在以下文档: + + +```js +{ "_id": 1, "foo": { "a": 1 }, "bar": { "b": 2 } } +{ "_id": 2, "foo": { "c": 1 }, "bar": { "d": 2 } } +{ "_id": 3, "foo": { "e": 1 }, "bar": { "f": 2 } } +``` +下面的代码使用 `mergeObjects()`,将文档中的 `foo` 和 `bar` 字段合并为 `foobar`: + + +```js +const $ = db.command.aggregate +let res = await db.collection('sales').aggregate() + .project({ + foobar: $.mergeObjects(['$foo', '$bar']) + }) + .end() +``` +输出结果如下: + + +```js +{ "_id": 1, "foobar": { "a": 1, "b": 2 } } +{ "_id": 2, "foobar": { "c": 1, "d": 2 } } +{ "_id": 3, "foobar": { "e": 1, "f": 2 } } +``` + +### objectToArray + +见[objectToArray](#objectToArray) + +## 集合操作符 + +### allElementsTrue + +输入一个数组,或者数组字段的表达式。如果数组中所有元素均为真值,那么返回 `true`,否则返回 `false`。空数组永远返回 `true`。 + +#### API 说明 + +语法如下: + +```js +allElementsTrue([]) +``` + +#### 示例代码 + +假设集合 `test` 有如下记录: + +```js +{ "_id": 1, "array": [ true ] } +{ "_id": 2, "array": [ ] } +{ "_id": 3, "array": [ false ] } +{ "_id": 4, "array": [ true, false ] } +{ "_id": 5, "array": [ 0 ] } +{ "_id": 6, "array": [ "stark" ] } +``` +下面的代码使用 `allElementsTrue()`,判断 `array` 字段中是否均为真值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('price') + .aggregate() + .project({ + isAllTrue: $.allElementsTrue(['$array']) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "isAllTrue": true } +{ "_id": 2, "isAllTrue": true } +{ "_id": 3, "isAllTrue": false } +{ "_id": 4, "isAllTrue": false } +{ "_id": 5, "isAllTrue": false } +{ "_id": 6, "isAllTrue": true } +``` + +### anyElementTrue + +输入一个数组,或者数组字段的表达式。如果数组中任意一个元素为真值,那么返回 `true`,否则返回 `false`。空数组永远返回 `false`。 + + +#### API 说明 + 语法如下: + + +```js +anyElementTrue([]) +``` + +#### 示例代码 + 假设集合 `test` 有如下记录: + + +```js +{ "_id": 1, "array": [ true ] } +{ "_id": 2, "array": [ ] } +{ "_id": 3, "array": [ false ] } +{ "_id": 4, "array": [ true, false ] } +{ "_id": 5, "array": [ 0 ] } +{ "_id": 6, "array": [ "stark" ] } +``` +下面的代码使用 `anyElementTrue()`,判断 `array` 字段中是否含有真值: + + +```js +const $ = db.command.aggregate +let res = await db.collection('price') + .aggregate() + .project({ + isAnyTrue: $.anyElementTrue(['$array']) + }) + .end() +``` +返回结果如下: + + +```js +{ "_id": 1, "isAnyTrue": true } +{ "_id": 2, "isAnyTrue": false } +{ "_id": 3, "isAnyTrue": false } +{ "_id": 4, "isAnyTrue": true } +{ "_id": 5, "isAnyTrue": false } +{ "_id": 6, "isAnyTrue": true } +``` + +### setDifference + +输入两个集合,输出只存在于第一个集合中的元素。 + + +#### API 说明 + 使用形式如下: + + +```js +setDifference([, ]) +``` + +#### 示例代码 + 假设集合 `test` 存在以下数据: + + +```js +{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } +{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } +{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } +{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } +{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } +{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } +{ "_id": 7, "A": [ ], "B": [ ] } +{ "_id": 8, "A": [ ], "B": [ 1 ] } +``` +下面的代码使用 `setDifference`,找到只存在于 `B` 中的数字: + + +```js +let res = await db.collection('test') + .aggregate() + .project({ + isBOnly: $.setDifference(['$B', '$A']) + }) + .end() +``` + +```js +{ "_id": 1, "isBOnly": [] } +{ "_id": 2, "isBOnly": [] } +{ "_id": 3, "isBOnly": [3] } +{ "_id": 4, "isBOnly": [3] } +{ "_id": 5, "isBOnly": [] } +{ "_id": 6, "isBOnly": [{}, []] } +{ "_id": 7, "isBOnly": [] } +{ "_id": 8, "isBOnly": [1] } +``` + +### setEquals + +输入两个集合,判断两个集合中包含的元素是否相同(不考虑顺序、去重)。 + + +#### API 说明 + 使用形式如下: + + +```js +setEquals([, ]) +``` + +#### 示例代码 + 假设集合 `test` 存在以下数据: + + +```js +{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } +{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } +{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } +{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } +{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } +{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } +{ "_id": 7, "A": [ ], "B": [ ] } +{ "_id": 8, "A": [ ], "B": [ 1 ] } +``` +下面的代码使用 `setEquals`,判断两个集合中包含的元素是否相同: + + +```js +let res = await db.collection('test') + .aggregate() + .project({ + sameElements: $.setEquals(['$A', '$B']) + }) + .end() +``` + +```js +{ "_id": 1, "sameElements": true } +{ "_id": 2, "sameElements": true } +{ "_id": 3, "sameElements": false } +{ "_id": 4, "sameElements": false } +{ "_id": 5, "sameElements": false } +{ "_id": 6, "sameElements": false } +{ "_id": 7, "sameElements": true } +{ "_id": 8, "sameElements": false } +``` + +### setIntersection + +输入两个集合,输出两个集合的交集。 + + +#### API 说明 + 使用形式如下: + + +```js +setIntersection([, ]) +``` + +#### 示例代码 + 假设集合 `test` 存在以下数据: + + +```js +{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } +{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } +{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } +{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } +{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } +{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } +{ "_id": 7, "A": [ ], "B": [ ] } +{ "_id": 8, "A": [ ], "B": [ 1 ] } +``` +下面的代码使用 `setIntersection`,输出两个集合的交集: + + +```js +let res = await db.collection('test') + .aggregate() + .project({ + commonToBoth: $.setIntersection(['$A', '$B']) + }) + .end() +``` +输出如下: + + +```js +{ "_id": 1, "commonToBoth": [ 1, 2 ] } +{ "_id": 2, "commonToBoth": [ 1, 2 ] } +{ "_id": 3, "commonToBoth": [ 1, 2 ] } +{ "_id": 4, "commonToBoth": [ 1 ] } +{ "_id": 5, "commonToBoth": [ ] } +{ "_id": 6, "commonToBoth": [ ] } +{ "_id": 7, "commonToBoth": [ ] } +{ "_id": 8, "commonToBoth": [ ] } +``` + +### setIsSubset + +输入两个集合,判断第一个集合是否是第二个集合的子集。 + + +#### API 说明 + 使用形式如下: + + +```js +setIsSubset([, ]) +``` + +#### 示例代码 + 假设集合 `test` 存在以下数据: + + +```js +{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } +{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } +{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } +{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } +{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } +{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } +{ "_id": 7, "A": [ ], "B": [ ] } +{ "_id": 8, "A": [ ], "B": [ 1 ] } +``` +下面的代码使用 `setIsSubset`,判断第一个集合是否是第二个集合的子集: + + +```js +let res = await db.collection('test') + .aggregate() + .project({ + AisSubsetOfB: $.setIsSubset(['$A', '$B']) + }) + .end() +``` + +```js +{ "_id": 1, "AisSubsetOfB": true } +{ "_id": 2, "AisSubsetOfB": true } +{ "_id": 3, "AisSubsetOfB": true } +{ "_id": 4, "AisSubsetOfB": false } +{ "_id": 5, "AisSubsetOfB": false } +{ "_id": 6, "AisSubsetOfB": false } +{ "_id": 7, "AisSubsetOfB": true } +{ "_id": 8, "AisSubsetOfB": true } +``` + +### setUnion + +输入两个集合,输出两个集合的并集。 + + +#### API 说明 + 使用形式如下: + + +```js +setUnion([, ]) +``` + +#### 示例代码 + 假设集合 `test` 存在以下数据: + + +```js +{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } +{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } +{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } +{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } +{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } +{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } +{ "_id": 7, "A": [ ], "B": [ ] } +{ "_id": 8, "A": [ ], "B": [ 1 ] } +``` +下面的代码使用 `setUnion`,输出两个集合的并集: + + +```js +let res = await db.collection('test') + .aggregate() + .project({ + AB: $.setUnion(['$A', '$B']) + }) + .end() +``` +输出如下: + + +```js +{ "_id": 1, "AB": [ 1, 2 ] } +{ "_id": 2, "AB": [ 1, 2 ] } +{ "_id": 3, "AB": [ 1, 2, 3 ] } +{ "_id": 4, "AB": [ 1, 2, 3 ] } +{ "_id": 5, "AB": [ 1, 2 ] } +{ "_id": 6, "AB": [ 1, 2, {}, [] ] } +{ "_id": 7, "AB": [ ] } +{ "_id": 8, "AB": [ 1 ] } +``` + +## 字符串操作符 + +### concat + +连接字符串,返回拼接后的字符串。 + + +#### API 说明 + `concat` 的语法如下: + + +```js +db.command.aggregate.concat([<表达式1>, <表达式2>, ...]) +``` +表达式可以是形如 `$ + 指定字段`,也可以是普通字符串。只要能够被解析成字符串即可。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `concat` 可以拼接 `lastName` 和 `firstName` 字段,得到每位学生的名字全称: + + +```js +const $ = db.command.aggregate +db + .collection('students') + .aggregate() + .project({ + _id: 0, + fullName: $.concat(['$firstName', ' ', '$lastName']) + }) + .end() +``` +返回的结果如下: + + +```js +{ "fullName": "Yuanxin Dong" } +{ "fullName": "Weijia Wang" } +{ "fullName": "Chengxi Li" } +``` + +### dateFromString + +见[dateFromString](#dateFromString) + +### dateToString + +见[dateToString](#dateToString) + +### indexOfBytes + +在目标字符串中查找子字符串,并返回第一次出现的 `UTF-8` 的字节索引(从0开始)。如果不存在子字符串,返回 -1。 + + +#### API 说明 + `indexOfBytes` 的语法如下: + + +```js +db.command.aggregate.indexOfBytes([<目标字符串表达式>, <子字符串表达式>, <开始位置表达式>, <结束位置表达式>]) +``` +下面是 4 种表达式的详细描述: + +|表达式 |描述 | +|---- |---- | +|目标字符串表达式 |任何可以被解析为字符串的表达式 | +|子字符串表达式 |任何可以被解析为字符串的表达式 | +|开始位置表达式 |任何可以被解析为非负整数的表达式 | +|结束位置表达式 |任何可以被解析为非负整数的表达式 | + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `indexOfBytes` 查找字符 `"a"` 在字段 `firstName` 中的位置: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + aStrIndex: $.indexOfBytes(['$firstName', 'a']) + }) + .end() +``` +返回的结果如下: + + +```js +{ "aStrIndex": 2 } +{ "aStrIndex": 5 } +{ "aStrIndex": -1 } +``` + +### indexOfCP + +在目标字符串中查找子字符串,并返回第一次出现的 `UTF-8` 的 `code point` 索引(从0开始)。如果不存在子字符串,返回 -1。 + + +#### API 说明 + `code point` 是“码位”,又名“编码位置”。这里特指 `Unicode` 包中的码位,范围是从0(16进制)到10FFFF(16进制)。 + + `indexOfCP` 的语法如下: + + +```js +db.command.aggregate.indexOfCP([<目标字符串表达式>, <子字符串表达式>, <开始位置表达式>, <结束位置表达式>]) +``` +下面是 4 种表达式的详细描述: + +|表达式 |描述 | +|---- |---- | +|目标字符串表达式 |任何可以被解析为字符串的表达式 | +|子字符串表达式 |任何可以被解析为字符串的表达式 | +|开始位置表达式 |任何可以被解析为非负整数的表达式 | +|结束位置表达式 |任何可以被解析为非负整数的表达式 | + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `indexOfCP` 查找字符 `"a"` 在字段 `firstName` 中的位置: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + aStrIndex: $.indexOfCP(['$firstName', 'a']) + }) + .end() +``` +返回的结果如下: + + +```js +{ "aStrIndex": 2 } +{ "aStrIndex": 5 } +{ "aStrIndex": -1 } +``` + +### split + +按照分隔符分隔数组,并且删除分隔符,返回子字符串组成的数组。如果字符串无法找到分隔符进行分隔,返回原字符串作为数组的唯一元素。 + + +#### API 说明 + `split` 的语法如下: + + +```js +db.command.aggregate.split([<字符串表达式>, <分隔符表达式>]) +``` +字符串表达式和分隔符表达式可以是任意形式的表达式,只要它可以被解析为字符串即可。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "birthday": "1999/12/12" } +{ "birthday": "1998/11/11" } +{ "birthday": "1997/10/10" } +``` +通过 `split` 将每条记录中的 `birthday` 字段对应值分隔成数组,每个数组分别由代表年、月、日的3个元素组成: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + birthday: $.split(['$birthday', '/']) + }) + .end() +``` +返回的结果如下: + + +```js +{ "birthday": [ "1999", "12", "12" ] } +{ "birthday": [ "1998", "11", "11" ] } +{ "birthday": [ "1997", "10", "10" ] } +``` + +### strLenBytes + +计算并返回指定字符串中 `utf-8` 编码的字节数量。 + + +#### API 说明 + `strLenBytes` 的语法如下: + + +```js +db.command.aggregate.strLenBytes(<表达式>) +``` +只要表达式可以被解析成字符串,那么它就是有效表达式。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "name": "dongyuanxin", "nickname": "心谭" } +``` +借助 `strLenBytes` 计算 `name` 字段和 `nickname` 字段对应值的字节长度: + + +```js +const $ = db.command.aggregate +db + .collection('students') + .aggregate() + .project({ + _id: 0, + nameLength: $.strLenBytes('$name'), + nicknameLength: $.strLenBytes('$nickname') + }) + .end() +``` +返回结果如下: + + +```js +{ "nameLength": 11, "nicknameLength": 6 } +``` + +### strLenCP + +计算并返回指定字符串的UTF-8 [code points](http://www.unicode.org/glossary/#code_point) 数量。 + + +#### API 说明 + `strLenCP` 的语法如下: + + +```js +db.command.aggregate.strLenCP(<表达式>) +``` +只要表达式可以被解析成字符串,那么它就是有效表达式。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "name": "dongyuanxin", "nickname": "心谭" } +``` +借助 `strLenCP` 计算 `name` 字段和 `nickname` 字段对应值的UTF-8 [code points](http://www.unicode.org/glossary/#code_point)的数量: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + nameLength: $.strLenCP('$name'), + nicknameLength: $.strLenCP('$nickname') + }) + .end() +``` +返回结果如下: + + +```js +{ "nameLength": 11, "nicknameLength": 2 } +``` + +### strcasecmp + +对两个字符串在不区分大小写的情况下进行大小比较,并返回比较的结果。 + + +#### API 说明 + `strcasecmp` 的语法如下: + + +```js +db.command.aggregate.strcasecmp([<表达式1>, <表达式2>]) +``` +只要 `表达式1`和 `表达式2` 可以被解析成字符串,那么它们就是有效的。 + + 返回的比较结果有1,0和-1三种: + + +- 1:`表达式1` 解析的字符串 > `表达式2` 解析的字符串 - 0:`表达式1` 解析的字符串 = `表达式2` 解析的字符串 - -1:`表达式1` 解析的字符串 < `表达式2` 解析的字符串 + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `strcasecmp` 比较 `firstName` 字段值和 `lastName` 字段值的大小: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + result: $.strcasecmp(['$firstName', '$lastName']), + }) + .end() +``` +返回结果如下: + + +```js +{ "result": 1 } +{ "result": 1 } +{ "result": -1 } +``` + +### substr + +返回字符串从指定位置开始的指定长度的子字符串。它是 `db.command.aggregate.substrBytes` 的别名,更推荐使用后者。 + + +#### API 说明 + `substr` 的语法如下: + + +```js +db.command.aggregate.substr([<表达式1>, <表达式2>, <表达式3>]) +``` +`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 + + 如果 `表达式2` 是负数,返回的结果为 `""`。 + + 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "birthday": "1999/12/12", "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "birthday": "1998/11/11", "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "birthday": "1997/10/10", "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `substr` 可以提取 `birthday` 中的年、月、日信息,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + year: $.substr(['$birthday', 0, 4]), + month: $.substr(['$birthday', 5, 2]), + day: $.substr(['$birthday', 8, -1]) + }) + .end() +``` +返回的结果如下: + + +```js +{ "day": "12", "month": "12", "year": "1999" } +{ "day": "11", "month": "11", "year": "1998" } +{ "day": "10", "month": "10", "year": "1997" } +``` + +### substrBytes + +返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 `UTF-8` 字节索引的字符开始,长度为指定的字节数。 + + +#### API 说明 + `substrBytes` 的语法如下: + + +```js +db.command.aggregate.substrBytes([<表达式1>, <表达式2>, <表达式3>]) +``` +`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 + + 如果 `表达式2` 是负数,返回的结果为 `""`。 + + 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "birthday": "1999/12/12", "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "birthday": "1998/11/11", "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "birthday": "1997/10/10", "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `substrBytes` 可以提取 `birthday` 中的年、月、日信息,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + year: $.substrBytes(['$birthday', 0, 4]), + month: $.substrBytes(['$birthday', 5, 2]), + day: $.substrBytes(['$birthday', 8, -1]) + }) + .end() +``` +返回的结果如下: + + +```js +{ "day": "12", "month": "12", "year": "1999" } +{ "day": "11", "month": "11", "year": "1998" } +{ "day": "10", "month": "10", "year": "1997" } +``` + +### substrCP + +返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 `UTF-8` 字节索引的字符开始,长度为指定的字节数。 + + +#### API 说明 + `substrCP` 的语法如下: + + +```js +db.command.aggregate.substrCP([<表达式1>, <表达式2>, <表达式3>]) +``` +`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 + + 如果 `表达式2` 是负数,返回的结果为 `""`。 + + 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "name": "dongyuanxin", "nickname": "心谭" } +``` +借助 `substrCP` 可以提取 `nickname` 字段值的第一个汉字: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + firstCh: $.substrCP(['$nickname', 0, 1]) + }) + .end() +``` +返回的结果如下: + + +```js +{ "firstCh": "心" } +``` + +### toLower + +将字符串转化为小写并返回。 + + +#### API 说明 + `toLower` 的语法如下: + + +```js +db.command.aggregate.toLower(表达式) +``` +只要表达式可以被解析成字符串,那么它就是有效表达式。例如:`$ + 指定字段`。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `toLower` 将 `firstName` 的字段值转化为小写: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + result: $.toLower('$firstName'), + }) + .end() +``` +返回的结果如下: + + +```js +{ "result": "yuanxin" } +{ "result": "weijia" } +{ "result": "chengxi" } +``` + +### toUpper + +将字符串转化为大写并返回。 + + +#### API 说明 + `toUpper` 的语法如下: + + +```js +db.command.aggregate.toUpper(表达式) +``` +只要表达式可以被解析成字符串,那么它就是有效表达式。例如:`$ + 指定字段`。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } +{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } +{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } +``` +借助 `toUpper` 将 `lastName` 的字段值转化为大写: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .project({ + _id: 0, + result: $.toUpper('$lastName'), + }) + .end() +``` +返回的结果如下: + +```js +{ "result": "DONG" } +{ "result": "WANG" } +{ "result": "LI" } +``` + +## 分组运算方法@accumulator + +### addToSet + +聚合运算符。向数组中添加值,如果数组中已存在该值,不执行任何操作。它只能在 `group stage` 中使用。 + +#### API 说明 + +`addToSet` 语法如下: + +```js +db.command.aggregate.addToSet(<表达式>) +``` + +表达式是形如 `$ + 指定字段` 的字符串。如果指定字段的值是数组,那么整个数组会被当作一个元素。 + +#### 示例代码 + +假设集合 `passages` 的记录如下: + +```js +{ "category": "web", "tags": [ "JavaScript", "CSS" ], "title": "title1" } +{ "category": "System", "tags": [ "C++", "C" ], "title": "title2" } +``` + + +**非数组字段** + + 每条记录的 `category` 对应值的类型是非数组,利用 `addToSet` 统计所有分类: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('passages') + .aggregate() + .group({ + _id: null, + categories: $.addToSet('$category') + }) + .end() +``` +返回的结果如下: + + +```js +{ "_id": null, "categories": [ "System", "web" ] } +``` + + +**数组字段** + + 每条记录的 `tags` 对应值的类型是数组,数组不会被自动展开: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('passages') + .aggregate() + .group({ + _id: null, + tagsList: $.addToSet('$tags') + }) + .end() +``` +返回的结果如下: + + +```js +{ "_id": null, "tagsList": [ [ "C++", "C" ], [ "JavaScript", "CSS" ] ] } +``` + +### avg + + + +返回一组集合中,指定字段对应数据的平均值。 + + +#### API 说明 + `avg` 的语法如下: + + +```js +db.command.aggregate.avg() +``` +`avg` 传入的值除了数字常量外,也可以是任何最终解析成一个数字的表达式。它会忽略非数字值。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +借助 `avg` 可以计算所有记录的 `score` 的平均值: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .group({ + _id: null, + average: $.avg('$score') + }) + .end() +``` +返回的结果如下: + + +```js +{ "_id": null, "average": 90 } +``` + +### first + +返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( `sort` )后,此操作才有意义。 + + +#### API 说明 + `first` 的语法如下: + + +```js +db.command.aggregate.first(<表达式>) +``` +表达式是形如 `$ + 指定字段` 的字符串。 + + `first` 只能在 `group` 阶段被使用,并且需要配合 `sort` 才有意义。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +如果需要得到所有记录中 `score` 的最小值,可以先将所有记录按照 `score` 排序,然后取出第一条记录的 `first`。 + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .sort({ + score: 1 + }) + .group({ + _id: null, + min: $.first('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": null, "min": 80 } +``` + +### last + +返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( `sort` )后,此操作才有意义。 + + +#### API 说明 + `last` 的语法如下: + + +```js +db.command.aggregate.last(<表达式>) +``` +表达式是形如 `$ + 指定字段` 的字符串。 + + `last` 只能在 `group` 阶段被使用,并且需要配合 `sort` 才有意义。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +如果需要得到所有记录中 `score` 的最大值,可以先将所有记录按照 `score` 排序,然后取出最后一条记录的 `last`。 + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .sort({ + score: 1 + }) + .group({ + _id: null, + max: $.last('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": null, "max": 100 } +``` + +### max + +返回一组数值的最大值。 + + +#### API 说明 + `max` 的语法如下: + + +```js +db.command.aggregate.max(<表达式>) +``` +表达式是形如 `$ + 指定字段` 的字符串。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +借助 `max` 可以统计不同组( `group` )中成绩的最高值,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .group({ + _id: '$group', + maxScore: $.max('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": "b", "maxScore": 100 } +{ "_id": "a", "maxScore": 96 } +... +``` + +### mergeObjects + +见[mergeObjects](#mergeObjects) + +### min + +返回一组数值的最小值。 + + +#### API 说明 + `min` 的语法如下: + + +```js +db.command.aggregate.min(<表达式>) +``` +表达式是形如 `$ + 指定字段` 的字符串。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +借助 `min` 可以统计不同组( `group` )中成绩的最低值,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .group({ + _id: '$group', + minScore: $.min('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": "b", "minScore": 80 } +{ "_id": "a", "minScore": 84 } +``` + +### push + +在 `group` 阶段,返回一组中表达式指定列与对应的值,一起组成的数组。 + + +#### API 说明 + `push` 语法如下: + + +```js +db.command.aggregate.push({ + <字段名1>: <指定字段1>, + <字段名2>: <指定字段2>, + ... +}) +``` + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "group": "a", "name": "stu1", "score": 84 } +{ "group": "a", "name": "stu2", "score": 96 } +{ "group": "b", "name": "stu3", "score": 80 } +{ "group": "b", "name": "stu4", "score": 100 } +``` +借助 `push` 操作,对不同分组( `group` )的所有记录,聚合所有数据并且将其放入一个新的字段中,进一步结构化和语义化数据。 + + +```js +const $ = db.command.aggregate +let res = await db + .collection('students') + .aggregate() + .group({ + _id: '$group', + students: $.push({ + name: '$name', + score: '$score' + }) + }) + .end() +``` +输出结果如下: + + +```js +{ "_id": "b", "students": [{ "name": "stu3", "score": 80 }, { "name": "stu4", "score": 100 }] } +{ "_id": "a", "students": [{ "name": "stu1", "score": 84 }, { "name": "stu2", "score": 96 }] } +``` + +### stdDevPop + +返回一组字段对应值的标准差。 + + +#### API 说明 + `stdDevPop` 的使用形式如下: + + +```js +db.command.aggregate.stdDevPop(<表达式>) +``` +表达式传入的是指定字段,指定字段对应的值的数据类型必须是 `number` ,否则结果会返回 `null`。 + + +#### 示例代码 + 假设集合 `students` 的记录如下:`a` 组同学的成绩分别是84和96,`b`组同学的成绩分别是80和100。 + + +```js +{ "group":"a", "score":84 } +{ "group":"a", "score":96 } +{ "group":"b", "score":80 } +{ "group":"b", "score":100 } +``` +可以用 `stdDevPop` 来分别计算 `a` 和 `b` 两组同学成绩的标准差,以此来比较哪一组同学的成绩更稳定。代码如下: + + +```js +const $ = db.command.aggregate +let res = await db.collection('students').aggregate() + .group({ + _id: '$group', + stdDev: $.stdDevPop('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": "b", "stdDev": 10 } +{ "_id": "a", "stdDev": 6 } +``` + +### stdDevSamp + +计算输入值的样本标准偏差。如果输入值代表数据总体,或者不概括更多的数据,请改用 `db.command.aggregate.stdDevPop`。 + + +#### API 说明 + `stdDevSamp` 的使用形式如下: + + +```js +db.command.aggregate.stdDevSamp(<表达式>) +``` +表达式传入的是指定字段,`stdDevSamp` 会自动忽略非数字值。如果指定字段所有的值均是非数字,那么结果返回 `null`。 + + +#### 示例代码 + 假设集合 `students` 的记录如下: + + +```js +{ "score": 80 } +{ "score": 100 } +``` +可以用 `stdDevSamp` 来计算成绩的标准样本偏差。代码如下: + + +```js +const $ = db.command.aggregate +let res = await db.collection('students').aggregate() + .group({ + _id: null, + ageStdDev: $.stdDevSamp('$score') + }) + .end() +``` +返回的数据结果如下: + + +```js +{ "_id": null, "ageStdDev": 14.142135623730951 } +``` +如果向集合 `students` 添加一条新记录,它的 `score` 字段类型是 `string`: + + +```js +{ "score": "aa" } +``` +用上面代码计算标准样本偏差时,`stdDevSamp` 会自动忽略类型不为 `number` 的记录,返回结果保持不变。 + +### sum + + + +计算并且返回一组字段所有数值的总和。 + + +#### API 说明 + `sum` 的使用形式如下: + + +```js +db.command.aggregate.sum(<表达式>) +``` +表达式可以传入指定字段,也可以传入指定字段组成的列表。`sum` 会自动忽略非数字值。如果字段下的所有值均是非数字,那么结果返回 0。若传入数字常量,则当做所有记录该字段的值都给给定常量,在聚合时相加,最终值为输入记录数乘以常量。 + + +#### 示例代码 + 假设代表商品的集合 `goods` 的记录如下:`price` 代表商品销售额,`cost` 代表商品成本 + + +```js +{ "cost": -10, "price": 100 } +{ "cost": -15, "price": 1 } +{ "cost": -10, "price": 10 } +``` + + +**单独字段** + + 借助 `sum` 可以计算所有商品的销售总和,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('goods') + .aggregate() + .group({ + _id: null, + totalPrice: $.sum('$price') + }) + .end() +``` +返回的数据结果如下:销售额是 111 + + +```js +{ "_id": null, "totalPrice": 111 } +``` + + +**字段列表** + + 如果需要计算所有商品的利润总额,那么需要将每条记录的 `cost` 和 `price` 相加得到此记录对应商品的利润。最后再计算所有商品的利润总额。 + + 借助 `sum`,代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('goods') + .aggregate() + .group({ + _id: null, + totalProfit: $.sum( + $.sum(['$price', '$cost']) + ) + }) + .end() +``` +返回的数据结果如下:利润总额为 76 + + +```js +{ "_id": null, "totalProfit": 76 } +``` + +## 变量操作符 + +### let + +自定义变量,并且在指定表达式中使用,返回的结果是表达式的结果。 + + +#### API 说明 + `let` 的语法如下: + + +```js +db.command.aggregate.let({ + vars: { + <变量1>: <变量表达式>, + <变量2>: <变量表达式>, + ... + }, + in: <结果表达式> +}) +``` +`vars` 中可以定义多个变量,变量的值由 `变量表达式` 计算而来,并且被定义的变量只有在 `in` 中的 `结果表达式` 才可以访问。 + + 在 `in` 的结果表达式中访问自定义变量时候,请在变量名前加上双美元符号( `$$` )并用引号括起来。 + + +#### 示例代码 + 假设代表商品的集合 `goods` 的记录如下:`price` 代表商品价格,`discount` 代表商品折扣率,`cost` 代表商品成本 + + +```js +{ "cost": -10, "discount": 0.95, "price": 100 } +{ "cost": -15, "discount": 0.98, "price": 1 } +{ "cost": -10, "discount": 1, "price": 10 } +``` +借助 `let` 可以定义并计算每件商品实际的销售价格,并将其赋值给自定义变量 `priceTotal`。最后再将 `priceTotal` 与 `cost` 进行取和( `sum` )运算,得到每件商品的利润。 + + 代码如下: + + +```js +const $ = db.command.aggregate +let res = await db + .collection('goods') + .aggregate() + .project({ + profit: $.let({ + vars: { + priceTotal: $.multiply(['$price', '$discount']) + }, + in: $.sum(['$$priceTotal', '$cost']) + }) + }) + .end() +``` +返回的数据结果如下: + + +``` +{ "profit": 85 } +{ "profit": -14.02 } +{ "profit": 0 } +``` + diff --git a/docs/uniCloud/cf-database-aggregate.md b/docs/uniCloud/cf-database-aggregate.md new file mode 100644 index 0000000000000000000000000000000000000000..3758b2d0323f890b46368ad0b92845dba410dfc2 --- /dev/null +++ b/docs/uniCloud/cf-database-aggregate.md @@ -0,0 +1,2123 @@ +# 云数据库聚合操作@aggregate + +有时候我们需要对数据进行分析操作,比如一些统计操作、联表查询等,这个时候简单的查询操作就搞不定这些需求,因此就需要使用聚合操作来完成。 + +获取数据库集合的聚合操作实例 + +```js +db.collection('scores').aggregate() +``` + +**注意:** + +- 聚合操作实例仅用于查询,不可执行增删改操作。在聚合操作实例上只能使用聚合操作方法,不能使用where/orderBy等基础方法,where需改为match,orderBy应使用sort实现,细节请阅读下方聚合操作文档。 +- 聚合操作在大数据量下性能不如简单查询,请根据自身业务选择合适的用法 + +云函数中使用时切勿复用aggregate实例,容易引发Bug。 + +以下两种写法均为错误示例: + +```js +const db = uniCloud.database() +const collection = db.collection('test') +const aggregate = collection.aggregate() // 云函数实例复用时,此聚合实例也会复用,导致Bug +exports.main = async function(){ + const res = await aggregate.match({a:1}).end() + return {res} +} +``` + +```js +const db = uniCloud.database() +const collection = db.collection('test') +exports.main = async function(){ + const aggregate = collection.aggregate() // 此聚合实例分别在两个请求内使用,导致Bug + const res1 = await aggregate.match({a:1}).end() + const res2 = await aggregate.match({a:2}).end() + return {res1, res2} +} +``` + +## 聚合表达式@aggregate-expression + +表达式可以是字段路径、常量、或聚合操作符。表达式可以嵌套表达式。 + +**字段路径** + +表达式用字段路径表示法来指定记录中的字段。字段路径的表示由一个 `$` 符号加上字段名或嵌套字段名。嵌套字段名用点将嵌套的各级字段连接起来。如 `$profile` 就表示 `profile` 的字段路径,`$profile.name` 就表示 `profile.name` 的字段路径(`profile` 字段中嵌套的 `name` 字段)。 + +例如:现有以下数据 + +```json +[{ + "profile": { + "name": "Chloe" + }, + "status": 0 +}] +``` + +```js +// 执行以下操作 +let res = await db.collection('scores').aggregate() + .addFields({ + name: '$profile.name' + }) + .end() + +// 返回值为 +{ + "data": [{ + "profile": { + "name": "Chloe" + }, + "status": 0, + "name": "Chloe" + }] +} +``` + + +**常量** + +常量可以是任意类型。默认情况下 $ 开头的字符串都会被当做字段路径处理,如果想要避免这种行为,使用 `db.command.aggregate.literal` 声明为常量。 + +**聚合操作符** + +参考[聚合操作符](#aggregate-operator) + +## addFields@aggregate-add-fields + +聚合阶段。添加新字段到输出的记录。经过 `addFields` 聚合阶段,输出的所有记录中除了输入时带有的字段外,还将带有 `addFields` 指定的字段。 + + +**API 说明** + +`addFields` 等同于同时指定了所有已有字段和新增字段的 `project` 阶段。 + +**`addFields` 的形式如下:** +```js +addFields({ + <新字段>: <表达式> +}) +``` +`addFields` 可指定多个新字段,每个新字段的值由使用的表达式决定。 + +如果指定的新字段与原有字段重名,则新字段的值会覆盖原有字段的值。注意 `addFields` 不能用来给数组字段添加元素。 + +**示例 1:连续两次 addFields** + +假设集合 scores 有如下记录: +```js +{ + _id: 1, + student: "Maya", + homework: [ 10, 5, 10 ], + quiz: [ 10, 8 ], + extraCredit: 0 +} +{ + _id: 2, + student: "Ryan", + homework: [ 5, 6, 5 ], + quiz: [ 8, 8 ], + extraCredit: 8 +} +``` +应用两次 `addFields`,第一次增加两个字段分别为 `homework` 和 `quiz` 的和值,第二次增加一个字段再基于上两个和值求一次和值。 +```js +const $ = db.command.aggregate +let res = await db.collection('scores').aggregate() + .addFields({ + totalHomework: $.sum('$homework'), + totalQuiz: $.sum('$quiz') + }) + .addFields({ + totalScore: $.add(['$totalHomework', '$totalQuiz', '$extraCredit']) + }) + .end() +``` + +返回结果如下: +```js +{ + "_id" : 1, + "student" : "Maya", + "homework" : [ 10, 5, 10 ], + "quiz" : [ 10, 8 ], + "extraCredit" : 0, + "totalHomework" : 25, + "totalQuiz" : 18, + "totalScore" : 43 +} +{ + "_id" : 2, + "student" : "Ryan", + "homework" : [ 5, 6, 5 ], + "quiz" : [ 8, 8 ], + "extraCredit" : 8, + "totalHomework" : 16, + "totalQuiz" : 16, + "totalScore" : 40 +} +``` + +**示例 2:在嵌套记录里增加字段** + +可以用点表示法在嵌套记录里增加字段。假设 vehicles 集合含有如下记录: +```js +{ _id: 1, type: "car", specs: { doors: 4, wheels: 4 } } +{ _id: 2, type: "motorcycle", specs: { doors: 0, wheels: 2 } } +{ _id: 3, type: "jet ski" } +``` +可以用如下操作在 `specs` 字段下增加一个新的字段 `fuel_type`,值都设为固定字符串 `unleaded`: +```js +let res = await db.collection('vehicles').aggregate() + .addFields({ + 'specs.fuel_type': 'unleaded' + }) + .end() +``` + +返回结果如下: +```js +{ _id: 1, type: "car", + specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } } +{ _id: 2, type: "motorcycle", + specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } } +{ _id: 3, type: "jet ski", + specs: { fuel_type: "unleaded" } } +``` + +**示例 3:设置字段值为另一个字段** + +可以通过 `$` 加字段名组成的字符串作为值的表达式来设置字段的值为另一个字段的值。 + +同样用上一个集合示例,可以用如下操作添加一个字段 `vehicle_type`,将其值设置为 `type` 字段的值: +```js +let res = await db.collection('vehicles').aggregate() + .addFields({ + vehicle_type: '$type' + }) + .end() +``` +返回结果如下: +```js +{ _id: 1, type: "car", vehicle_type: "car", + specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } } +{ _id: 2, type: "motorcycle", vehicle_type: "motorcycle", + specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } } +{ _id: 3, type: "jet ski", vehicle_type: "jet ski", + specs: { fuel_type: "unleaded" } } +``` + +## bucket@aggregate-bucket + +聚合阶段。将输入记录根据给定的条件和边界划分成不同的组,每组即一个 `bucket`。 + +**API 说明** + +每组分别作为一个记录输出,包含一个以下界为值的 `_id` 字段和一个以组中记录数为值的 `count` 字段。`count` 在没有指定 `output` 的时候是默认输出的。 + +`bucket` 只会在组内有至少一个记录的时候输出。 + +**bucket 的形式如下:** +```js +bucket({ + groupBy: , + boundaries: [, , ...], + default: , + output: { + : , + ... + : + } +}) +``` +`groupBy` 是一个用以决定分组的表达式,会应用在各个输入记录上。可以用 `$` 前缀加上要用以分组的字段路径来作为表达式。除非用 `default` 指定了默认值,否则每个记录都需要包含指定的字段,且字段值必须在 `boundaries` 指定的范围之内。 + +`boundaries` 是一个数组,每个元素分别是每组的下界。必须至少指定两个边界值。数组值必须是同类型递增的值。 + +`default` 可选,指定之后,没有进入任何分组的记录将都进入一个默认分组,这个分组记录的 `_id` 即由 `default` 决定。`default` 的值必须小于 `boundaries` 中的最小值或大于等于其中的最大值。`default` 的值可以与 `boundaries` 元素值类型不同。 + +`output` 可选,用以决定输出记录除了 `_id` 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 `output` 指定时,默认的 `count` 是不会被默认输出的,必须手动指定: +```js +output: { + count: $.sum(1), + ... + : +} +``` +使用 bucket 需要满足以下至少一个条件,否则会抛出错误: + +每一个输入记录应用 groupBy 表达式获取的值都必须是一个在 boundaries 内的值 + +指定一个 default 值,该值在 boundaries 以外,或与 boundaries 元素的值不同的类型。 + +**示例** + +假设集合 items 有如下记录: +```js +{ + _id: "1", + price: 10 +} +{ + _id: "2", + price: 50 +} +{ + _id: "3", + price: 20 +} +{ + _id: "4", + price: 80 +} +{ + _id: "5", + price: 200 +} +``` + +对上述记录进行分组,将 [0, 50) 分为一组,[50, 100) 分为一组,其他分为一组: + +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .bucket({ + groupBy: '$price', + boundaries: [0, 50, 100], + default: 'other', + output: { + count: $.sum(1), + ids: $.push('$_id') + } + }) + .end() +``` + +返回结果如下: + +```js +[ + { + "_id": 0, + "count": 2, + "ids": [ + "1", + "3" + ] + }, + { + "_id": 50, + "count": 2, + "ids": [ + "2", + "4" + ] + }, + { + "_id": "other", + "count": 1, + "ids": [ + "5" + ] + } +] +``` + +## bucketAuto@aggregate-bucket-auto + +聚合阶段。将输入记录根据给定的条件划分成不同的组,每组即一个 `bucket`。与 `bucket` 的其中一个不同之处在于无需指定 `boundaries`,`bucketAuto` 会自动尝试将记录尽可能平均的分散到每组中。 + +**API 说明** +每组分别作为一个记录输出,包含一个以包含组中最大值和最小值两个字段的对象为值的 _id 字段和一个以组中记录数为值的 count 字段。count 在没有指定 output 的时候是默认输出的。 + +**bucketAuto 的形式如下:** +```js +bucketAuto({ + groupBy: , + buckets: , + granularity: , + output: { + : , + ... + : + } +}) +``` +`groupBy` 是一个用以决定分组的表达式,会应用在各个输入记录上。可以用 $ 前缀加上要用以分组的字段路径来作为表达式。除非用 `default` 指定了默认值,否则每个记录都需要包含指定的字段,且字段值必须在 `boundaries` 指定的范围之内。 + +`buckets` 是一个用于指定划分组数的正整数。 + +`granularity` 是可选枚举值字符串,用于保证自动计算出的边界符合给定的规则。这个字段仅可在所有 `groupBy` 值都是数字并且没有 `NaN` 的情况下使用。枚举值包括:`R5、R10、R20、R40、R80、1-2-5、E6、E12、E24、E48、E96、E192、POWERSOF2`。 + +`output` 可选,用以决定输出记录除了 `_id` 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 `output` 指定时,默认的 `count` 是不会被默认输出的,必须手动指定: +```js +output: { + count: $.sum(1), + ... + : +} +``` +在以下情况中,输出的分组可能会小于给定的组数: + +输入记录数少于分组数 +- `groupBy` 计算得到的唯一值少于分组数 +- `granularity` 的间距少于分组数 +- `granularity` 不够精细以至于不能平均分配到各组 + +**granularity 详细说明** + +`granularity` 用于保证边界值属于一个给定的数字序列。 + +**Renard 序列** + +Renard 序列是以 10 的 5 / 10 / 20 / 40 / 80 次方根来推导的、在 1.0 到 10.0 (如果是 R80 则是 10.3) 之间的数字序列。 + +设置 granularity 为 R5 / R10 / R20 / R40 / R80 就把边界值限定在序列内。如果 groupBy 的值不在 1.0 到 10.0 (如果是 R80 则是 10.3) 内,则序列数字会自动乘以 10。 + +**E 序列** + +E 序列是以 10 的 6 / 12 / 24 / 48 / 96 / 192 次方跟来推导的、带有一个特定误差的、在 1.0 到 10.0 之间的数字序列。 + +**1-2-5 序列** + +1-2-5 序列 表现与三值 Renard 序列一样。 + +**2的次方序列** + +由 2 的各次方组成的序列数字。 + +**示例** + +假设集合 items 有如下记录: +```js +{ + _id: "1", + price: 10.5 +} +{ + _id: "2", + price: 50.3 +} +{ + _id: "3", + price: 20.8 +} +{ + _id: "4", + price: 80.2 +} +{ + _id: "5", + price: 200.3 +} +``` +对上述记录进行自动分组,分成三组: +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .bucketAuto({ + groupBy: '$price', + buckets: 3, + }) + .end() +``` +返回结果如下: +```js +{ + "_id": { + "min": 10.5, + "max": 50.3 + }, + "count": 2 +} +{ + "_id": { + "min": 50.3, + "max": 200.3 + }, + "count": 2 +} +{ + "_id": { + "min": 200.3, + "max": 200.3 + }, + "count": 1 +} +``` + +## count@aggregate-count + +聚合阶段。计算上一聚合阶段输入到本阶段的记录数,输出一个记录,其中指定字段的值为记录数。 + +**API 说明** + +**count 的形式如下:** +```js +count() +``` +`` 是输出记录数的字段的名字,不能是空字符串,不能以 $ 开头,不能包含 . 字符。 + +count 阶段等同于 group + project 的操作: +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .group({ + _id: null, + count: $.sum(1), + }) + .project({ + _id: 0, + }) + .end() +``` +上述操作会输出一个包含 count 字段的记录。 + +**示例** + +假设集合 items 有如下记录: +```js +{ + _id: "1", + price: 10.5 +} +{ + _id: "2", + price: 50.3 +} +{ + _id: "3", + price: 20.8 +} +{ + _id: "4", + price: 80.2 +} +{ + _id: "5", + price: 200.3 +} +``` +找出价格大于 50 的记录数: +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .match({ + price: $.gt(50) + }) + .count('expensiveCount') + .end() +``` +返回结果如下: +```js +{ + "expensiveCount": 3 +} +``` + +## geoNear@aggregate-geo-near + +聚合阶段。将记录按照离给定点从近到远输出。 + +|属性 |类型 |默认值 |必填 |说明 | +|---- |---- |---- |---- |---- | +|near |GeoPoint | |是 |GeoJSON Point,用于判断距离的点 | +|spherical |true | |是 |必填,值为 true | +|maxDistance |number | |否 |距离最大值 | +|minDistance |number | |否 |距离最小值 | +|query |Object | |否 |要求记录必须同时满足该条件(语法同 where) | +|distanceMultiplier |number | |否 |返回时在距离上乘以该数字 | +|distanceField |string | |是 |存放距离的输出字段名,可以用点表示法表示一个嵌套字段 | +|includeLocs |string | |否 |列出要用于距离计算的字段,如果记录中有多个字段都是地理位置时有用 | +|key |string | |否 |选择要用的地理位置索引。如果集合由多个地理位置索引,则必须指定一个,指定的方式是指定对应的字段 | + +**API 说明** + +- `geoNear` 必须为第一个聚合阶段 +- 必须有地理位置索引。如果有多个,必须用 `key` 参数指定要使用的索引。 + +**示例** + +假设集合 attractions 有如下记录: +```js +{ + "_id": "geoNear.0", + "city": "Guangzhou", + "docType": "geoNear", + "location": { + "type": "Point", + "coordinates": [ + 113.30593, + 23.1361155 + ] + }, + "name": "Canton Tower" +}, +{ + "_id": "geoNear.1", + "city": "Guangzhou", + "docType": "geoNear", + "location": { + "type": "Point", + "coordinates": [ + 113.306789, + 23.1564721 + ] + }, + "name": "Baiyun Mountain" +}, +{ + "_id": "geoNear.2", + "city": "Beijing", + "docType": "geoNear", + "location": { + "type": "Point", + "coordinates": [ + 116.3949659, + 39.9163447 + ] + }, + "name": "The Palace Museum" +}, +{ + "_id": "geoNear.3", + "city": "Beijing", + "docType": "geoNear", + "location": { + "type": "Point", + "coordinates": [ + 116.2328567, + 40.242373 + ] + }, + "name": "Great Wall" +} +``` +```js +const $ = db.command.aggregate +let res = await db.collection('attractions').aggregate() + .geoNear({ + distanceField: 'distance', // 输出的每个记录中 distance 即是与给定点的距离 + spherical: true, + near: new db.Geo.Point(113.3089506, 23.0968251), + query: { + docType: 'geoNear', + }, + key: 'location', // 若只有 location 一个地理位置索引的字段,则不需填 + includeLocs: 'location', // 若只有 location 一个是地理位置,则不需填 + }) + .end() +``` + +返回结果如下: +```js +{ + "_id": "geoNear.0", + "location": { + "type": "Point", + "coordinates": [ + 113.30593, + 23.1361155 + ] + }, + "docType": "geoNear", + "name": "Canton Tower", + "city": "Guangzhou", + "distance": 4384.68131486958 +}, +{ + "_id": "geoNear.1", + "city": "Guangzhou", + "location": { + "type": "Point", + "coordinates": [ + 113.306789, + 23.1564721 + ] + }, + "docType": "geoNear", + "name": "Baiyun Mountain", + "distance": 6643.521654040738 +}, +{ + "_id": "geoNear.2", + "docType": "geoNear", + "name": "The Palace Museum", + "city": "Beijing", + "location": { + "coordinates": [ + 116.3949659, + 39.9163447 + ], + "type": "Point" + }, + "distance": 1894750.4414538583 +}, +{ + "_id": "geoNear.3", + "docType": "geoNear", + "name": "Great Wall", + "city": "Beijing", + "location": { + "type": "Point", + "coordinates": [ + 116.2328567, + 40.242373 + ] + }, + "distance": 1928300.3308822548 +} +``` + +## group@aggregate-group + +聚合阶段。将输入记录按给定表达式分组,输出时每个记录代表一个分组,每个记录的 _id 是区分不同组的 key。输出记录中也可以包括累计值,将输出字段设为累计值即会从该分组中计算累计值。 + +使用group可以很方便的实现类似SQL的distinct功能 + +**API 说明** + +**group 的形式如下:** +```js +group({ + _id: , + : , + ... + : +}) +``` + +`_id` 参数是必填的,如果填常量则只有一组。其他字段是可选的,都是累计值,用 `$.sum` 等累计器(`const $ = db.command.aggregate`),但也可以使用其他表达式。 + +累计器必须是以下操作符之一: + +详细使用方法见[累计器操作符](#累计器操作符) + +|操作符 |说明 | +|---- |---- | +|addToSet |向数组中添加值,如果数组中已存在该值,不执行任何操作 | +|avg |返回一组集合中,指定字段对应数据的平均值 | +|sum |计算并且返回一组字段所有数值的总和 | +|first |返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 | +|last |返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 | +|max |返回一组数值的最大值 | +|min |返回一组数值的最小值 | +|push |在 group 阶段,返回一组中表达式指定列与对应的值,一起组成的数组 | +|stdDevPop |返回一组字段对应值的标准差 | +|stdDevSamp |计算输入值的样本标准偏差。如果输入值代表数据总体,或者不概括更多的数据,请改用 db.command.aggregate.stdDevPop| +|mergeObjects |将多个文档合并为单个文档 | + +**内存限制** + +该阶段有 100M 内存使用限制。 + +**示例 1:按字段值分组** + +假设集合 avatar 有如下记录: +```js +{ + _id: "1", + alias: "john", + region: "asia", + scores: [40, 20, 80], + coins: 100 +} +{ + _id: "2", + alias: "arthur", + region: "europe", + scores: [60, 90], + coins: 20 +} +{ + _id: "3", + alias: "george", + region: "europe", + scores: [50, 70, 90], + coins: 50 +} +{ + _id: "4", + alias: "john", + region: "asia", + scores: [30, 60, 100, 90], + coins: 40 +} +{ + _id: "5", + alias: "george", + region: "europe", + scores: [20], + coins: 60 +} +{ + _id: "6", + alias: "john", + region: "asia", + scores: [40, 80, 70], + coins: 120 +} +``` +```js +const $ = db.command.aggregate +let res = await db.collection('avatar').aggregate() + .group({ + _id: '$alias', + num: $.sum(1) + }) + .end() +``` +返回结果如下: +```js +{ + "_id": "john", + "num": 3 +} +{ + "_id": "authur", + "num": 1 +} +{ + "_id": "george", + "num": 2 +} +``` + +**示例 2:按多个值分组** + +可以给 _id 传入记录的方式按多个值分组。还是沿用上面的示例数据,按各个区域(region)获得相同最高分(score)的来分组,并求出各组虚拟币(coins)的总量: +```js +const $ = db.command.aggregate +let res = await db.collection('avatar').aggregate() + .group({ + _id: { + region: '$region', + maxScore: $.max('$scores') + }, + totalCoins: $.sum('$coins') + }) + .end() +``` +返回结果如下: +```js +{ + "_id": { + "region": "asia", + "maxScore": 80 + }, + "totalCoins": 220 +} +{ + "_id": { + "region": "asia", + "maxScore": 100 + }, + "totalCoins": 40 +} +{ + "_id": { + "region": "europe", + "maxScore": 90 + }, + "totalCoins": 70 +} +{ + "_id": { + "region": "europe", + "maxScore": 20 + }, + "totalCoins": 60 +} +``` + +## limit@aggregate-limit + +聚合阶段。限制输出到下一阶段的记录数。 + +**示例** + +假设集合 items 有如下记录: +```js +{ + _id: "1", + price: 10 +} +{ + _id: "2", + price: 50 +} +{ + _id: "3", + price: 20 +} +{ + _id: "4", + price: 80 +} +{ + _id: "5", + price: 200 +} +``` +返回价格大于 20 的记录的最小的两个记录: +```js +const $ = db.command.aggregate +let res = await db.collection('items').aggregate() + .match({ + price: $.gt(20) + }) + .sort({ + price: 1, + }) + .limit(2) + .end() +``` +返回结果如下: +```js +{ + "_id": "2", + "price": 50 +} +{ + "_id": "4", + "price": 80 +} +``` + +## lookup@aggregate-lookup + +聚合阶段。联表查询。与同个数据库下的一个指定的集合做 left outer join(左外连接)。对该阶段的每一个输入记录,lookup 会在该记录中增加一个数组字段,该数组是被联表中满足匹配条件的记录列表。lookup 会将连接后的结果输出给下个阶段。 + +**API 说明** + +`lookup` 有两种使用方式 + +### 相等匹配 +将输入记录的一个字段和被连接集合的一个字段进行相等匹配时,采用以下定义: +```js +lookup({ + from: <要连接的集合名>, + localField: <输入记录的要进行相等匹配的字段>, + foreignField: <被连接集合的要进行相等匹配的字段>, + as: <输出的数组字段名> +}) +``` + +**参数详细说明** + +|参数字段 |说明 | +|---- |---- | +|from |要进行连接的另外一个集合的名字 | +|localField |当前流水线的输入记录的字段名,该字段将被用于与 from 指定的集合的 foreignField 进行相等匹配。如果输入记录中没有该字段,则该字段的值在匹配时会被视作 null| +|foreignField |被连接集合的字段名,该字段会被用于与 localField 进行相等匹配。如果被连接集合的记录中没有该字段,该字段的值将在匹配时被视作 null | +|as |指定连接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。如果输入记录中本来就已有该字段,则该字段会被覆写 | + +这个操作等价于以下伪 SQL 操作: + +``` +SELECT *, +FROM collection +WHERE IN (SELECT * + FROM + WHERE = ); +``` + +**例子:** + +- 指定一个相等匹配条件 +- 对数组字段应用相等匹配 +- 组合 mergeObjects 应用相等匹配 + +### 自定义连接条件、拼接子查询 + +如果需要指定除相等匹配之外的连接条件,或指定多个相等匹配条件,或需要拼接被连接集合的子查询结果,那可以使用如下定义: +```js +lookup({ + from: <要连接的集合名>, + let: { <变量1>: <表达式1>, ..., <变量n>: <表达式n> }, + pipeline: [ <在要连接的集合上进行的流水线操作> ], + as: <输出的数组字段名> +}) +``` + +**参数详细说明** + +|参数字段 |说明 | +|---- |---- | +|from |要进行连接的另外一个集合的名字 | +|let |可选。指定在 pipeline 中可以使用的变量,变量的值可以引用输入记录的字段,比如 let: { userName: '$name' } 就代表将输入记录的 name 字段作为变量 userName 的值。在 pipeline 中无法直接访问输入记录的字段,必须通过 let 定义之后才能访问,访问的方式是在 expr 操作符中用 $$变量名 的方式访问,比如 $$userName。 | +|pipeline |指定要在被连接集合中运行的聚合操作。如果要返回整个集合,则该字段取值空数组 []。在 pipeline 中无法直接访问输入记录的字段,必须通过 let 定义之后才能访问,访问的方式是在 expr 操作符中用 $$变量名 的方式访问,比如 $$userName。 | +|as |指定连接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。如果输入记录中本来就已有该字段,则该字段会被覆写 | + +该操作等价于以下伪 SQL 语句: +``` +SELECT *, +FROM collection +WHERE IN (SELECT + FROM + WHERE ); +``` + +**例子** + +- 指定多个连接条件 +- 拼接被连接集合的子查询 + +**示例** + +**指定一个相等匹配条件** + +假设 orders 集合有以下记录: +```js +[ + {"_id":4,"book":"novel 1","price":30,"quantity":2}, + {"_id":5,"book":"science 1","price":20,"quantity":1}, + {"_id":6} +] +``` +books 集合有以下记录: +```js +[ + {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, + {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, + {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"}, + {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"}, + {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null}, + {"_id":"book6","author":"author 5","category":"novel","stock":"60"} +] +``` +以下聚合操作可以通过一个相等匹配条件连接 `orders` 和 `books` 集合,匹配的字段是 `orders` 集合的 `book` 字段和 `books` 集合的 title 字段: +```js +const db = uniCloud.database() +let res = await db.collection('orders').aggregate() + .lookup({ + from: 'books', + localField: 'book', + foreignField: 'title', + as: 'bookList', + }) + .end() +``` +结果: +```js +[ + { + "_id": 4, + "book": "novel 1", + "price": 30, + "quantity": 2, + "bookList": [ + { + "_id": "book1", + "title": "novel 1", + "author": "author 1", + "category": "novel", + "stock": 10 + } + ] + }, + { + "_id": 5, + "book": "science 1", + "price": 20, + "quantity": 1, + "bookList": [ + { + "_id": "book3", + "category": "science", + "title": "science 1", + "author": "author 3", + "stock": 30 + } + ] + }, + { + "_id": 6, + "bookList": [ + { + "_id": "book5", + "category": "science", + "author": "author 4", + "stock": 50, + "title": null + }, + { + "_id": "book6", + "author": "author 5", + "stock": "60", + "category": "novel" + } + ] + } +] +``` +对数组字段应用相等匹配 +假设 authors 集合有以下记录: +```js +[ + {"_id": 1, "name": "author 1", "intro": "Two-time best-selling sci-fiction novelist"}, + {"_id": 3, "name": "author 3", "intro": "UCB assistant professor"}, + {"_id": 4, "name": "author 4", "intro": "major in CS"} +] +``` +books 集合有以下记录: +```js +[ + {"_id":"book1","authors":["author 1"],"category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, + {"_id":"book3","authors":["author 3", "author 4"],"category":"science","stock":30,"title":"science 1"}, + {"_id":"book4","authors":["author 3"],"category":"science","stock":40,"title":"science 2"} +] +``` +以下操作获取作者信息及他们分别发表的书籍,使用了 lookup 操作匹配 authors 集合的 name 字段和 books 集合的 authors 数组字段: +```js +const db = cloud.database() +let res = await db.collection('authors').aggregate() + .lookup({ + from: 'books', + localField: 'name', + foreignField: 'authors', + as: 'publishedBooks', + }) + .end() +``` +结果 +```js +[ + { + "_id": 1, + "intro": "Two-time best-selling sci-fiction novelist", + "name": "author 1", + "publishedBooks": [ + { + "_id": "book1", + "title": "novel 1", + "category": "novel", + "stock": 10, + "authors": [ + "author 1" + ] + } + ] + }, + { + "_id": 3, + "name": "author 3", + "intro": "UCB assistant professor", + "publishedBooks": [ + { + "_id": "book3", + "category": "science", + "title": "science 1", + "stock": 30, + "authors": [ + "author 3", + "author 4" + ] + }, + { + "_id": "book4", + "title": "science 2", + "category": "science", + "stock": 40, + "authors": [ + "author 3" + ] + } + ] + }, + { + "_id": 4, + "intro": "major in CS", + "name": "author 4", + "publishedBooks": [ + { + "_id": "book3", + "category": "science", + "title": "science 1", + "stock": 30, + "authors": [ + "author 3", + "author 4" + ] + } + ] + } +] +``` + +**组合 mergeObjects 应用相等匹配** + +假设 `orders` 集合有以下记录: +```js +[ + {"_id":4,"book":"novel 1","price":30,"quantity":2}, + {"_id":5,"book":"science 1","price":20,"quantity":1}, + {"_id":6} +] +``` +`books` 集合有以下记录: +```js +[ + {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, + {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, + {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"}, + {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"}, + {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null}, + {"_id":"book6","author":"author 5","category":"novel","stock":"60"} +] +``` +以下操作匹配 orders 的 book 字段和 books 的 title 字段,并将 books 匹配结果直接 merge 到 orders 记录中。 +```js +var db = cloud.database() +var $ = db.command.aggregate +let res = await db.collection('orders').aggregate() + .lookup({ + from: "books", + localField: "book", + foreignField: "title", + as: "bookList" + }) + .replaceRoot({ + newRoot: $.mergeObjects([ $.arrayElemAt(['$bookList', 0]), '$$ROOT' ]) + }) + .project({ + bookList: 0 + }) + .end() +``` +结果 +```js +[ + { + "_id": 4, + "title": "novel 1", + "author": "author 1", + "time": 1564456048486, + "category": "novel", + "stock": 10, + "book": "novel 1", + "price": 30, + "quantity": 2 + }, + { + "_id": 5, + "category": "science", + "title": "science 1", + "author": "author 3", + "stock": 30, + "book": "science 1", + "price": 20, + "quantity": 1 + }, + { + "_id": 6, + "category": "science", + "author": "author 4", + "stock": 50, + "title": null + } +] +``` + +**指定多个连接条件** + +假设 `orders` 集合有以下记录: +```js +[ + {"_id":4,"book":"novel 1","price":300,"quantity":20}, + {"_id":5,"book":"science 1","price":20,"quantity":1} +] +``` +`books` 集合有以下记录: +```js +[ + {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, + {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"} +] +``` +以下操作连接 `orders` 和 `books` 集合,要求两个条件: + +- orders 的 book 字段与 books 的 title 字段相等 +- books 的 stock 字段 大于或等于 orders 的 quantity 字段 +```js +const db = cloud.database() +const dbCmd = db.command +const $ = dbCmd.aggregate +let res = await db.collection('orders').aggregate() + .lookup({ + from: 'books', + let: { + order_book: '$book', + order_quantity: '$quantity' + }, + pipeline: $.pipeline() + .match(dbCmd.expr($.and([ + $.eq(['$title', '$$order_book']), + $.gte(['$stock', '$$order_quantity']) + ]))) + .project({ + _id: 0, + title: 1, + author: 1, + stock: 1 + }) + .done(), + as: 'bookList', + }) + .end() +``` +结果: +```js +[ + { + "_id": 4, + "book": "novel 1", + "price": 300, + "quantity": 20, + "bookList": [] + }, + { + "_id": 5, + "book": "science 1", + "price": 20, + "quantity": 1, + "bookList": [ + { + "title": "science 1", + "author": "author 3", + "stock": 30 + } + ] + } +] +``` + +**拼接被连接集合的子查询** + +假设 `orders` 集合有以下记录: +```js +[ + {"_id":4,"book":"novel 1","price":30,"quantity":2}, + {"_id":5,"book":"science 1","price":20,"quantity":1} +] +``` +`books` 集合有以下记录: +```js +[ + {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, + {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, + {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"} +] +``` +在每条输出记录上加上一个数组字段,该数组字段的值是对 books 集合的一个查询语句的结果: +```js +const db = cloud.database() +const $ = db.command.aggregate +let res = await db.collection('orders').aggregate() + .lookup({ + from: 'books', + let: { + order_book: '$book', + order_quantity: '$quantity' + }, + pipeline: $.pipeline() + .match({ + author: 'author 3' + }) + .project({ + _id: 0, + title: 1, + author: 1, + stock: 1 + }) + .done(), + as: 'bookList', + }) + .end() +``` +结果 +```js +[ + { + "_id": 4, + "book": "novel 1", + "price": 30, + "quantity": 20, + "bookList": [ + { + "title": "science 1", + "author": "author 3", + "stock": 30 + }, + { + "title": "science 2", + "author": "author 3", + "stock": 40 + } + ] + }, + { + "_id": 5, + "book": "science 1", + "price": 20, + "quantity": 1, + "bookList": [ + { + "title": "science 1", + "author": "author 3", + "stock": 30 + }, + { + "title": "science 2", + "author": "author 3", + "stock": 40 + } + ] + } +] +``` + +**多表联表查询** + +假设 `orders` 集合有以下记录: +```js +[ + {"_id":4,"book":"book1","price":30,"quantity":2}, + {"_id":5,"book":"book2","price":20,"quantity":1} +] +``` +`books` 集合有以下记录: +```js +[ + {"_id":"book1","author":"author1","title":"novel 1"}, + {"_id":"book2","author":"author2","title":"science 1"}, +] +``` +`authors` 集合有以下记录: +```js +[ + {"_id":"author1","name":"A1"}, + {"_id":"author2","author":"A2"} +] +``` + +如需orders关联books,book再关联authors查询,可以在pipeline内再使用lookup + +```js +const db = cloud.database() +const dbCmd = db.command +const $ = db.command.aggregate +let res = await db.collection('orders').aggregate() + .lookup({ + from: 'books', + let: { + book_id: '$book' + }, + pipeline: $.pipeline() + .match( + dbCmd.expr($.eq(['$_id', '$$book_id'])) + ) + .lookup({ + from: 'authors', + let: { + author_id: '$author' + }, + pipeline: $.pipeline() + .match( + dbCmd.expr($.eq(['$_id', '$$author_id'])) + ) + .done(), + as: 'authorList' + }) + .done(), + as: 'bookList', + }) + .end() + +``` + +结果如下 + +```js +[ + { + "_id":4, + "book":"book1", + "price":30, + "quantity":2, + "bookList": [{ + "_id":"book1", + "author":"author1", + "title":"novel 1", + authorList: [{ + "_id":"author1", + "name":"A1" + }] + }] + }, + { + "_id":5, + "book":"book2", + "price":20, + "quantity":1, + "bookList": [{ + "_id":"book2", + "author":"author2", + "title":"science 1", + authorList: [{ + "_id":"author2", + "name":"A2" + }] + }] + } +] +``` + +## match@aggregate-match + +聚合阶段。根据条件过滤文档,并且把符合条件的文档传递给下一个流水线阶段。 + +**API 说明** + +**match 的形式如下:** +```js +match(<查询条件>) +``` + +查询条件与普通查询一致,可以用普通查询操作符,注意 match 阶段和其他聚合阶段不同,不可使用聚合操作符,只能使用查询操作符。 +```js +// 直接使用字符串 +match({ + name: 'Tony Stark' +}) +``` +```js +// 使用操作符 +const dbCmd = db.command +match({ + age: dbCmd.gt(18) +}) +``` + +**示例** + +假设集合 articles 有如下记录: +```js +{ "_id" : "1", "author" : "stark", "score" : 80 } +{ "_id" : "2", "author" : "stark", "score" : 85 } +{ "_id" : "3", "author" : "bob", "score" : 60 } +{ "_id" : "4", "author" : "li", "score" : 55 } +{ "_id" : "5", "author" : "jimmy", "score" : 60 } +{ "_id" : "6", "author" : "li", "score" : 94 } +{ "_id" : "7", "author" : "justan", "score" : 95 } +``` + +**匹配** + +下面是一个直接匹配的例子: +```js +let res = await db.collection('articles') + .aggregate() + .match({ + author: 'stark' + }) + .end() +``` +这里的代码尝试找到所有 author 字段是 stark 的文章,那么匹配如下: +```js +{ "_id" : "1", "author" : "stark", "score" : 80 } +{ "_id" : "2", "author" : "stark", "score" : 85 } +``` + +**计数** + +match 过滤出文档后,还可以与其他流水线阶段配合使用。 + +比如下面这个例子,我们使用 group 进行搭配,计算 score 字段大于 80 的文档数量: +```js +const dbCmd = db.command +const $ = dbCmd.aggregate +let res = await db.collection('articles') + .aggregate() + .match({ + score: dbCmd.gt(80) + }) + .group({ + _id: null, + count: $.sum(1) + }) + .end() +``` +返回值如下: +```js +{ "_id" : null, "count" : 3 } +``` + +## project@aggregate-project + +聚合阶段。把指定的字段传递给下一个流水线,指定的字段可以是某个已经存在的字段,也可以是计算出来的新字段。 + +**API 说明** + +**project 的形式如下:** +```js +project({ + <表达式> +}) +``` + +表达式可以有以下格式: + +|格式 |说明 | +|---- |---- | +|<字段>: <1 或 true> |指定包含某个已有字段 | +|_id: <0 或 false> |舍弃 _id 字段 | +|<字段>: <表达式> |加入一个新字段,或者重置某个已有字段 | +|<字段>: <0 或 false> |舍弃某个字段(如果你指定舍弃了某个非 _id 字段,那么在此次 project 中,你不能再使用其它表达式) | + +**指定包含字段** + +_id 字段是默认包含在输出中的,除此之外其他任何字段,如果想要在输出中体现的话,必须在 project 中指定; 如果指定包含一个尚不存在的字段,那么 project 会忽略这个字段,不会加入到输出的文档中; + +**指定排除字段** + +如果你在 project 中指定排除某个字段,那么其它字段都会体现在输出中; 如果指定排除的是非 _id 字段,那么在本次 project 中,不能再使用其它表达式; + +**加入新的字段或重置某个已有字段** + +你可以使用一些特殊的表达式加入新的字段,或重置某个已有字段。 + +**多层嵌套的字段** + +有时有些字段处于多层嵌套的底层,我们可以使用点记法: +```js +"contact.phone.number": <1 or 0 or 表达式> +``` +也可以直接使用嵌套的格式: +```js +contact: { phone: { number: <1 or 0 or 表达式> } } +``` + +**示例** + +假设我们有一个 articles 集合,其中含有以下文档: +```js +{ + "_id": 666, + "title": "This is title", + "author": "Nobody", + "isbn": "123456789", + "introduction": "......" +} +``` +**指定包含某些字段** + +下面的代码使用 project,让输出只包含 _id、title 和 author 字段: +```js +let res = await db.collection('articles') + .aggregate() + .project({ + title: 1, + author: 1 + }) + .end() +``` + +输出如下: +```js +{ "_id" : 666, "title" : "This is title", "author" : "Nobody" } +``` + +**去除输出中的 _id 字段** + +_id 是默认包含在输出中的,如果不想要它,可以指定去除它: +```js +let res = await db.collection('articles') + .aggregate() + .project({ + _id: 0, // 指定去除 _id 字段 + title: 1, + author: 1 + }) + .end() +``` +输出如下: +```js +{ "title" : "This is title", "author" : "Nobody" } +``` + +**去除某个非 _id 字段** + +我们还可以指定在输出中去掉某个非 _id 字段,这样其它字段都会被输出: +```js +let res = await db.collection('articles') + .aggregate() + .project({ + isbn: 0, // 指定去除 isbn 字段 + }) + .end() +``` +输出如下,相比输入,没有了 isbn 字段: +```js +{ + "_id" : 666, + "title" : "This is title", + "author" : "Nobody", + "introduction": "......" +} +``` + +**加入计算出的新字段** + +假设我们有一个 students 集合,其中包含以下文档: +```js +{ + "_id": 1, + "name": "小明", + "scores": { + "chinese": 80, + "math": 90, + "english": 70 + } +} +``` +下面的代码,我们使用 project,在输出中加入了一个新的字段 totalScore: +```js +const { sum } = db.command.aggregate +let res = await db.collection('students') + .aggregate() + .project({ + _id: 0, + name: 1, + totalScore: sum([ + "$scores.chinese", + "$scores.math", + "$scores.english" + ]) + }) + .end() +``` +输出为: +```js +{ "name": "小明", "totalScore": 240 } +``` + +**加入新的数组字段** + +假设我们有一个 points 集合,包含以下文档: +```js +{ "_id": 1, "x": 1, "y": 1 } +{ "_id": 2, "x": 2, "y": 2 } +{ "_id": 3, "x": 3, "y": 3 } +``` + +下面的代码,我们使用 project,把 x 和 y 字段,放入到一个新的数组字段 coordinate 中: +```js +let res = await db.collection('points') + .aggregate() + .project({ + coordinate: ["$x", "$y"] + }) + .end() +``` +输出如下: +```js +{ "_id": 1, "coordinate": [1, 1] } +{ "_id": 2, "coordinate": [2, 2] } +{ "_id": 3, "coordinate": [3, 3] } +``` + +## replaceRoot@aggregate-replace-root + +聚合阶段。指定一个已有字段作为输出的根节点,也可以指定一个计算出的新字段作为根节点。 + +**API 说明** + +**replaceRoot 使用形式如下:** +```js +replaceRoot({ + newRoot: <表达式> +}) +``` +表达式格式如下: + +|格式 |说明 | +|---- |---- | +|<字段名> |指定一个已有字段作为输出的根节点(如果字段不存在则报错) | +|<对象> |计算一个新字段,并且把这个新字段作为根节点 | + +**示例** + +**使用已有字段作为根节点** + +假设我们有一个 schools 集合,内容如下: +```js +{ + "_id": 1, + "name": "SFLS", + "teachers": { + "chinese": 22, + "math": 18, + "english": 21, + "other": 123 + } +} +``` +下面的代码使用 replaceRoot,把 teachers 字段作为根节点输出: +```js +let res = await db.collection('schools') + .aggregate() + .replaceRoot({ + newRoot: '$teachers' + }) + .end() +``` +输出如下: +```js +{ + "chinese": 22, + "math": 18, + "english": 21, + "other": 123 +} +``` +**使用计算出的新字段作为根节点** + +假设我们有一个 roles 集合,内容如下: +```js +{ "_id": 1, "first_name": "四郎", "last_name": "黄" } +{ "_id": 2, "first_name": "邦德", "last_name": "马" } +{ "_id": 3, "first_name": "牧之", "last_name": "张" } +``` +下面的代码使用 replaceRoot,把 first_name 和 last_name 拼在一起: +```js +const { concat } = db.command.aggregate +let res = await db.collection('roles') + .aggregate() + .replaceRoot({ + newRoot: { + full_name: concat(['$last_name', '$first_name']) + } + }) + .end() +``` +输出如下: +```js +{ "full_name": "黄四郎" } +{ "full_name": "马邦德" } +{ "full_name": "张牧之" } +``` + +## sample@aggregate-sample + +**注意** + +- 此方法在数据量大的集合高频调用时可能会导致响应缓慢 +- 腾讯云数据库操作有默认20的limit,如需使用sample返回20条以上数据,请额外指定limit,例:`sample({size:40}).limit(40)` + +聚合阶段。随机从文档中选取指定数量的记录。 + +**API 说明** + +**sample 的形式如下:** +```js +sample({ + size: <正整数> +}) +``` +请注意:size 是正整数,否则会出错。 + +**示例** + +假设文档 users 有以下记录: +```js +{ "name": "a" } +{ "name": "b" } +``` + +**随机选取** + +如果现在进行抽奖活动,需要选出一名幸运用户。那么 sample 的调用方式如下: +```js +let res = await db.collection('users') + .aggregate() + .sample({ + size: 1 + }) + .end() +``` + +返回了随机选中的一个用户对应的记录,结果如下: + +```js +{ "_id": "696529e4-7e82-4e7f-812e-5144714edff6", "name": "b" } +``` + +## skip@aggregate-skip + +聚合阶段。指定一个正整数,跳过对应数量的文档,输出剩下的文档。 + +**示例** +```js +let res = await db.collection('users') + .aggregate() + .skip(5) + .end() +``` + +这段代码会跳过查找到的前 5 个文档,并且把剩余的文档输出。 + +## sort@aggregate-sort + +聚合阶段。根据指定的字段,对输入的文档进行排序。 + +**API 说明** + +**形式如下:** +```js +sort({ + <字段名1>: <排序规则>, + <字段名2>: <排序规则>, +}) +``` + +<排序规则>可以是以下取值: + +- 1 代表升序排列(从小到大); +- -1 代表降序排列(从大到小); + +**示例** + +升序/降序排列 + +假设我们有集合 articles,其中包含数据如下: +```js +{ "_id": "1", "author": "stark", "score": 80, "age": 18 } +{ "_id": "2", "author": "bob", "score": 60, "age": 18 } +{ "_id": "3", "author": "li", "score": 55, "age": 19 } +{ "_id": "4", "author": "jimmy", "score": 60, "age": 22 } +{ "_id": "5", "author": "justan", "score": 95, "age": 33 } +``` +```js +let res = await db.collection('articles') + .aggregate() + .sort({ + age: -1, + score: -1 + }) + .end() +``` +上面的代码在 students 集合中进行聚合搜索,并且将结果排序,首先根据 age 字段降序排列,然后再根据 score 字段进行降序排列。 + +输出结果如下: +```js +{ "_id": "5", "author": "justan", "score": 95, "age": 33 } +{ "_id": "4", "author": "jimmy", "score": 60, "age": 22 } +{ "_id": "3", "author": "li", "score": 55, "age": 19 } +{ "_id": "1", "author": "stark", "score": 80, "age": 18 } +{ "_id": "2", "author": "bob", "score": 60, "age": 18 } +``` + +## sortByCount@aggregate-sort-by-count + +聚合阶段。根据传入的表达式,将传入的集合进行分组(group)。然后计算不同组的数量,并且将这些组按照它们的数量进行排序,返回排序后的结果。 + +**API 说明** + +**sortByCount 的调用方式如下:** +```js +sortByCount(<表达式>) +``` + +表达式的形式是:$ + 指定字段。请注意:不要漏写 $ 符号。 + +**示例** + +**统计基础类型** + +假设集合 passages 的记录如下: +```js +{ "category": "Web" } +{ "category": "Web" } +{ "category": "Life" } +``` +下面的代码就可以统计文章的分类信息,并且计算每个分类的数量。即对 category 字段执行 sortByCount 聚合操作。 +```js +let res = await db.collection('passages') + .aggregate() + .sortByCount('$category') + .end() +``` + +返回的结果如下所示:Web 分类下有2篇文章,Life 分类下有1篇文章。 +```js +{ "_id": "Web", "count": 2 } +{ "_id": "Life", "count": 1 } +``` +**解构数组类型** + +假设集合 passages 的记录如下:tags 字段对应的值是数组类型。 +```js +{ "tags": [ "JavaScript", "C#" ] } +{ "tags": [ "Go", "C#" ] } +{ "tags": [ "Go", "Python", "JavaScript" ] } +``` +如何统计文章的标签信息,并且计算每个标签的数量?因为 tags 字段对应的数组,所以需要借助 unwind 操作解构 tags 字段,然后再调用 sortByCount。 + +下面的代码实现了这个功能: +```js +let res = await db.collection('passages') + .aggregate() + .unwind(`$tags`) + .sortByCount(`$tags`) + .end() +``` +返回的结果如下所示: +```js +{ "_id": "Go", "count": 2 } +{ "_id": "C#", "count": 2 } +{ "_id": "JavaScript", "count": 2 } +{ "_id": "Python", "count": 1 } +``` + +## unwind@aggregate-unwind + +聚合阶段。使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 + +**API 说明** + +使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 + +**unwind 有两种使用形式:** + +**参数是一个字段名** +```js +unwind(<字段名>) +``` +**参数是一个对象** +```js +unwind({ + path: <字段名>, + includeArrayIndex: , + preserveNullAndEmptyArrays: +}) +``` + +|字段 |类型 |说明 | +|---- |---- |---- | +|path |string |想要拆分的数组的字段名,需要以 $ 开头。 | +|includeArrayIndex |string |可选项,传入一个新的字段名,数组索引会保存在这个新的字段上。新的字段名不能以 $ 开头。 | +|preserveNullAndEmptyArrays |boolean|如果为 true,那么在 path 对应的字段为 null、空数组或者这个字段不存在时,依然会输出这个文档;如果为 false,unwind 将不会输出这些文档。默认为 false。| + +**示例** + +**拆分数组** + +假设我们有一个 products 集合,包含数据如下: +```js +{ "_id": "1", "product": "tshirt", "size": ["S", "M", "L"] } +{ "_id": "2", "product": "pants", "size": [] } +{ "_id": "3", "product": "socks", "size": null } +{ "_id": "4", "product": "trousers", "size": ["S"] } +{ "_id": "5", "product": "sweater", "size": ["M", "L"] } +``` + +我们根据 size 字段对这些文档进行拆分 +```js +db.collection('products') + .aggregate() + .unwind('$size') + .end() +``` + +输出如下: +```js +{ "_id": "1", "product": "tshirt", "size": "S" } +{ "_id": "1", "product": "tshirt", "size": "M" } +{ "_id": "1", "product": "tshirt", "size": "L" } +{ "_id": "4", "product": "trousers", "size": "S" } +{ "_id": "5", "product": "sweater", "size": "M" } +{ "_id": "5", "product": "sweater", "size": "L" } +``` + +**拆分后,保留原数组的索引** + +我们根据 size 字段对文档进行拆分后,想要保留原数组索引在新的 index 字段中。 +```js +let res = await db.collection('products') + .aggregate() + .unwind({ + path: '$size', + includeArrayIndex: 'index' + }) + .end() +``` +输出如下: +```js +{ "_id": "1", "product": "tshirt", "size": "S", "index": 0 } +{ "_id": "1", "product": "tshirt", "size": "M", "index": 1 } +{ "_id": "1", "product": "tshirt", "size": "L", "index": 2 } +{ "_id": "4", "product": "trousers", "size": "S", "index": 0 } +{ "_id": "5", "product": "sweater", "size": "M", "index": 0 } +{ "_id": "5", "product": "sweater", "size": "L", "index": 1 } +``` + +**保留字段为空的文档** + +注意到我们的集合中有两行特殊的空值数据: +```js +... +{ "_id": "2", "product": "pants", "size": [] } +{ "_id": "3", "product": "socks", "size": null } +... +``` +如果想要在输出中保留 size 为空数组、null,或者 size 字段不存在的文档,可以使用 preserveNullAndEmptyArrays 参数 +```js +let res = await db.collection('products') + .aggregate() + .unwind({ + path: '$size', + preserveNullAndEmptyArrays: true + }) + .end() +``` +输出如下: +```js +{ "_id": "1", "product": "tshirt", "size": "S" } +{ "_id": "1", "product": "tshirt", "size": "M" } +{ "_id": "1", "product": "tshirt", "size": "L" } +{ "_id": "2", "product": "pants", "size": null } +{ "_id": "3", "product": "socks", "size": null } +{ "_id": "4", "product": "trousers", "size": "S" } +{ "_id": "5", "product": "sweater", "size": "M" } +{ "_id": "5", "product": "sweater", "size": "L" } +``` + +## end@aggregate-end + +标志聚合操作定义完成,发起实际聚合操作 + +**返回值** + +Promise.<Object> + +|属性 |类型 |说明 | +|--- |--- |--- | +|list |Array.<any>|聚合结果列表 | + +**示例代码** +```js +const $ = db.command.aggregate +let res = await db.collection('books').aggregate() + .group({ + // 按 category 字段分组 + _id: '$category', + // 让输出的每组记录有一个 avgSales 字段,其值是组内所有记录的 sales 字段的平均值 + avgSales: $.avg('$sales') + }) + .end() +``` diff --git a/docs/uniCloud/cf-database-dbcmd.md b/docs/uniCloud/cf-database-dbcmd.md new file mode 100644 index 0000000000000000000000000000000000000000..45456730ae198c1409cc997c0f68a83eeafdc509 --- /dev/null +++ b/docs/uniCloud/cf-database-dbcmd.md @@ -0,0 +1,1193 @@ +# 云数据库操作符@dbcmd + +## 查询·逻辑操作符@dbcmd-query + +### and + +查询操作符,用于表示逻辑 "与" 的关系,表示需同时满足多个查询筛选条件 + +#### 使用说明 + `and` 有两种使用情况: + + + +**1. 用在根查询条件** + + 此时需传入多个查询条件,表示需同时满足提供的多个完整查询条件 + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where(dbCmd.and([ + { + progress: dbCmd.gt(50) + }, + { + tags: 'cloud' + } +])).get() +``` +但以上用 `and` 组成的查询条件是不必要的,因为传入的对象的各字段隐式组成了 “与” 的关系,上述条件等价于下方更简洁的写法: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.gt(50), + tags: 'cloud' +}).get() +``` + +通常需要显示使用 `and` 是用在有跨字段或操作的时候 + + +**2. 用在字段查询条件** + + 需传入多个查询操作符或常量,表示字段需满足或匹配给定的条件。 + + 如以下用前置写法的方式表示 "progress 字段值大于 50 且小于 100" + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.and(dbCmd.gt(50), dbCmd.lt(100)) +}).get() +``` +还可以用后置写法的方式表示同样的条件: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.gt(50).and(dbCmd.lt(100)) +}).get() +``` +注意 `Command` 默认也可以直接链式调用其他 `Command`,默认表示多个 `Command` 的与操作,因此上述代码还可以精简为: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.gt(50).lt(100) +}).get() +``` + +#### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +```js +// 传入数组 +function and(expressions: Expression[]): Command +// 传入多参数 +function and(...expressions: Expression[]): Command +``` + +### or + +查询操作符,用于表示逻辑 "或" 的关系,表示需同时满足多个查询筛选条件。或指令有两种用法,一是可以进行字段值的 “或” 操作,二是也可以进行跨字段的 “或” 操作。 + + +#### 字段值的或操作 + 字段值的 “或” 操作指的是指定一个字段值为多个值之一即可。 + + 如筛选出进度大于 80 或小于 20 的 todo: + + 流式写法: + + +```js +let res = await const dbCmd = db.command +db.collection('todo').where({ + progress: dbCmd.gt(80).or(dbCmd.lt(20)) +}).get() +``` +前置写法: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.or(dbCmd.gt(80), dbCmd.lt(20)) +}).get() +``` +前置写法也可接收一个数组: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.or([dbCmd.gt(80), dbCmd.lt(20)]) +}).get() +``` + +#### 跨字段的或操作 + 跨字段的 “或” 操作指条件 “或”,相当于可以传入多个 where 语句,满足其中一个即可。 + + 如筛选出进度大于 80 或已标为已完成的 todo: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where(dbCmd.or([ + { + progress: dbCmd.gt(80) + }, + { + done: true + } +])).get() +``` + +#### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +```js +// 传入数组 +function or(expressions: Expression[]): Command +// 传入多参数 +function or(...expressions: Expression[]): Command +``` + +### not + +查询操作符,用于表示逻辑 "非" 的关系,表示需不满足指定的条件。 + + +#### 示例 + 如筛选出进度不等于100的 todo: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.not(dbCmd.eq(100)) +}).get() +``` +`not` 也可搭配其他逻辑指令使用,包括 `and`, `or`, `nor`, `not`,如 `or`: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.not(dbCmd.or([dbCmd.lt(50), dbCmd.eq(100)])) +}).get() +``` + +### nor + +查询操作符,用于表示逻辑 "都不" 的关系,表示需不满足指定的所有条件。如果记录中没有对应的字段,则默认满足条件。 + + +#### 示例 1 + 筛选出进度既不小于20又不大于80的 todo : + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)]) +}).get() +``` +以上同时会筛选出不存在 `progress` 字段的记录,如果要要求 `progress` 字段存在,可以用 `exists` 指令: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where({ + progress: dbCmd.exists().nor([dbCmd.lt(20), dbCmd.gt(80)]) + // 等价于以下非链式调用的写法: + // progress: dbCmd.exists().and(dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)])) +}).get() +``` + +#### 示例 2 + 筛选出 `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串的记录: + + +```js +const dbCmd = db.command +db.collection('todo').where(dbCmd.nor([{ + progress: dbCmd.lt(20), +}, { + tags: 'miniprogram', +}])).get() +``` +以上会筛选出满足以下条件之一的记录: + + +1. `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串 3. `progress` 不小于 20 且 `tags` 字段不存在 5. `progress` 字段不存在 且 `tags` 数组不包含 `miniprogram` 字符串 7. `progress` 不小于 20 且 `tags` 字段不存在 + 如果要求 `progress` 和 `tags` 字段存在,可以用 `exists` 指令: + + +```js +const dbCmd = db.command +let res = await db.collection('todo').where( + dbCmd.nor([{ + progress: dbCmd.lt(20), + }, { + tags: 'miniprogram', + }]) + .and({ + progress: dbCmd.exists(true), + tags: dbCmd.exists(true), + }) +).get() +``` + +#### 调用风格 + 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 + + +```js +// 传入数组 +function nor(expressions: Expression[]): Command +// 传入多参数 +function nor(...expressions: Expression[]): Command +``` + +## 查询·比较操作符@dbcmd-compare + +### eq + +查询筛选条件,表示字段等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 + + +#### 使用说明 + 比如筛选出所有自己发表的文章,除了用传对象的方式: + + +```js +const openID = 'xxx' +let res = await db.collection('articles').where({ + _openid: openID +}).get() +``` +还可以用指令: + + +```js +const dbCmd = db.command +const openID = 'xxx' +let res = await db.collection('articles').where({ + _openid: dbCmd.eq(openid) +}).get() +``` +注意 `eq` 指令比对象的方式有更大的灵活性,可以用于表示字段等于某个对象的情况,比如: + + +```js +// 这种写法表示匹配 stat.publishYear == 2018 且 stat.language == 'zh-CN' +let res = await db.collection('articles').where({ + stat: { + publishYear: 2018, + language: 'zh-CN' + } +}).get() +// 这种写法表示 stat 对象等于 { publishYear: 2018, language: 'zh-CN' } +const dbCmd = db.command +let res = await db.collection('articles').where({ + stat: dbCmd.eq({ + publishYear: 2018, + language: 'zh-CN' + }) +}).get() +``` + +### neq + +查询筛选条件,表示字段不等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 + + +#### 使用说明 + 表示字段不等于某个值,和 [eq](Command.eq.html) 相反 + +### lt + +查询筛选操作符,表示需小于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +#### 示例代码 + 找出进度小于 50 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.lt(50) +}) +.get() +``` + +### lte + +查询筛选操作符,表示需小于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +#### 示例代码 + 找出进度小于或等于 50 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.lte(50) +}) +.get() +``` + +### gt + +查询筛选操作符,表示需大于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +#### 示例代码 + 找出进度大于 50 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.gt(50) +}) +.get() +``` + +### gte + +查询筛选操作符,表示需大于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 + + +#### 示例代码 + 找出进度大于或等于 50 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.gte(50) +}) +.get() +``` + +### in + +查询筛选操作符,表示要求值在给定的数组内。 + + +#### 示例代码 + 找出进度为 0 或 100 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.in([0, 100]) +}) +.get() +``` + +### nin + +查询筛选操作符,表示要求值不在给定的数组内。 + + +#### 示例代码 + 找出进度不是 0 或 100 的 todo + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.nin([0, 100]) +}) +.get() +``` + +## 查询·字段操作符@dbcmd-field + +### exists + +判断字段是否存在 + + +#### 示例代码 + 找出存在 tags 字段的记录 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + tags: dbCmd.exists(true) +}) +.get() +``` + +### mod + +查询筛选操作符,给定除数 divisor 和余数 remainder,要求字段作为被除数时 value % divisor = remainder。 + + +#### 示例代码 + 找出进度为 10 的倍数的字段的记录 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + progress: dbCmd.mod(10, 0) +}) +.get() +``` + +## 查询·数组操作符@dbcmd-array + +### all + +数组查询操作符。用于数组字段的查询筛选条件,要求数组字段中包含给定数组的所有元素。 + + +#### 示例代码 1:普通数组 + 找出 tags 数组字段同时包含 cloud 和 database 的记录 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + tags: dbCmd.all(['cloud', 'database']) +}) +.get() +``` + +#### 示例代码 2:对象数组 + 如果数组元素是对象,则可以用 `dbCmd.elemMatch` 匹配对象的部分字段 + + 假设有字段 `places` 定义如下: + + +```js +{ + "type": string + "area": number + "age": number +} +``` +找出数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素和一个满足 “type 为 mall 且 age 大于 5” 的元素 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + places: dbCmd.all([ + dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }), + dbCmd.elemMatch({ + type: 'mall', + age: dbCmd.gt(5), + }), + ]), +}) +.get() +``` + +### elemMatch + +用于数组字段的查询筛选条件,要求数组中包含至少一个满足 `elemMatch` 给定的所有条件的元素 + + +#### 示例代码:数组是对象数组的情况 + 假设集合示例数据如下: + + +```js +{ + "_id": "a0", + "city": "x0", + "places": [{ + "type": "garden", + "area": 300, + "age": 1 + }, { + "type": "theatre", + "area": 50, + "age": 15 + }] +} +``` +找出 `places` 数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + places: dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) +}) +.get() +``` +*注意**:如果不使用 `elemMatch` 而直接如下指定条件,则表示的是 `places` 数组字段中至少有一个元素的 `area` 字段大于 100 且 `places` 数组字段中至少有一个元素的 `age` 字段小于 2: + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + places: { + area: dbCmd.gt(100), + age: dbCmd.lt(2), + } +}) +.get() +``` + +#### 示例代码:数组元素都是普通数据类型的情况 + 假设集合示例数据如下: + + +```js +{ + "_id": "a0", + "scores": [60, 80, 90] +} +``` +找出 `scores` 数组字段中至少同时包含一个满足 “大于 80 且小于 100” 的元素 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + scores: dbCmd.elemMatch(dbCmd.gt(80).lt(100)) +}) +.get() +``` + +### size + +更新操作符,用于数组字段的查询筛选条件,要求数组长度为给定值 + + +#### 示例 + 找出 tags 数组字段长度为 2 的所有记录 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').where({ + places: dbCmd.size(2) +}) +.get() +``` + +## 查询·地理位置操作符@dbcmd-geo + +### geoNear + +按从近到远的顺序,找出字段值在给定点的附近的记录。 + + +#### 索引要求 + 需对查询字段建立地理位置索引 + + +#### 示例代码 + 找出离给定位置 1 公里到 5 公里范围内的记录 + + +```js +const dbCmd = db.command +let res = await db.collection('restaurants').where({ + location: dbCmd.geoNear({ + geometry: new db.Geo.Point(113.323809, 23.097732), + minDistance: 1000, + maxDistance: 5000, + }) +}).get() +``` + +### geoWithin + +找出字段值在指定区域内的记录,无排序。指定的区域必须是多边形(Polygon)或多边形集合(MultiPolygon)。 + + +#### 索引要求 + 需对查询字段建立地理位置索引 + + +#### 示例代码 1:给定多边形 + +```js +const dbCmd = db.command +const { Point, LineString, Polygon } = db.Geo +let res = await .collection('restaurants').where({ + location: dbCmd.geoWithin({ + geometry: new Polygon([ + new LineString([ + new Point(0, 0), + new Point(3, 2), + new Point(2, 3), + new Point(0, 0) + ]) + ]), + }) +}).get() +``` + +#### 示例代码 2:给定圆形 + 可以不用 `geometry` 而用 `centerSphere` 构建一个圆形。 + + `centerSphere` 对应的值的定义是:`[ [经度, 纬度], 半径 ]` + + 半径需以弧度计,比如需要 10km 的半径,则用距离除以地球半径 6378.1km 得出的数字。 + + +```js +const dbCmd = db.command +let res = await db.collection('restaurants').where({ + location: dbCmd.geoWithin({ + centerSphere: [ + [-88, 30], + 10 / 6378.1, + ] + }) +}).get() +``` + +### geoIntersects + +找出给定的地理位置图形相交的记录 + + +#### 索引要求 + 需对查询字段建立地理位置索引 + + +#### 示例代码:找出和一个多边形相交的记录 + +```js +const dbCmd = db.command +const { Point, LineString, Polygon } = db.Geo +let res = await db.collection('restaurants').where({ + location: dbCmd.geoIntersects({ + geometry: new Polygon([ + new LineString([ + new Point(0, 0), + new Point(3, 2), + new Point(2, 3), + new Point(0, 0) + ]) + ]), + }) +}).get() +``` + +## 查询·表达式操作符@dbcmd-expr + +### expr + +查询操作符,用于在查询语句中使用聚合表达式,方法接收一个参数,该参数必须为聚合表达式 + +#### 使用说明 + +1. `expr` 可用于在聚合 [`match`](../aggregate/Aggregate.match.html) 流水线阶段中引入聚合表达式 3. 如果聚合 [`match`](../aggregate/Aggregate.match.html) 阶段是在 [`lookup`](../aggregate/Aggregate.lookup.html) 阶段内,此时的 `expr` 表达式内可使用 `lookup` 中使用 `let` 参数定义的变量,具体示例可见 [`lookup`](../aggregate/Aggregate.lookup.html) 的 `指定多个连接条件` 例子 5. `expr` 可用在普通查询语句(`where`)中引入聚合表达式 + +#### 示例代码 1:比较同一个记录中的两个字段 + 假设 `items` 集合的数据结构如下: + + +```js +{ + "_id": string, + "inStock": number, // 库存量 + "ordered": number // 被订量 +} +``` +找出被订量大于库存量的记录: + + +```js +const dbCmd = db.command +const $ = dbCmd.aggregate +let res = await db.collection('items').where(dbCmd.expr($.gt(['$ordered', '$inStock']))).get() +``` + +#### 示例代码 2:与条件语句组合使用 + 假设 `items` 集合的数据结构如下: + + +```json +{ + "_id": string, + "price": number +} +``` +假设加个小于等于 10 的打 8 折,大于 10 的打 5 折,让数据库查询返回打折后价格小于等于 8 的记录: + + +```js +const dbCmd = db.command +const $ = dbCmd.aggregate +let res = await db.collection('items').where(dbCmd.expr( + $.lt([ + $.cond({ + if: $.gte(['$price', 10]), + then: $.multiply(['$price', '0.5']), + else: $.multiply(['$price', '0.8']), + }) + , + 8 + ]) +).get() +``` + +## 更新·字段操作符@dbcmd-update-field + +### set + +更新操作符,用于设定字段等于指定值。 + + +#### 使用说明 + 这种方法相比传入纯 JS 对象的好处是能够指定字段等于一个对象 + + +#### 示例 + +```js +// 以下方法只会更新 style.color 为 red,而不是将 style 更新为 { color: 'red' },即不影响 style 中的其他字段 +let res = await db.collection('todos').doc('doc-id').update({ + style: { + color: 'red' + } +}) + +// 以下方法更新 style 为 { color: 'red', size: 'large' } +let res = await db.collection('todos').doc('doc-id').update({ + style: dbCmd.set({ + color: 'red', + size: 'large' + }) +}) +``` + +### remove + +更新操作符,用于表示删除某个字段。 + + +#### 示例代码 + 删除 style 字段: + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('todo-id').update({ + style: dbCmd.remove() +}) +``` + +### inc + +更新操作符,原子操作,用于指示字段自增 + + +#### 原子自增 + 多个用户同时写,对数据库来说都是将字段自增,不会有后来者覆写前者的情况 + + +#### 示例代码 + 将一个 todo 的进度自增 10: + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('todo-id').update({ + progress: dbCmd.inc(10) +}) +``` + +### mul + +更新操作符,原子操作,用于指示字段自乘某个值 + + +#### 原子自乘 + 多个用户同时写,对数据库来说都是将字段自乘,不会有后来者覆写前者的情况 + + +#### 示例代码 + 将一个 todo 的进度自乘 10: + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('todo-id').update({ + progress: dbCmd.mul(10) +}) +``` + +### min + +更新操作符,给定一个值,只有该值小于字段当前值才进行更新。 + + +#### 示例代码 + 如果字段 progress > 50,则更新到 50 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + progress: dbCmd.min(50) +}) +``` + +### max + +更新操作符,给定一个值,只有该值大于字段当前值才进行更新。 + + +#### 示例代码 + 如果字段 progress < 50,则更新到 50 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + progress: dbCmd.max(50) +}) +``` + +### rename + +更新操作符,字段重命名。如果需要对嵌套深层的字段做重命名,需要用点路径表示法。不能对嵌套在数组里的对象的字段进行重命名。 + + +#### 示例 1:重命名顶层字段 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + progress: dbCmd.rename('totalProgress') +}) +``` + +#### 示例 2:重命名嵌套字段 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + someObject: { + someField: dbCmd.rename('someObject.renamedField') + } +}) +``` + +或: + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + 'someObject.someField': dbCmd.rename('someObject.renamedField') +}) +``` + +## 更新·数组操作符@dbcmd-update-array + +### push + +数组更新操作符。对一个值为数组的字段,往数组添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 + + +#### 参数说明 + + +**position 说明** + + 要求必须同时有 `each` 参数存在。 + + 非负数代表从数组开始位置数的位置,从 0 开始计。如果数值大于等于数组长度,则视为在尾部添加。负数代表从数组尾部倒数的位置,比如 -1 就代表倒数第二个元素的位置。如果负数数值的绝对值大于等于数组长度,则视为从数组头部添加。 + + + +**sort 说明** + + 要求必须同时有 `each` 参数存在。给定 1 代表升序,-1 代表降序。 + + 如果数组元素是记录,则用 `{ <字段>: 1 | -1 }` 的格式表示根据记录中的什么字段做升降序排序。 + + + +**slice** 说明** + + 要求必须同时有 `each` 参数存在 + +|值 |说明 | +|:-: |:-: | +|0 |将字段更新为空数组 | +|正数 |数组只保留前 n 个元素| +|负数 |数组只保留后 n 个元素| + + +#### 示例 1:尾部添加元素 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push(['mini-program', 'cloud']) +}) +``` + +#### 示例 2:从第二个位置开始插入 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + position: 1, + }) +}) +``` + +#### 示例 3:排序 + +插入后对整个数组做排序 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + sort: 1, + }) +}) +``` + +不插入,只对数组做排序 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: [], + sort: 1, + }) +}) +``` +如果字段是对象数组,可以如下根据元素对象里的字段进行排序: + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: [ + { name: 'miniprogram', weight: 8 }, + { name: 'cloud', weight: 6 }, + ], + sort: { + weight: 1, + }, + }) +}) +``` + +#### 示例 4:截断保留 + 插入后只保留后 2 个元素 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + slice: -2, + }) +}) +``` + +#### 示例 5:在指定位置插入、然后排序、最后只保留前 2 个元素 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.push({ + each: ['mini-program', 'cloud'], + position: 1, + slice: 2, + sort: 1, + }) +}) +``` + +### pop + +数组更新操作符,对一个值为数组的字段,将数组尾部元素删除,仅可以删除末尾一个 + +#### 示例代码 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.pop() +}) +``` + +### unshift + +数组更新操作符,对一个值为数组的字段,往数组头部添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 + + +#### 示例代码 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.unshift(['mini-program', 'cloud']) +}) +``` + +### shift + +数组更新操作符,对一个值为数组的字段,将数组头部元素删除。 + + +#### 示例代码 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.shift() +}) +``` + +### pull + +数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值或查询条件的元素都移除掉。 + +#### 示例代码 1:根据常量匹配移除 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.pull('database') +}) +``` + +#### 示例代码 2:根据查询条件匹配移除 + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.pull(dbCmd.in(['database', 'cloud'])) +}) +``` + +#### 示例代码 3:对象数组时,根据查询条件匹配移除 + 假设有字段 `places` 数组中的元素结构如下 + + +```json +{ + "type": string + "area": number + "age": number +} +``` + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + places: dbCmd.pull({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) +}) +``` + +#### 示例代码 4:有嵌套对象的对象数组时,根据查询条件匹配移除 + 假设有字段 `cities` 数组中的元素结构如下 + + +```json +{ + "name": string + "places": Place[] +} +``` +`Place` 结构如下: + + +```json +{ + "type": string + "area": number + "age": number +} +``` +可用 `elemMatch` 匹配嵌套在对象数组里面的对象数组字段 places + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + cities: dbCmd.pull({ + places: dbCmd.elemMatch({ + area: dbCmd.gt(100), + age: dbCmd.lt(2), + }) + }) +}) +``` + +### pullAll + +数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值的元素都移除掉。跟 `pull` 的差别在于只能指定常量值、传入的是数组。 + + +#### 示例代码:根据常量匹配移除 + 从 tags 中移除所有 database 和 cloud 字符串 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.pullAll(['database', 'cloud']) +}) +``` + +### addToSet + +数组更新操作符。原子操作。给定一个或多个元素,除非数组中已存在该元素,否则添加进数组。 + + +#### 示例代码 1:添加一个元素 + 如果 tags 数组中不包含 database,添加进去 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.addToSet('database') +}) +``` + +#### 示例代码 2:添加多个元素 + +需传入一个对象,其中有一个字段 `each`,其值为数组,每个元素就是要添加的元素 + + +```js +const dbCmd = db.command +let res = await db.collection('todos').doc('doc-id').update({ + tags: dbCmd.addToSet({ + $each: ['database', 'cloud'] + }) +}) +``` diff --git a/docs/uniCloud/cf-database.md b/docs/uniCloud/cf-database.md index be1b3eea738cb7c1a87ff70e2dadb5df5f6aa058..e46fb70aa61583b70934208d88112fdf69abffba 100644 --- a/docs/uniCloud/cf-database.md +++ b/docs/uniCloud/cf-database.md @@ -205,7 +205,7 @@ db.collection('goods').where({ 在SQL里使用字符串表达式操作。但在NOSQL中使用json操作。这使得 等于 的表达,从 `=` 变成了 `:`;而大于的表达,从 `>` 变成了 `dbCmd.gt()` -所有的比较符,详见[表格](https://uniapp.dcloud.io/uniCloud/cf-database?id=%e6%9f%a5%e8%af%a2%e7%ad%9b%e9%80%89%e6%8c%87%e4%bb%a4-query-command) +所有的比较符,详见[表格](https://uniapp.dcloud.io/uniCloud/cf-database-dbcmd?id=%e6%9f%a5%e8%af%a2%e7%ad%9b%e9%80%89%e6%8c%87%e4%bb%a4-query-command) `where` 还可以使用正则表达式来查询文档,比如一下示例查询所有`name`字段以ABC开头的用户 @@ -1988,8096 +1988,3 @@ exports.main = async (event) => { --> -## 聚合操作@aggregate - -有时候我们需要对数据进行分析操作,比如一些统计操作、联表查询等,这个时候简单的查询操作就搞不定这些需求,因此就需要使用聚合操作来完成。 - -获取数据库集合的聚合操作实例 - -```js -db.collection('scores').aggregate() -``` - -**注意:** - -- 聚合操作实例仅用于查询,不可执行增删改操作。在聚合操作实例上只能使用聚合操作方法,不能使用where/orderBy等基础方法,where需改为match,orderBy应使用sort实现,细节请阅读下方聚合操作文档。 -- 聚合操作在大数据量下性能不如简单查询,请根据自身业务选择合适的用法 - -云函数中使用时切勿复用aggregate实例,容易引发Bug。 - -以下两种写法均为错误示例: - -```js -const db = uniCloud.database() -const collection = db.collection('test') -const aggregate = collection.aggregate() // 云函数实例复用时,此聚合实例也会复用,导致Bug -exports.main = async function(){ - const res = await aggregate.match({a:1}).end() - return {res} -} -``` - -```js -const db = uniCloud.database() -const collection = db.collection('test') -exports.main = async function(){ - const aggregate = collection.aggregate() // 此聚合实例分别在两个请求内使用,导致Bug - const res1 = await aggregate.match({a:1}).end() - const res2 = await aggregate.match({a:2}).end() - return {res1, res2} -} -``` - -### 聚合表达式@aggregate-expression - -表达式可以是字段路径、常量、或聚合操作符。表达式可以嵌套表达式。 - -**字段路径** - -表达式用字段路径表示法来指定记录中的字段。字段路径的表示由一个 `$` 符号加上字段名或嵌套字段名。嵌套字段名用点将嵌套的各级字段连接起来。如 `$profile` 就表示 `profile` 的字段路径,`$profile.name` 就表示 `profile.name` 的字段路径(`profile` 字段中嵌套的 `name` 字段)。 - -例如:现有以下数据 - -```json -[{ - "profile": { - "name": "Chloe" - }, - "status": 0 -}] -``` - -```js -// 执行以下操作 -let res = await db.collection('scores').aggregate() - .addFields({ - name: '$profile.name' - }) - .end() - -// 返回值为 -{ - "data": [{ - "profile": { - "name": "Chloe" - }, - "status": 0, - "name": "Chloe" - }] -} -``` - - -**常量** - -常量可以是任意类型。默认情况下 $ 开头的字符串都会被当做字段路径处理,如果想要避免这种行为,使用 `db.command.aggregate.literal` 声明为常量。 - -**聚合操作符** - -参考[聚合操作符](#aggregate-operator) - -### addFields@aggregate-add-fields - -聚合阶段。添加新字段到输出的记录。经过 `addFields` 聚合阶段,输出的所有记录中除了输入时带有的字段外,还将带有 `addFields` 指定的字段。 - - -**API 说明** - -`addFields` 等同于同时指定了所有已有字段和新增字段的 `project` 阶段。 - -**`addFields` 的形式如下:** -```js -addFields({ - <新字段>: <表达式> -}) -``` -`addFields` 可指定多个新字段,每个新字段的值由使用的表达式决定。 - -如果指定的新字段与原有字段重名,则新字段的值会覆盖原有字段的值。注意 `addFields` 不能用来给数组字段添加元素。 - -**示例 1:连续两次 addFields** - -假设集合 scores 有如下记录: -```js -{ - _id: 1, - student: "Maya", - homework: [ 10, 5, 10 ], - quiz: [ 10, 8 ], - extraCredit: 0 -} -{ - _id: 2, - student: "Ryan", - homework: [ 5, 6, 5 ], - quiz: [ 8, 8 ], - extraCredit: 8 -} -``` -应用两次 `addFields`,第一次增加两个字段分别为 `homework` 和 `quiz` 的和值,第二次增加一个字段再基于上两个和值求一次和值。 -```js -const $ = db.command.aggregate -let res = await db.collection('scores').aggregate() - .addFields({ - totalHomework: $.sum('$homework'), - totalQuiz: $.sum('$quiz') - }) - .addFields({ - totalScore: $.add(['$totalHomework', '$totalQuiz', '$extraCredit']) - }) - .end() -``` - -返回结果如下: -```js -{ - "_id" : 1, - "student" : "Maya", - "homework" : [ 10, 5, 10 ], - "quiz" : [ 10, 8 ], - "extraCredit" : 0, - "totalHomework" : 25, - "totalQuiz" : 18, - "totalScore" : 43 -} -{ - "_id" : 2, - "student" : "Ryan", - "homework" : [ 5, 6, 5 ], - "quiz" : [ 8, 8 ], - "extraCredit" : 8, - "totalHomework" : 16, - "totalQuiz" : 16, - "totalScore" : 40 -} -``` - -**示例 2:在嵌套记录里增加字段** - -可以用点表示法在嵌套记录里增加字段。假设 vehicles 集合含有如下记录: -```js -{ _id: 1, type: "car", specs: { doors: 4, wheels: 4 } } -{ _id: 2, type: "motorcycle", specs: { doors: 0, wheels: 2 } } -{ _id: 3, type: "jet ski" } -``` -可以用如下操作在 `specs` 字段下增加一个新的字段 `fuel_type`,值都设为固定字符串 `unleaded`: -```js -let res = await db.collection('vehicles').aggregate() - .addFields({ - 'specs.fuel_type': 'unleaded' - }) - .end() -``` - -返回结果如下: -```js -{ _id: 1, type: "car", - specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } } -{ _id: 2, type: "motorcycle", - specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } } -{ _id: 3, type: "jet ski", - specs: { fuel_type: "unleaded" } } -``` - -**示例 3:设置字段值为另一个字段** - -可以通过 `$` 加字段名组成的字符串作为值的表达式来设置字段的值为另一个字段的值。 - -同样用上一个集合示例,可以用如下操作添加一个字段 `vehicle_type`,将其值设置为 `type` 字段的值: -```js -let res = await db.collection('vehicles').aggregate() - .addFields({ - vehicle_type: '$type' - }) - .end() -``` -返回结果如下: -```js -{ _id: 1, type: "car", vehicle_type: "car", - specs: { doors: 4, wheels: 4, fuel_type: "unleaded" } } -{ _id: 2, type: "motorcycle", vehicle_type: "motorcycle", - specs: { doors: 0, wheels: 2, fuel_type: "unleaded" } } -{ _id: 3, type: "jet ski", vehicle_type: "jet ski", - specs: { fuel_type: "unleaded" } } -``` - -### bucket@aggregate-bucket - -聚合阶段。将输入记录根据给定的条件和边界划分成不同的组,每组即一个 `bucket`。 - -**API 说明** - -每组分别作为一个记录输出,包含一个以下界为值的 `_id` 字段和一个以组中记录数为值的 `count` 字段。`count` 在没有指定 `output` 的时候是默认输出的。 - -`bucket` 只会在组内有至少一个记录的时候输出。 - -**bucket 的形式如下:** -```js -bucket({ - groupBy: , - boundaries: [, , ...], - default: , - output: { - : , - ... - : - } -}) -``` -`groupBy` 是一个用以决定分组的表达式,会应用在各个输入记录上。可以用 `$` 前缀加上要用以分组的字段路径来作为表达式。除非用 `default` 指定了默认值,否则每个记录都需要包含指定的字段,且字段值必须在 `boundaries` 指定的范围之内。 - -`boundaries` 是一个数组,每个元素分别是每组的下界。必须至少指定两个边界值。数组值必须是同类型递增的值。 - -`default` 可选,指定之后,没有进入任何分组的记录将都进入一个默认分组,这个分组记录的 `_id` 即由 `default` 决定。`default` 的值必须小于 `boundaries` 中的最小值或大于等于其中的最大值。`default` 的值可以与 `boundaries` 元素值类型不同。 - -`output` 可选,用以决定输出记录除了 `_id` 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 `output` 指定时,默认的 `count` 是不会被默认输出的,必须手动指定: -```js -output: { - count: $.sum(1), - ... - : -} -``` -使用 bucket 需要满足以下至少一个条件,否则会抛出错误: - -每一个输入记录应用 groupBy 表达式获取的值都必须是一个在 boundaries 内的值 - -指定一个 default 值,该值在 boundaries 以外,或与 boundaries 元素的值不同的类型。 - -**示例** - -假设集合 items 有如下记录: -```js -{ - _id: "1", - price: 10 -} -{ - _id: "2", - price: 50 -} -{ - _id: "3", - price: 20 -} -{ - _id: "4", - price: 80 -} -{ - _id: "5", - price: 200 -} -``` - -对上述记录进行分组,将 [0, 50) 分为一组,[50, 100) 分为一组,其他分为一组: - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .bucket({ - groupBy: '$price', - boundaries: [0, 50, 100], - default: 'other', - output: { - count: $.sum(1), - ids: $.push('$_id') - } - }) - .end() -``` - -返回结果如下: - -```js -[ - { - "_id": 0, - "count": 2, - "ids": [ - "1", - "3" - ] - }, - { - "_id": 50, - "count": 2, - "ids": [ - "2", - "4" - ] - }, - { - "_id": "other", - "count": 1, - "ids": [ - "5" - ] - } -] -``` - -### bucketAuto@aggregate-bucket-auto - -聚合阶段。将输入记录根据给定的条件划分成不同的组,每组即一个 `bucket`。与 `bucket` 的其中一个不同之处在于无需指定 `boundaries`,`bucketAuto` 会自动尝试将记录尽可能平均的分散到每组中。 - -**API 说明** -每组分别作为一个记录输出,包含一个以包含组中最大值和最小值两个字段的对象为值的 _id 字段和一个以组中记录数为值的 count 字段。count 在没有指定 output 的时候是默认输出的。 - -**bucketAuto 的形式如下:** -```js -bucketAuto({ - groupBy: , - buckets: , - granularity: , - output: { - : , - ... - : - } -}) -``` -`groupBy` 是一个用以决定分组的表达式,会应用在各个输入记录上。可以用 $ 前缀加上要用以分组的字段路径来作为表达式。除非用 `default` 指定了默认值,否则每个记录都需要包含指定的字段,且字段值必须在 `boundaries` 指定的范围之内。 - -`buckets` 是一个用于指定划分组数的正整数。 - -`granularity` 是可选枚举值字符串,用于保证自动计算出的边界符合给定的规则。这个字段仅可在所有 `groupBy` 值都是数字并且没有 `NaN` 的情况下使用。枚举值包括:`R5、R10、R20、R40、R80、1-2-5、E6、E12、E24、E48、E96、E192、POWERSOF2`。 - -`output` 可选,用以决定输出记录除了 `_id` 外还要包含哪些字段,各个字段的值必须用累加器表达式指定。当 `output` 指定时,默认的 `count` 是不会被默认输出的,必须手动指定: -```js -output: { - count: $.sum(1), - ... - : -} -``` -在以下情况中,输出的分组可能会小于给定的组数: - -输入记录数少于分组数 -- `groupBy` 计算得到的唯一值少于分组数 -- `granularity` 的间距少于分组数 -- `granularity` 不够精细以至于不能平均分配到各组 - -**granularity 详细说明** - -`granularity` 用于保证边界值属于一个给定的数字序列。 - -**Renard 序列** - -Renard 序列是以 10 的 5 / 10 / 20 / 40 / 80 次方根来推导的、在 1.0 到 10.0 (如果是 R80 则是 10.3) 之间的数字序列。 - -设置 granularity 为 R5 / R10 / R20 / R40 / R80 就把边界值限定在序列内。如果 groupBy 的值不在 1.0 到 10.0 (如果是 R80 则是 10.3) 内,则序列数字会自动乘以 10。 - -**E 序列** - -E 序列是以 10 的 6 / 12 / 24 / 48 / 96 / 192 次方跟来推导的、带有一个特定误差的、在 1.0 到 10.0 之间的数字序列。 - -**1-2-5 序列** - -1-2-5 序列 表现与三值 Renard 序列一样。 - -**2的次方序列** - -由 2 的各次方组成的序列数字。 - -**示例** - -假设集合 items 有如下记录: -```js -{ - _id: "1", - price: 10.5 -} -{ - _id: "2", - price: 50.3 -} -{ - _id: "3", - price: 20.8 -} -{ - _id: "4", - price: 80.2 -} -{ - _id: "5", - price: 200.3 -} -``` -对上述记录进行自动分组,分成三组: -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .bucketAuto({ - groupBy: '$price', - buckets: 3, - }) - .end() -``` -返回结果如下: -```js -{ - "_id": { - "min": 10.5, - "max": 50.3 - }, - "count": 2 -} -{ - "_id": { - "min": 50.3, - "max": 200.3 - }, - "count": 2 -} -{ - "_id": { - "min": 200.3, - "max": 200.3 - }, - "count": 1 -} -``` - -### count@aggregate-count - -聚合阶段。计算上一聚合阶段输入到本阶段的记录数,输出一个记录,其中指定字段的值为记录数。 - -**API 说明** - -**count 的形式如下:** -```js -count() -``` -`` 是输出记录数的字段的名字,不能是空字符串,不能以 $ 开头,不能包含 . 字符。 - -count 阶段等同于 group + project 的操作: -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .group({ - _id: null, - count: $.sum(1), - }) - .project({ - _id: 0, - }) - .end() -``` -上述操作会输出一个包含 count 字段的记录。 - -**示例** - -假设集合 items 有如下记录: -```js -{ - _id: "1", - price: 10.5 -} -{ - _id: "2", - price: 50.3 -} -{ - _id: "3", - price: 20.8 -} -{ - _id: "4", - price: 80.2 -} -{ - _id: "5", - price: 200.3 -} -``` -找出价格大于 50 的记录数: -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .match({ - price: $.gt(50) - }) - .count('expensiveCount') - .end() -``` -返回结果如下: -```js -{ - "expensiveCount": 3 -} -``` - -### geoNear@aggregate-geo-near - -聚合阶段。将记录按照离给定点从近到远输出。 - -|属性 |类型 |默认值 |必填 |说明 | -|---- |---- |---- |---- |---- | -|near |GeoPoint | |是 |GeoJSON Point,用于判断距离的点 | -|spherical |true | |是 |必填,值为 true | -|maxDistance |number | |否 |距离最大值 | -|minDistance |number | |否 |距离最小值 | -|query |Object | |否 |要求记录必须同时满足该条件(语法同 where) | -|distanceMultiplier |number | |否 |返回时在距离上乘以该数字 | -|distanceField |string | |是 |存放距离的输出字段名,可以用点表示法表示一个嵌套字段 | -|includeLocs |string | |否 |列出要用于距离计算的字段,如果记录中有多个字段都是地理位置时有用 | -|key |string | |否 |选择要用的地理位置索引。如果集合由多个地理位置索引,则必须指定一个,指定的方式是指定对应的字段 | - -**API 说明** - -- `geoNear` 必须为第一个聚合阶段 -- 必须有地理位置索引。如果有多个,必须用 `key` 参数指定要使用的索引。 - -**示例** - -假设集合 attractions 有如下记录: -```js -{ - "_id": "geoNear.0", - "city": "Guangzhou", - "docType": "geoNear", - "location": { - "type": "Point", - "coordinates": [ - 113.30593, - 23.1361155 - ] - }, - "name": "Canton Tower" -}, -{ - "_id": "geoNear.1", - "city": "Guangzhou", - "docType": "geoNear", - "location": { - "type": "Point", - "coordinates": [ - 113.306789, - 23.1564721 - ] - }, - "name": "Baiyun Mountain" -}, -{ - "_id": "geoNear.2", - "city": "Beijing", - "docType": "geoNear", - "location": { - "type": "Point", - "coordinates": [ - 116.3949659, - 39.9163447 - ] - }, - "name": "The Palace Museum" -}, -{ - "_id": "geoNear.3", - "city": "Beijing", - "docType": "geoNear", - "location": { - "type": "Point", - "coordinates": [ - 116.2328567, - 40.242373 - ] - }, - "name": "Great Wall" -} -``` -```js -const $ = db.command.aggregate -let res = await db.collection('attractions').aggregate() - .geoNear({ - distanceField: 'distance', // 输出的每个记录中 distance 即是与给定点的距离 - spherical: true, - near: new db.Geo.Point(113.3089506, 23.0968251), - query: { - docType: 'geoNear', - }, - key: 'location', // 若只有 location 一个地理位置索引的字段,则不需填 - includeLocs: 'location', // 若只有 location 一个是地理位置,则不需填 - }) - .end() -``` - -返回结果如下: -```js -{ - "_id": "geoNear.0", - "location": { - "type": "Point", - "coordinates": [ - 113.30593, - 23.1361155 - ] - }, - "docType": "geoNear", - "name": "Canton Tower", - "city": "Guangzhou", - "distance": 4384.68131486958 -}, -{ - "_id": "geoNear.1", - "city": "Guangzhou", - "location": { - "type": "Point", - "coordinates": [ - 113.306789, - 23.1564721 - ] - }, - "docType": "geoNear", - "name": "Baiyun Mountain", - "distance": 6643.521654040738 -}, -{ - "_id": "geoNear.2", - "docType": "geoNear", - "name": "The Palace Museum", - "city": "Beijing", - "location": { - "coordinates": [ - 116.3949659, - 39.9163447 - ], - "type": "Point" - }, - "distance": 1894750.4414538583 -}, -{ - "_id": "geoNear.3", - "docType": "geoNear", - "name": "Great Wall", - "city": "Beijing", - "location": { - "type": "Point", - "coordinates": [ - 116.2328567, - 40.242373 - ] - }, - "distance": 1928300.3308822548 -} -``` - -### group@aggregate-group - -聚合阶段。将输入记录按给定表达式分组,输出时每个记录代表一个分组,每个记录的 _id 是区分不同组的 key。输出记录中也可以包括累计值,将输出字段设为累计值即会从该分组中计算累计值。 - -使用group可以很方便的实现类似SQL的distinct功能 - -**API 说明** - -**group 的形式如下:** -```js -group({ - _id: , - : , - ... - : -}) -``` - -`_id` 参数是必填的,如果填常量则只有一组。其他字段是可选的,都是累计值,用 `$.sum` 等累计器(`const $ = db.command.aggregate`),但也可以使用其他表达式。 - -累计器必须是以下操作符之一: - -详细使用方法见[累计器操作符](#累计器操作符) - -|操作符 |说明 | -|---- |---- | -|addToSet |向数组中添加值,如果数组中已存在该值,不执行任何操作 | -|avg |返回一组集合中,指定字段对应数据的平均值 | -|sum |计算并且返回一组字段所有数值的总和 | -|first |返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 | -|last |返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( sort )后,此操作才有意义。 | -|max |返回一组数值的最大值 | -|min |返回一组数值的最小值 | -|push |在 group 阶段,返回一组中表达式指定列与对应的值,一起组成的数组 | -|stdDevPop |返回一组字段对应值的标准差 | -|stdDevSamp |计算输入值的样本标准偏差。如果输入值代表数据总体,或者不概括更多的数据,请改用 db.command.aggregate.stdDevPop| -|mergeObjects |将多个文档合并为单个文档 | - -**内存限制** - -该阶段有 100M 内存使用限制。 - -**示例 1:按字段值分组** - -假设集合 avatar 有如下记录: -```js -{ - _id: "1", - alias: "john", - region: "asia", - scores: [40, 20, 80], - coins: 100 -} -{ - _id: "2", - alias: "arthur", - region: "europe", - scores: [60, 90], - coins: 20 -} -{ - _id: "3", - alias: "george", - region: "europe", - scores: [50, 70, 90], - coins: 50 -} -{ - _id: "4", - alias: "john", - region: "asia", - scores: [30, 60, 100, 90], - coins: 40 -} -{ - _id: "5", - alias: "george", - region: "europe", - scores: [20], - coins: 60 -} -{ - _id: "6", - alias: "john", - region: "asia", - scores: [40, 80, 70], - coins: 120 -} -``` -```js -const $ = db.command.aggregate -let res = await db.collection('avatar').aggregate() - .group({ - _id: '$alias', - num: $.sum(1) - }) - .end() -``` -返回结果如下: -```js -{ - "_id": "john", - "num": 3 -} -{ - "_id": "authur", - "num": 1 -} -{ - "_id": "george", - "num": 2 -} -``` - -**示例 2:按多个值分组** - -可以给 _id 传入记录的方式按多个值分组。还是沿用上面的示例数据,按各个区域(region)获得相同最高分(score)的来分组,并求出各组虚拟币(coins)的总量: -```js -const $ = db.command.aggregate -let res = await db.collection('avatar').aggregate() - .group({ - _id: { - region: '$region', - maxScore: $.max('$scores') - }, - totalCoins: $.sum('$coins') - }) - .end() -``` -返回结果如下: -```js -{ - "_id": { - "region": "asia", - "maxScore": 80 - }, - "totalCoins": 220 -} -{ - "_id": { - "region": "asia", - "maxScore": 100 - }, - "totalCoins": 40 -} -{ - "_id": { - "region": "europe", - "maxScore": 90 - }, - "totalCoins": 70 -} -{ - "_id": { - "region": "europe", - "maxScore": 20 - }, - "totalCoins": 60 -} -``` - -### limit@aggregate-limit - -聚合阶段。限制输出到下一阶段的记录数。 - -**示例** - -假设集合 items 有如下记录: -```js -{ - _id: "1", - price: 10 -} -{ - _id: "2", - price: 50 -} -{ - _id: "3", - price: 20 -} -{ - _id: "4", - price: 80 -} -{ - _id: "5", - price: 200 -} -``` -返回价格大于 20 的记录的最小的两个记录: -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .match({ - price: $.gt(20) - }) - .sort({ - price: 1, - }) - .limit(2) - .end() -``` -返回结果如下: -```js -{ - "_id": "2", - "price": 50 -} -{ - "_id": "4", - "price": 80 -} -``` - -### lookup@aggregate-lookup - -聚合阶段。联表查询。与同个数据库下的一个指定的集合做 left outer join(左外连接)。对该阶段的每一个输入记录,lookup 会在该记录中增加一个数组字段,该数组是被联表中满足匹配条件的记录列表。lookup 会将连接后的结果输出给下个阶段。 - -**API 说明** - -`lookup` 有两种使用方式 - -#### 相等匹配 -将输入记录的一个字段和被连接集合的一个字段进行相等匹配时,采用以下定义: -```js -lookup({ - from: <要连接的集合名>, - localField: <输入记录的要进行相等匹配的字段>, - foreignField: <被连接集合的要进行相等匹配的字段>, - as: <输出的数组字段名> -}) -``` - -**参数详细说明** - -|参数字段 |说明 | -|---- |---- | -|from |要进行连接的另外一个集合的名字 | -|localField |当前流水线的输入记录的字段名,该字段将被用于与 from 指定的集合的 foreignField 进行相等匹配。如果输入记录中没有该字段,则该字段的值在匹配时会被视作 null| -|foreignField |被连接集合的字段名,该字段会被用于与 localField 进行相等匹配。如果被连接集合的记录中没有该字段,该字段的值将在匹配时被视作 null | -|as |指定连接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。如果输入记录中本来就已有该字段,则该字段会被覆写 | - -这个操作等价于以下伪 SQL 操作: - -``` -SELECT *, -FROM collection -WHERE IN (SELECT * - FROM - WHERE = ); -``` - -**例子:** - -- 指定一个相等匹配条件 -- 对数组字段应用相等匹配 -- 组合 mergeObjects 应用相等匹配 - -#### 自定义连接条件、拼接子查询 - -如果需要指定除相等匹配之外的连接条件,或指定多个相等匹配条件,或需要拼接被连接集合的子查询结果,那可以使用如下定义: -```js -lookup({ - from: <要连接的集合名>, - let: { <变量1>: <表达式1>, ..., <变量n>: <表达式n> }, - pipeline: [ <在要连接的集合上进行的流水线操作> ], - as: <输出的数组字段名> -}) -``` - -**参数详细说明** - -|参数字段 |说明 | -|---- |---- | -|from |要进行连接的另外一个集合的名字 | -|let |可选。指定在 pipeline 中可以使用的变量,变量的值可以引用输入记录的字段,比如 let: { userName: '$name' } 就代表将输入记录的 name 字段作为变量 userName 的值。在 pipeline 中无法直接访问输入记录的字段,必须通过 let 定义之后才能访问,访问的方式是在 expr 操作符中用 $$变量名 的方式访问,比如 $$userName。 | -|pipeline |指定要在被连接集合中运行的聚合操作。如果要返回整个集合,则该字段取值空数组 []。在 pipeline 中无法直接访问输入记录的字段,必须通过 let 定义之后才能访问,访问的方式是在 expr 操作符中用 $$变量名 的方式访问,比如 $$userName。 | -|as |指定连接匹配出的记录列表要存放的字段名,这个数组包含的是匹配出的来自 from 集合的记录。如果输入记录中本来就已有该字段,则该字段会被覆写 | - -该操作等价于以下伪 SQL 语句: -``` -SELECT *, -FROM collection -WHERE IN (SELECT - FROM - WHERE ); -``` - -**例子** - -- 指定多个连接条件 -- 拼接被连接集合的子查询 - -**示例** - -**指定一个相等匹配条件** - -假设 orders 集合有以下记录: -```js -[ - {"_id":4,"book":"novel 1","price":30,"quantity":2}, - {"_id":5,"book":"science 1","price":20,"quantity":1}, - {"_id":6} -] -``` -books 集合有以下记录: -```js -[ - {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, - {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, - {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"}, - {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"}, - {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null}, - {"_id":"book6","author":"author 5","category":"novel","stock":"60"} -] -``` -以下聚合操作可以通过一个相等匹配条件连接 `orders` 和 `books` 集合,匹配的字段是 `orders` 集合的 `book` 字段和 `books` 集合的 title 字段: -```js -const db = uniCloud.database() -let res = await db.collection('orders').aggregate() - .lookup({ - from: 'books', - localField: 'book', - foreignField: 'title', - as: 'bookList', - }) - .end() -``` -结果: -```js -[ - { - "_id": 4, - "book": "novel 1", - "price": 30, - "quantity": 2, - "bookList": [ - { - "_id": "book1", - "title": "novel 1", - "author": "author 1", - "category": "novel", - "stock": 10 - } - ] - }, - { - "_id": 5, - "book": "science 1", - "price": 20, - "quantity": 1, - "bookList": [ - { - "_id": "book3", - "category": "science", - "title": "science 1", - "author": "author 3", - "stock": 30 - } - ] - }, - { - "_id": 6, - "bookList": [ - { - "_id": "book5", - "category": "science", - "author": "author 4", - "stock": 50, - "title": null - }, - { - "_id": "book6", - "author": "author 5", - "stock": "60", - "category": "novel" - } - ] - } -] -``` -对数组字段应用相等匹配 -假设 authors 集合有以下记录: -```js -[ - {"_id": 1, "name": "author 1", "intro": "Two-time best-selling sci-fiction novelist"}, - {"_id": 3, "name": "author 3", "intro": "UCB assistant professor"}, - {"_id": 4, "name": "author 4", "intro": "major in CS"} -] -``` -books 集合有以下记录: -```js -[ - {"_id":"book1","authors":["author 1"],"category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, - {"_id":"book3","authors":["author 3", "author 4"],"category":"science","stock":30,"title":"science 1"}, - {"_id":"book4","authors":["author 3"],"category":"science","stock":40,"title":"science 2"} -] -``` -以下操作获取作者信息及他们分别发表的书籍,使用了 lookup 操作匹配 authors 集合的 name 字段和 books 集合的 authors 数组字段: -```js -const db = cloud.database() -let res = await db.collection('authors').aggregate() - .lookup({ - from: 'books', - localField: 'name', - foreignField: 'authors', - as: 'publishedBooks', - }) - .end() -``` -结果 -```js -[ - { - "_id": 1, - "intro": "Two-time best-selling sci-fiction novelist", - "name": "author 1", - "publishedBooks": [ - { - "_id": "book1", - "title": "novel 1", - "category": "novel", - "stock": 10, - "authors": [ - "author 1" - ] - } - ] - }, - { - "_id": 3, - "name": "author 3", - "intro": "UCB assistant professor", - "publishedBooks": [ - { - "_id": "book3", - "category": "science", - "title": "science 1", - "stock": 30, - "authors": [ - "author 3", - "author 4" - ] - }, - { - "_id": "book4", - "title": "science 2", - "category": "science", - "stock": 40, - "authors": [ - "author 3" - ] - } - ] - }, - { - "_id": 4, - "intro": "major in CS", - "name": "author 4", - "publishedBooks": [ - { - "_id": "book3", - "category": "science", - "title": "science 1", - "stock": 30, - "authors": [ - "author 3", - "author 4" - ] - } - ] - } -] -``` - -**组合 mergeObjects 应用相等匹配** - -假设 `orders` 集合有以下记录: -```js -[ - {"_id":4,"book":"novel 1","price":30,"quantity":2}, - {"_id":5,"book":"science 1","price":20,"quantity":1}, - {"_id":6} -] -``` -`books` 集合有以下记录: -```js -[ - {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, - {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, - {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"}, - {"_id":"book2","author":"author 2","category":"novel","stock":20,"title":"novel 2"}, - {"_id":"book5","author":"author 4","category":"science","stock":50,"title":null}, - {"_id":"book6","author":"author 5","category":"novel","stock":"60"} -] -``` -以下操作匹配 orders 的 book 字段和 books 的 title 字段,并将 books 匹配结果直接 merge 到 orders 记录中。 -```js -var db = cloud.database() -var $ = db.command.aggregate -let res = await db.collection('orders').aggregate() - .lookup({ - from: "books", - localField: "book", - foreignField: "title", - as: "bookList" - }) - .replaceRoot({ - newRoot: $.mergeObjects([ $.arrayElemAt(['$bookList', 0]), '$$ROOT' ]) - }) - .project({ - bookList: 0 - }) - .end() -``` -结果 -```js -[ - { - "_id": 4, - "title": "novel 1", - "author": "author 1", - "time": 1564456048486, - "category": "novel", - "stock": 10, - "book": "novel 1", - "price": 30, - "quantity": 2 - }, - { - "_id": 5, - "category": "science", - "title": "science 1", - "author": "author 3", - "stock": 30, - "book": "science 1", - "price": 20, - "quantity": 1 - }, - { - "_id": 6, - "category": "science", - "author": "author 4", - "stock": 50, - "title": null - } -] -``` - -**指定多个连接条件** - -假设 `orders` 集合有以下记录: -```js -[ - {"_id":4,"book":"novel 1","price":300,"quantity":20}, - {"_id":5,"book":"science 1","price":20,"quantity":1} -] -``` -`books` 集合有以下记录: -```js -[ - {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, - {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"} -] -``` -以下操作连接 `orders` 和 `books` 集合,要求两个条件: - -- orders 的 book 字段与 books 的 title 字段相等 -- books 的 stock 字段 大于或等于 orders 的 quantity 字段 -```js -const db = cloud.database() -const dbCmd = db.command -const $ = dbCmd.aggregate -let res = await db.collection('orders').aggregate() - .lookup({ - from: 'books', - let: { - order_book: '$book', - order_quantity: '$quantity' - }, - pipeline: $.pipeline() - .match(dbCmd.expr($.and([ - $.eq(['$title', '$$order_book']), - $.gte(['$stock', '$$order_quantity']) - ]))) - .project({ - _id: 0, - title: 1, - author: 1, - stock: 1 - }) - .done(), - as: 'bookList', - }) - .end() -``` -结果: -```js -[ - { - "_id": 4, - "book": "novel 1", - "price": 300, - "quantity": 20, - "bookList": [] - }, - { - "_id": 5, - "book": "science 1", - "price": 20, - "quantity": 1, - "bookList": [ - { - "title": "science 1", - "author": "author 3", - "stock": 30 - } - ] - } -] -``` - -**拼接被连接集合的子查询** - -假设 `orders` 集合有以下记录: -```js -[ - {"_id":4,"book":"novel 1","price":30,"quantity":2}, - {"_id":5,"book":"science 1","price":20,"quantity":1} -] -``` -`books` 集合有以下记录: -```js -[ - {"_id":"book1","author":"author 1","category":"novel","stock":10,"time":1564456048486,"title":"novel 1"}, - {"_id":"book3","author":"author 3","category":"science","stock":30,"title":"science 1"}, - {"_id":"book4","author":"author 3","category":"science","stock":40,"title":"science 2"} -] -``` -在每条输出记录上加上一个数组字段,该数组字段的值是对 books 集合的一个查询语句的结果: -```js -const db = cloud.database() -const $ = db.command.aggregate -let res = await db.collection('orders').aggregate() - .lookup({ - from: 'books', - let: { - order_book: '$book', - order_quantity: '$quantity' - }, - pipeline: $.pipeline() - .match({ - author: 'author 3' - }) - .project({ - _id: 0, - title: 1, - author: 1, - stock: 1 - }) - .done(), - as: 'bookList', - }) - .end() -``` -结果 -```js -[ - { - "_id": 4, - "book": "novel 1", - "price": 30, - "quantity": 20, - "bookList": [ - { - "title": "science 1", - "author": "author 3", - "stock": 30 - }, - { - "title": "science 2", - "author": "author 3", - "stock": 40 - } - ] - }, - { - "_id": 5, - "book": "science 1", - "price": 20, - "quantity": 1, - "bookList": [ - { - "title": "science 1", - "author": "author 3", - "stock": 30 - }, - { - "title": "science 2", - "author": "author 3", - "stock": 40 - } - ] - } -] -``` - -**多表联表查询** - -假设 `orders` 集合有以下记录: -```js -[ - {"_id":4,"book":"book1","price":30,"quantity":2}, - {"_id":5,"book":"book2","price":20,"quantity":1} -] -``` -`books` 集合有以下记录: -```js -[ - {"_id":"book1","author":"author1","title":"novel 1"}, - {"_id":"book2","author":"author2","title":"science 1"}, -] -``` -`authors` 集合有以下记录: -```js -[ - {"_id":"author1","name":"A1"}, - {"_id":"author2","author":"A2"} -] -``` - -如需orders关联books,book再关联authors查询,可以在pipeline内再使用lookup - -```js -const db = cloud.database() -const dbCmd = db.command -const $ = db.command.aggregate -let res = await db.collection('orders').aggregate() - .lookup({ - from: 'books', - let: { - book_id: '$book' - }, - pipeline: $.pipeline() - .match( - dbCmd.expr($.eq(['$_id', '$$book_id'])) - ) - .lookup({ - from: 'authors', - let: { - author_id: '$author' - }, - pipeline: $.pipeline() - .match( - dbCmd.expr($.eq(['$_id', '$$author_id'])) - ) - .done(), - as: 'authorList' - }) - .done(), - as: 'bookList', - }) - .end() - -``` - -结果如下 - -```js -[ - { - "_id":4, - "book":"book1", - "price":30, - "quantity":2, - "bookList": [{ - "_id":"book1", - "author":"author1", - "title":"novel 1", - authorList: [{ - "_id":"author1", - "name":"A1" - }] - }] - }, - { - "_id":5, - "book":"book2", - "price":20, - "quantity":1, - "bookList": [{ - "_id":"book2", - "author":"author2", - "title":"science 1", - authorList: [{ - "_id":"author2", - "name":"A2" - }] - }] - } -] -``` - -### match@aggregate-match - -聚合阶段。根据条件过滤文档,并且把符合条件的文档传递给下一个流水线阶段。 - -**API 说明** - -**match 的形式如下:** -```js -match(<查询条件>) -``` - -查询条件与普通查询一致,可以用普通查询操作符,注意 match 阶段和其他聚合阶段不同,不可使用聚合操作符,只能使用查询操作符。 -```js -// 直接使用字符串 -match({ - name: 'Tony Stark' -}) -``` -```js -// 使用操作符 -const dbCmd = db.command -match({ - age: dbCmd.gt(18) -}) -``` - -**示例** - -假设集合 articles 有如下记录: -```js -{ "_id" : "1", "author" : "stark", "score" : 80 } -{ "_id" : "2", "author" : "stark", "score" : 85 } -{ "_id" : "3", "author" : "bob", "score" : 60 } -{ "_id" : "4", "author" : "li", "score" : 55 } -{ "_id" : "5", "author" : "jimmy", "score" : 60 } -{ "_id" : "6", "author" : "li", "score" : 94 } -{ "_id" : "7", "author" : "justan", "score" : 95 } -``` - -**匹配** - -下面是一个直接匹配的例子: -```js -let res = await db.collection('articles') - .aggregate() - .match({ - author: 'stark' - }) - .end() -``` -这里的代码尝试找到所有 author 字段是 stark 的文章,那么匹配如下: -```js -{ "_id" : "1", "author" : "stark", "score" : 80 } -{ "_id" : "2", "author" : "stark", "score" : 85 } -``` - -**计数** - -match 过滤出文档后,还可以与其他流水线阶段配合使用。 - -比如下面这个例子,我们使用 group 进行搭配,计算 score 字段大于 80 的文档数量: -```js -const dbCmd = db.command -const $ = dbCmd.aggregate -let res = await db.collection('articles') - .aggregate() - .match({ - score: dbCmd.gt(80) - }) - .group({ - _id: null, - count: $.sum(1) - }) - .end() -``` -返回值如下: -```js -{ "_id" : null, "count" : 3 } -``` - -### project@aggregate-project - -聚合阶段。把指定的字段传递给下一个流水线,指定的字段可以是某个已经存在的字段,也可以是计算出来的新字段。 - -**API 说明** - -**project 的形式如下:** -```js -project({ - <表达式> -}) -``` - -表达式可以有以下格式: - -|格式 |说明 | -|---- |---- | -|<字段>: <1 或 true> |指定包含某个已有字段 | -|_id: <0 或 false> |舍弃 _id 字段 | -|<字段>: <表达式> |加入一个新字段,或者重置某个已有字段 | -|<字段>: <0 或 false> |舍弃某个字段(如果你指定舍弃了某个非 _id 字段,那么在此次 project 中,你不能再使用其它表达式) | - -**指定包含字段** - -_id 字段是默认包含在输出中的,除此之外其他任何字段,如果想要在输出中体现的话,必须在 project 中指定; 如果指定包含一个尚不存在的字段,那么 project 会忽略这个字段,不会加入到输出的文档中; - -**指定排除字段** - -如果你在 project 中指定排除某个字段,那么其它字段都会体现在输出中; 如果指定排除的是非 _id 字段,那么在本次 project 中,不能再使用其它表达式; - -**加入新的字段或重置某个已有字段** - -你可以使用一些特殊的表达式加入新的字段,或重置某个已有字段。 - -**多层嵌套的字段** - -有时有些字段处于多层嵌套的底层,我们可以使用点记法: -```js -"contact.phone.number": <1 or 0 or 表达式> -``` -也可以直接使用嵌套的格式: -```js -contact: { phone: { number: <1 or 0 or 表达式> } } -``` - -**示例** - -假设我们有一个 articles 集合,其中含有以下文档: -```js -{ - "_id": 666, - "title": "This is title", - "author": "Nobody", - "isbn": "123456789", - "introduction": "......" -} -``` -**指定包含某些字段** - -下面的代码使用 project,让输出只包含 _id、title 和 author 字段: -```js -let res = await db.collection('articles') - .aggregate() - .project({ - title: 1, - author: 1 - }) - .end() -``` - -输出如下: -```js -{ "_id" : 666, "title" : "This is title", "author" : "Nobody" } -``` - -**去除输出中的 _id 字段** - -_id 是默认包含在输出中的,如果不想要它,可以指定去除它: -```js -let res = await db.collection('articles') - .aggregate() - .project({ - _id: 0, // 指定去除 _id 字段 - title: 1, - author: 1 - }) - .end() -``` -输出如下: -```js -{ "title" : "This is title", "author" : "Nobody" } -``` - -**去除某个非 _id 字段** - -我们还可以指定在输出中去掉某个非 _id 字段,这样其它字段都会被输出: -```js -let res = await db.collection('articles') - .aggregate() - .project({ - isbn: 0, // 指定去除 isbn 字段 - }) - .end() -``` -输出如下,相比输入,没有了 isbn 字段: -```js -{ - "_id" : 666, - "title" : "This is title", - "author" : "Nobody", - "introduction": "......" -} -``` - -**加入计算出的新字段** - -假设我们有一个 students 集合,其中包含以下文档: -```js -{ - "_id": 1, - "name": "小明", - "scores": { - "chinese": 80, - "math": 90, - "english": 70 - } -} -``` -下面的代码,我们使用 project,在输出中加入了一个新的字段 totalScore: -```js -const { sum } = db.command.aggregate -let res = await db.collection('students') - .aggregate() - .project({ - _id: 0, - name: 1, - totalScore: sum([ - "$scores.chinese", - "$scores.math", - "$scores.english" - ]) - }) - .end() -``` -输出为: -```js -{ "name": "小明", "totalScore": 240 } -``` - -**加入新的数组字段** - -假设我们有一个 points 集合,包含以下文档: -```js -{ "_id": 1, "x": 1, "y": 1 } -{ "_id": 2, "x": 2, "y": 2 } -{ "_id": 3, "x": 3, "y": 3 } -``` - -下面的代码,我们使用 project,把 x 和 y 字段,放入到一个新的数组字段 coordinate 中: -```js -let res = await db.collection('points') - .aggregate() - .project({ - coordinate: ["$x", "$y"] - }) - .end() -``` -输出如下: -```js -{ "_id": 1, "coordinate": [1, 1] } -{ "_id": 2, "coordinate": [2, 2] } -{ "_id": 3, "coordinate": [3, 3] } -``` - -### replaceRoot@aggregate-replace-root - -聚合阶段。指定一个已有字段作为输出的根节点,也可以指定一个计算出的新字段作为根节点。 - -**API 说明** - -**replaceRoot 使用形式如下:** -```js -replaceRoot({ - newRoot: <表达式> -}) -``` -表达式格式如下: - -|格式 |说明 | -|---- |---- | -|<字段名> |指定一个已有字段作为输出的根节点(如果字段不存在则报错) | -|<对象> |计算一个新字段,并且把这个新字段作为根节点 | - -**示例** - -**使用已有字段作为根节点** - -假设我们有一个 schools 集合,内容如下: -```js -{ - "_id": 1, - "name": "SFLS", - "teachers": { - "chinese": 22, - "math": 18, - "english": 21, - "other": 123 - } -} -``` -下面的代码使用 replaceRoot,把 teachers 字段作为根节点输出: -```js -let res = await db.collection('schools') - .aggregate() - .replaceRoot({ - newRoot: '$teachers' - }) - .end() -``` -输出如下: -```js -{ - "chinese": 22, - "math": 18, - "english": 21, - "other": 123 -} -``` -**使用计算出的新字段作为根节点** - -假设我们有一个 roles 集合,内容如下: -```js -{ "_id": 1, "first_name": "四郎", "last_name": "黄" } -{ "_id": 2, "first_name": "邦德", "last_name": "马" } -{ "_id": 3, "first_name": "牧之", "last_name": "张" } -``` -下面的代码使用 replaceRoot,把 first_name 和 last_name 拼在一起: -```js -const { concat } = db.command.aggregate -let res = await db.collection('roles') - .aggregate() - .replaceRoot({ - newRoot: { - full_name: concat(['$last_name', '$first_name']) - } - }) - .end() -``` -输出如下: -```js -{ "full_name": "黄四郎" } -{ "full_name": "马邦德" } -{ "full_name": "张牧之" } -``` - -### sample@aggregate-sample - -**注意** - -- 此方法在数据量大的集合高频调用时可能会导致响应缓慢 -- 腾讯云数据库操作有默认20的limit,如需使用sample返回20条以上数据,请额外指定limit,例:`sample({size:40}).limit(40)` - -聚合阶段。随机从文档中选取指定数量的记录。 - -**API 说明** - -**sample 的形式如下:** -```js -sample({ - size: <正整数> -}) -``` -请注意:size 是正整数,否则会出错。 - -**示例** - -假设文档 users 有以下记录: -```js -{ "name": "a" } -{ "name": "b" } -``` - -**随机选取** - -如果现在进行抽奖活动,需要选出一名幸运用户。那么 sample 的调用方式如下: -```js -let res = await db.collection('users') - .aggregate() - .sample({ - size: 1 - }) - .end() -``` - -返回了随机选中的一个用户对应的记录,结果如下: - -```js -{ "_id": "696529e4-7e82-4e7f-812e-5144714edff6", "name": "b" } -``` - -### skip@aggregate-skip - -聚合阶段。指定一个正整数,跳过对应数量的文档,输出剩下的文档。 - -**示例** -```js -let res = await db.collection('users') - .aggregate() - .skip(5) - .end() -``` - -这段代码会跳过查找到的前 5 个文档,并且把剩余的文档输出。 - -### sort@aggregate-sort - -聚合阶段。根据指定的字段,对输入的文档进行排序。 - -**API 说明** - -**形式如下:** -```js -sort({ - <字段名1>: <排序规则>, - <字段名2>: <排序规则>, -}) -``` - -<排序规则>可以是以下取值: - -- 1 代表升序排列(从小到大); -- -1 代表降序排列(从大到小); - -**示例** - -升序/降序排列 - -假设我们有集合 articles,其中包含数据如下: -```js -{ "_id": "1", "author": "stark", "score": 80, "age": 18 } -{ "_id": "2", "author": "bob", "score": 60, "age": 18 } -{ "_id": "3", "author": "li", "score": 55, "age": 19 } -{ "_id": "4", "author": "jimmy", "score": 60, "age": 22 } -{ "_id": "5", "author": "justan", "score": 95, "age": 33 } -``` -```js -let res = await db.collection('articles') - .aggregate() - .sort({ - age: -1, - score: -1 - }) - .end() -``` -上面的代码在 students 集合中进行聚合搜索,并且将结果排序,首先根据 age 字段降序排列,然后再根据 score 字段进行降序排列。 - -输出结果如下: -```js -{ "_id": "5", "author": "justan", "score": 95, "age": 33 } -{ "_id": "4", "author": "jimmy", "score": 60, "age": 22 } -{ "_id": "3", "author": "li", "score": 55, "age": 19 } -{ "_id": "1", "author": "stark", "score": 80, "age": 18 } -{ "_id": "2", "author": "bob", "score": 60, "age": 18 } -``` - -### sortByCount@aggregate-sort-by-count - -聚合阶段。根据传入的表达式,将传入的集合进行分组(group)。然后计算不同组的数量,并且将这些组按照它们的数量进行排序,返回排序后的结果。 - -**API 说明** - -**sortByCount 的调用方式如下:** -```js -sortByCount(<表达式>) -``` - -表达式的形式是:$ + 指定字段。请注意:不要漏写 $ 符号。 - -**示例** - -**统计基础类型** - -假设集合 passages 的记录如下: -```js -{ "category": "Web" } -{ "category": "Web" } -{ "category": "Life" } -``` -下面的代码就可以统计文章的分类信息,并且计算每个分类的数量。即对 category 字段执行 sortByCount 聚合操作。 -```js -let res = await db.collection('passages') - .aggregate() - .sortByCount('$category') - .end() -``` - -返回的结果如下所示:Web 分类下有2篇文章,Life 分类下有1篇文章。 -```js -{ "_id": "Web", "count": 2 } -{ "_id": "Life", "count": 1 } -``` -**解构数组类型** - -假设集合 passages 的记录如下:tags 字段对应的值是数组类型。 -```js -{ "tags": [ "JavaScript", "C#" ] } -{ "tags": [ "Go", "C#" ] } -{ "tags": [ "Go", "Python", "JavaScript" ] } -``` -如何统计文章的标签信息,并且计算每个标签的数量?因为 tags 字段对应的数组,所以需要借助 unwind 操作解构 tags 字段,然后再调用 sortByCount。 - -下面的代码实现了这个功能: -```js -let res = await db.collection('passages') - .aggregate() - .unwind(`$tags`) - .sortByCount(`$tags`) - .end() -``` -返回的结果如下所示: -```js -{ "_id": "Go", "count": 2 } -{ "_id": "C#", "count": 2 } -{ "_id": "JavaScript", "count": 2 } -{ "_id": "Python", "count": 1 } -``` - -### unwind@aggregate-unwind - -聚合阶段。使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 - -**API 说明** - -使用指定的数组字段中的每个元素,对文档进行拆分。拆分后,文档会从一个变为一个或多个,分别对应数组的每个元素。 - -**unwind 有两种使用形式:** - -**参数是一个字段名** -```js -unwind(<字段名>) -``` -**参数是一个对象** -```js -unwind({ - path: <字段名>, - includeArrayIndex: , - preserveNullAndEmptyArrays: -}) -``` - -|字段 |类型 |说明 | -|---- |---- |---- | -|path |string |想要拆分的数组的字段名,需要以 $ 开头。 | -|includeArrayIndex |string |可选项,传入一个新的字段名,数组索引会保存在这个新的字段上。新的字段名不能以 $ 开头。 | -|preserveNullAndEmptyArrays |boolean|如果为 true,那么在 path 对应的字段为 null、空数组或者这个字段不存在时,依然会输出这个文档;如果为 false,unwind 将不会输出这些文档。默认为 false。| - -**示例** - -**拆分数组** - -假设我们有一个 products 集合,包含数据如下: -```js -{ "_id": "1", "product": "tshirt", "size": ["S", "M", "L"] } -{ "_id": "2", "product": "pants", "size": [] } -{ "_id": "3", "product": "socks", "size": null } -{ "_id": "4", "product": "trousers", "size": ["S"] } -{ "_id": "5", "product": "sweater", "size": ["M", "L"] } -``` - -我们根据 size 字段对这些文档进行拆分 -```js -db.collection('products') - .aggregate() - .unwind('$size') - .end() -``` - -输出如下: -```js -{ "_id": "1", "product": "tshirt", "size": "S" } -{ "_id": "1", "product": "tshirt", "size": "M" } -{ "_id": "1", "product": "tshirt", "size": "L" } -{ "_id": "4", "product": "trousers", "size": "S" } -{ "_id": "5", "product": "sweater", "size": "M" } -{ "_id": "5", "product": "sweater", "size": "L" } -``` - -**拆分后,保留原数组的索引** - -我们根据 size 字段对文档进行拆分后,想要保留原数组索引在新的 index 字段中。 -```js -let res = await db.collection('products') - .aggregate() - .unwind({ - path: '$size', - includeArrayIndex: 'index' - }) - .end() -``` -输出如下: -```js -{ "_id": "1", "product": "tshirt", "size": "S", "index": 0 } -{ "_id": "1", "product": "tshirt", "size": "M", "index": 1 } -{ "_id": "1", "product": "tshirt", "size": "L", "index": 2 } -{ "_id": "4", "product": "trousers", "size": "S", "index": 0 } -{ "_id": "5", "product": "sweater", "size": "M", "index": 0 } -{ "_id": "5", "product": "sweater", "size": "L", "index": 1 } -``` - -**保留字段为空的文档** - -注意到我们的集合中有两行特殊的空值数据: -```js -... -{ "_id": "2", "product": "pants", "size": [] } -{ "_id": "3", "product": "socks", "size": null } -... -``` -如果想要在输出中保留 size 为空数组、null,或者 size 字段不存在的文档,可以使用 preserveNullAndEmptyArrays 参数 -```js -let res = await db.collection('products') - .aggregate() - .unwind({ - path: '$size', - preserveNullAndEmptyArrays: true - }) - .end() -``` -输出如下: -```js -{ "_id": "1", "product": "tshirt", "size": "S" } -{ "_id": "1", "product": "tshirt", "size": "M" } -{ "_id": "1", "product": "tshirt", "size": "L" } -{ "_id": "2", "product": "pants", "size": null } -{ "_id": "3", "product": "socks", "size": null } -{ "_id": "4", "product": "trousers", "size": "S" } -{ "_id": "5", "product": "sweater", "size": "M" } -{ "_id": "5", "product": "sweater", "size": "L" } -``` - -### end@aggregate-end - -标志聚合操作定义完成,发起实际聚合操作 - -**返回值** - -Promise.<Object> - -|属性 |类型 |说明 | -|--- |--- |--- | -|list |Array.<any>|聚合结果列表 | - -**示例代码** -```js -const $ = db.command.aggregate -let res = await db.collection('books').aggregate() - .group({ - // 按 category 字段分组 - _id: '$category', - // 让输出的每组记录有一个 avgSales 字段,其值是组内所有记录的 sales 字段的平均值 - avgSales: $.avg('$sales') - }) - .end() -``` - -## 数据库操作符@dbcmd - -### 查询·逻辑操作符@dbcmd-query - -#### and - -查询操作符,用于表示逻辑 "与" 的关系,表示需同时满足多个查询筛选条件 - - -##### 使用说明 - `and` 有两种使用情况: - - - -**1. 用在根查询条件** - - 此时需传入多个查询条件,表示需同时满足提供的多个完整查询条件 - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where(dbCmd.and([ - { - progress: dbCmd.gt(50) - }, - { - tags: 'cloud' - } -])).get() -``` -但以上用 `and` 组成的查询条件是不必要的,因为传入的对象的各字段隐式组成了 “与” 的关系,上述条件等价于下方更简洁的写法: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.gt(50), - tags: 'cloud' -}).get() -``` - -通常需要显示使用 `and` 是用在有跨字段或操作的时候 - - -**2. 用在字段查询条件** - - 需传入多个查询操作符或常量,表示字段需满足或匹配给定的条件。 - - 如以下用前置写法的方式表示 "progress 字段值大于 50 且小于 100" - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.and(dbCmd.gt(50), dbCmd.lt(100)) -}).get() -``` -还可以用后置写法的方式表示同样的条件: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.gt(50).and(dbCmd.lt(100)) -}).get() -``` -注意 `Command` 默认也可以直接链式调用其他 `Command`,默认表示多个 `Command` 的与操作,因此上述代码还可以精简为: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.gt(50).lt(100) -}).get() -``` - -##### 调用风格 - 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 - - -```js -// 传入数组 -function and(expressions: Expression[]): Command -// 传入多参数 -function and(...expressions: Expression[]): Command -``` - -#### or - -查询操作符,用于表示逻辑 "或" 的关系,表示需同时满足多个查询筛选条件。或指令有两种用法,一是可以进行字段值的 “或” 操作,二是也可以进行跨字段的 “或” 操作。 - - -##### 字段值的或操作 - 字段值的 “或” 操作指的是指定一个字段值为多个值之一即可。 - - 如筛选出进度大于 80 或小于 20 的 todo: - - 流式写法: - - -```js -let res = await const dbCmd = db.command -db.collection('todo').where({ - progress: dbCmd.gt(80).or(dbCmd.lt(20)) -}).get() -``` -前置写法: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.or(dbCmd.gt(80), dbCmd.lt(20)) -}).get() -``` -前置写法也可接收一个数组: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.or([dbCmd.gt(80), dbCmd.lt(20)]) -}).get() -``` - -##### 跨字段的或操作 - 跨字段的 “或” 操作指条件 “或”,相当于可以传入多个 where 语句,满足其中一个即可。 - - 如筛选出进度大于 80 或已标为已完成的 todo: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where(dbCmd.or([ - { - progress: dbCmd.gt(80) - }, - { - done: true - } -])).get() -``` - -##### 调用风格 - 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 - - -```js -// 传入数组 -function or(expressions: Expression[]): Command -// 传入多参数 -function or(...expressions: Expression[]): Command -``` - -#### not - -查询操作符,用于表示逻辑 "非" 的关系,表示需不满足指定的条件。 - - -##### 示例 - 如筛选出进度不等于100的 todo: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.not(dbCmd.eq(100)) -}).get() -``` -`not` 也可搭配其他逻辑指令使用,包括 `and`, `or`, `nor`, `not`,如 `or`: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.not(dbCmd.or([dbCmd.lt(50), dbCmd.eq(100)])) -}).get() -``` - -#### nor - -查询操作符,用于表示逻辑 "都不" 的关系,表示需不满足指定的所有条件。如果记录中没有对应的字段,则默认满足条件。 - - -##### 示例 1 - 筛选出进度既不小于20又不大于80的 todo : - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)]) -}).get() -``` -以上同时会筛选出不存在 `progress` 字段的记录,如果要要求 `progress` 字段存在,可以用 `exists` 指令: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where({ - progress: dbCmd.exists().nor([dbCmd.lt(20), dbCmd.gt(80)]) - // 等价于以下非链式调用的写法: - // progress: dbCmd.exists().and(dbCmd.nor([dbCmd.lt(20), dbCmd.gt(80)])) -}).get() -``` - -##### 示例 2 - 筛选出 `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串的记录: - - -```js -const dbCmd = db.command -db.collection('todo').where(dbCmd.nor([{ - progress: dbCmd.lt(20), -}, { - tags: 'miniprogram', -}])).get() -``` -以上会筛选出满足以下条件之一的记录: - - -1. `progress` 不小于 20 且 `tags` 数组不包含 `miniprogram` 字符串 3. `progress` 不小于 20 且 `tags` 字段不存在 5. `progress` 字段不存在 且 `tags` 数组不包含 `miniprogram` 字符串 7. `progress` 不小于 20 且 `tags` 字段不存在 - 如果要求 `progress` 和 `tags` 字段存在,可以用 `exists` 指令: - - -```js -const dbCmd = db.command -let res = await db.collection('todo').where( - dbCmd.nor([{ - progress: dbCmd.lt(20), - }, { - tags: 'miniprogram', - }]) - .and({ - progress: dbCmd.exists(true), - tags: dbCmd.exists(true), - }) -).get() -``` - -##### 调用风格 - 方法接收两种传参方式,一是传入一个数组参数,二是传入多个参数,效果一样。 - - -```js -// 传入数组 -function nor(expressions: Expression[]): Command -// 传入多参数 -function nor(...expressions: Expression[]): Command -``` - -### 查询·比较操作符@dbcmd-compare - -#### eq - -查询筛选条件,表示字段等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 - - -##### 使用说明 - 比如筛选出所有自己发表的文章,除了用传对象的方式: - - -```js -const openID = 'xxx' -let res = await db.collection('articles').where({ - _openid: openID -}).get() -``` -还可以用指令: - - -```js -const dbCmd = db.command -const openID = 'xxx' -let res = await db.collection('articles').where({ - _openid: dbCmd.eq(openid) -}).get() -``` -注意 `eq` 指令比对象的方式有更大的灵活性,可以用于表示字段等于某个对象的情况,比如: - - -```js -// 这种写法表示匹配 stat.publishYear == 2018 且 stat.language == 'zh-CN' -let res = await db.collection('articles').where({ - stat: { - publishYear: 2018, - language: 'zh-CN' - } -}).get() -// 这种写法表示 stat 对象等于 { publishYear: 2018, language: 'zh-CN' } -const dbCmd = db.command -let res = await db.collection('articles').where({ - stat: dbCmd.eq({ - publishYear: 2018, - language: 'zh-CN' - }) -}).get() -``` - -#### neq - -查询筛选条件,表示字段不等于某个值。`eq` 指令接受一个字面量 (literal),可以是 `number`, `boolean`, `string`, `object`, `array`, `Date`。 - - -##### 使用说明 - 表示字段不等于某个值,和 [eq](Command.eq.html) 相反 - -#### lt - -查询筛选操作符,表示需小于指定值。可以传入 `Date` 对象用于进行日期比较。 - - -##### 示例代码 - 找出进度小于 50 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.lt(50) -}) -.get() -``` - -#### lte - -查询筛选操作符,表示需小于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 - - -##### 示例代码 - 找出进度小于或等于 50 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.lte(50) -}) -.get() -``` - -#### gt - -查询筛选操作符,表示需大于指定值。可以传入 `Date` 对象用于进行日期比较。 - - -##### 示例代码 - 找出进度大于 50 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.gt(50) -}) -.get() -``` - -#### gte - -查询筛选操作符,表示需大于或等于指定值。可以传入 `Date` 对象用于进行日期比较。 - - -##### 示例代码 - 找出进度大于或等于 50 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.gte(50) -}) -.get() -``` - -#### in - -查询筛选操作符,表示要求值在给定的数组内。 - - -##### 示例代码 - 找出进度为 0 或 100 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.in([0, 100]) -}) -.get() -``` - -#### nin - -查询筛选操作符,表示要求值不在给定的数组内。 - - -##### 示例代码 - 找出进度不是 0 或 100 的 todo - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.nin([0, 100]) -}) -.get() -``` - -### 查询·字段操作符@dbcmd-field - -#### exists - -判断字段是否存在 - - -##### 示例代码 - 找出存在 tags 字段的记录 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - tags: dbCmd.exists(true) -}) -.get() -``` - -#### mod - -查询筛选操作符,给定除数 divisor 和余数 remainder,要求字段作为被除数时 value % divisor = remainder。 - - -##### 示例代码 - 找出进度为 10 的倍数的字段的记录 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - progress: dbCmd.mod(10, 0) -}) -.get() -``` - -### 查询·数组操作符@dbcmd-array - -#### all - -数组查询操作符。用于数组字段的查询筛选条件,要求数组字段中包含给定数组的所有元素。 - - -##### 示例代码 1:普通数组 - 找出 tags 数组字段同时包含 cloud 和 database 的记录 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - tags: dbCmd.all(['cloud', 'database']) -}) -.get() -``` - -##### 示例代码 2:对象数组 - 如果数组元素是对象,则可以用 `dbCmd.elemMatch` 匹配对象的部分字段 - - 假设有字段 `places` 定义如下: - - -```js -{ - "type": string - "area": number - "age": number -} -``` -找出数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素和一个满足 “type 为 mall 且 age 大于 5” 的元素 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - places: dbCmd.all([ - dbCmd.elemMatch({ - area: dbCmd.gt(100), - age: dbCmd.lt(2), - }), - dbCmd.elemMatch({ - type: 'mall', - age: dbCmd.gt(5), - }), - ]), -}) -.get() -``` - -#### elemMatch - -用于数组字段的查询筛选条件,要求数组中包含至少一个满足 `elemMatch` 给定的所有条件的元素 - - -##### 示例代码:数组是对象数组的情况 - 假设集合示例数据如下: - - -```js -{ - "_id": "a0", - "city": "x0", - "places": [{ - "type": "garden", - "area": 300, - "age": 1 - }, { - "type": "theatre", - "area": 50, - "age": 15 - }] -} -``` -找出 `places` 数组字段中至少同时包含一个满足 “area 大于 100 且 age 小于 2” 的元素 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - places: dbCmd.elemMatch({ - area: dbCmd.gt(100), - age: dbCmd.lt(2), - }) -}) -.get() -``` -*注意**:如果不使用 `elemMatch` 而直接如下指定条件,则表示的是 `places` 数组字段中至少有一个元素的 `area` 字段大于 100 且 `places` 数组字段中至少有一个元素的 `age` 字段小于 2: - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - places: { - area: dbCmd.gt(100), - age: dbCmd.lt(2), - } -}) -.get() -``` - -##### 示例代码:数组元素都是普通数据类型的情况 - 假设集合示例数据如下: - - -```js -{ - "_id": "a0", - "scores": [60, 80, 90] -} -``` -找出 `scores` 数组字段中至少同时包含一个满足 “大于 80 且小于 100” 的元素 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - scores: dbCmd.elemMatch(dbCmd.gt(80).lt(100)) -}) -.get() -``` - -#### size - -更新操作符,用于数组字段的查询筛选条件,要求数组长度为给定值 - - -##### 示例 - 找出 tags 数组字段长度为 2 的所有记录 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').where({ - places: dbCmd.size(2) -}) -.get() -``` - -### 查询·地理位置操作符@dbcmd-geo - -#### geoNear - -按从近到远的顺序,找出字段值在给定点的附近的记录。 - - -##### 索引要求 - 需对查询字段建立地理位置索引 - - -##### 示例代码 - 找出离给定位置 1 公里到 5 公里范围内的记录 - - -```js -const dbCmd = db.command -let res = await db.collection('restaurants').where({ - location: dbCmd.geoNear({ - geometry: new db.Geo.Point(113.323809, 23.097732), - minDistance: 1000, - maxDistance: 5000, - }) -}).get() -``` - -#### geoWithin - -找出字段值在指定区域内的记录,无排序。指定的区域必须是多边形(Polygon)或多边形集合(MultiPolygon)。 - - -##### 索引要求 - 需对查询字段建立地理位置索引 - - -##### 示例代码 1:给定多边形 - -```js -const dbCmd = db.command -const { Point, LineString, Polygon } = db.Geo -let res = await .collection('restaurants').where({ - location: dbCmd.geoWithin({ - geometry: new Polygon([ - new LineString([ - new Point(0, 0), - new Point(3, 2), - new Point(2, 3), - new Point(0, 0) - ]) - ]), - }) -}).get() -``` - -##### 示例代码 2:给定圆形 - 可以不用 `geometry` 而用 `centerSphere` 构建一个圆形。 - - `centerSphere` 对应的值的定义是:`[ [经度, 纬度], 半径 ]` - - 半径需以弧度计,比如需要 10km 的半径,则用距离除以地球半径 6378.1km 得出的数字。 - - -```js -const dbCmd = db.command -let res = await db.collection('restaurants').where({ - location: dbCmd.geoWithin({ - centerSphere: [ - [-88, 30], - 10 / 6378.1, - ] - }) -}).get() -``` - -#### geoIntersects - -找出给定的地理位置图形相交的记录 - - -##### 索引要求 - 需对查询字段建立地理位置索引 - - -##### 示例代码:找出和一个多边形相交的记录 - -```js -const dbCmd = db.command -const { Point, LineString, Polygon } = db.Geo -let res = await db.collection('restaurants').where({ - location: dbCmd.geoIntersects({ - geometry: new Polygon([ - new LineString([ - new Point(0, 0), - new Point(3, 2), - new Point(2, 3), - new Point(0, 0) - ]) - ]), - }) -}).get() -``` - -### 查询·表达式操作符@dbcmd-expr - -#### expr - -查询操作符,用于在查询语句中使用聚合表达式,方法接收一个参数,该参数必须为聚合表达式 - -##### 使用说明 - -1. `expr` 可用于在聚合 [`match`](../aggregate/Aggregate.match.html) 流水线阶段中引入聚合表达式 3. 如果聚合 [`match`](../aggregate/Aggregate.match.html) 阶段是在 [`lookup`](../aggregate/Aggregate.lookup.html) 阶段内,此时的 `expr` 表达式内可使用 `lookup` 中使用 `let` 参数定义的变量,具体示例可见 [`lookup`](../aggregate/Aggregate.lookup.html) 的 `指定多个连接条件` 例子 5. `expr` 可用在普通查询语句(`where`)中引入聚合表达式 - -##### 示例代码 1:比较同一个记录中的两个字段 - 假设 `items` 集合的数据结构如下: - - -```js -{ - "_id": string, - "inStock": number, // 库存量 - "ordered": number // 被订量 -} -``` -找出被订量大于库存量的记录: - - -```js -const dbCmd = db.command -const $ = dbCmd.aggregate -let res = await db.collection('items').where(dbCmd.expr($.gt(['$ordered', '$inStock']))).get() -``` - -##### 示例代码 2:与条件语句组合使用 - 假设 `items` 集合的数据结构如下: - - -```json -{ - "_id": string, - "price": number -} -``` -假设加个小于等于 10 的打 8 折,大于 10 的打 5 折,让数据库查询返回打折后价格小于等于 8 的记录: - - -```js -const dbCmd = db.command -const $ = dbCmd.aggregate -let res = await db.collection('items').where(dbCmd.expr( - $.lt([ - $.cond({ - if: $.gte(['$price', 10]), - then: $.multiply(['$price', '0.5']), - else: $.multiply(['$price', '0.8']), - }) - , - 8 - ]) -).get() -``` - -### 更新·字段操作符@dbcmd-update-field - -#### set - -更新操作符,用于设定字段等于指定值。 - - -##### 使用说明 - 这种方法相比传入纯 JS 对象的好处是能够指定字段等于一个对象 - - -##### 示例 - -```js -// 以下方法只会更新 style.color 为 red,而不是将 style 更新为 { color: 'red' },即不影响 style 中的其他字段 -let res = await db.collection('todos').doc('doc-id').update({ - style: { - color: 'red' - } -}) - -// 以下方法更新 style 为 { color: 'red', size: 'large' } -let res = await db.collection('todos').doc('doc-id').update({ - style: dbCmd.set({ - color: 'red', - size: 'large' - }) -}) -``` - -#### remove - -更新操作符,用于表示删除某个字段。 - - -##### 示例代码 - 删除 style 字段: - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('todo-id').update({ - style: dbCmd.remove() -}) -``` - -#### inc - -更新操作符,原子操作,用于指示字段自增 - - -##### 原子自增 - 多个用户同时写,对数据库来说都是将字段自增,不会有后来者覆写前者的情况 - - -##### 示例代码 - 将一个 todo 的进度自增 10: - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('todo-id').update({ - progress: dbCmd.inc(10) -}) -``` - -#### mul - -更新操作符,原子操作,用于指示字段自乘某个值 - - -##### 原子自乘 - 多个用户同时写,对数据库来说都是将字段自乘,不会有后来者覆写前者的情况 - - -##### 示例代码 - 将一个 todo 的进度自乘 10: - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('todo-id').update({ - progress: dbCmd.mul(10) -}) -``` - -#### min - -更新操作符,给定一个值,只有该值小于字段当前值才进行更新。 - - -##### 示例代码 - 如果字段 progress > 50,则更新到 50 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - progress: dbCmd.min(50) -}) -``` - -#### max - -更新操作符,给定一个值,只有该值大于字段当前值才进行更新。 - - -##### 示例代码 - 如果字段 progress < 50,则更新到 50 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - progress: dbCmd.max(50) -}) -``` - -#### rename - -更新操作符,字段重命名。如果需要对嵌套深层的字段做重命名,需要用点路径表示法。不能对嵌套在数组里的对象的字段进行重命名。 - - -##### 示例 1:重命名顶层字段 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - progress: dbCmd.rename('totalProgress') -}) -``` - -##### 示例 2:重命名嵌套字段 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - someObject: { - someField: dbCmd.rename('someObject.renamedField') - } -}) -``` - -或: - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - 'someObject.someField': dbCmd.rename('someObject.renamedField') -}) -``` - -### 更新·数组操作符@dbcmd-update-array - -#### push - -数组更新操作符。对一个值为数组的字段,往数组添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 - - -##### 参数说明 - - -**position 说明** - - 要求必须同时有 `each` 参数存在。 - - 非负数代表从数组开始位置数的位置,从 0 开始计。如果数值大于等于数组长度,则视为在尾部添加。负数代表从数组尾部倒数的位置,比如 -1 就代表倒数第二个元素的位置。如果负数数值的绝对值大于等于数组长度,则视为从数组头部添加。 - - - -**sort 说明** - - 要求必须同时有 `each` 参数存在。给定 1 代表升序,-1 代表降序。 - - 如果数组元素是记录,则用 `{ <字段>: 1 | -1 }` 的格式表示根据记录中的什么字段做升降序排序。 - - - -**slice** 说明** - - 要求必须同时有 `each` 参数存在 - -|值 |说明 | -|:-: |:-: | -|0 |将字段更新为空数组 | -|正数 |数组只保留前 n 个元素| -|负数 |数组只保留后 n 个元素| - - -##### 示例 1:尾部添加元素 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push(['mini-program', 'cloud']) -}) -``` - -##### 示例 2:从第二个位置开始插入 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: ['mini-program', 'cloud'], - position: 1, - }) -}) -``` - -##### 示例 3:排序 - -插入后对整个数组做排序 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: ['mini-program', 'cloud'], - sort: 1, - }) -}) -``` - -不插入,只对数组做排序 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: [], - sort: 1, - }) -}) -``` -如果字段是对象数组,可以如下根据元素对象里的字段进行排序: - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: [ - { name: 'miniprogram', weight: 8 }, - { name: 'cloud', weight: 6 }, - ], - sort: { - weight: 1, - }, - }) -}) -``` - -##### 示例 4:截断保留 - 插入后只保留后 2 个元素 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: ['mini-program', 'cloud'], - slice: -2, - }) -}) -``` - -##### 示例 5:在指定位置插入、然后排序、最后只保留前 2 个元素 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.push({ - each: ['mini-program', 'cloud'], - position: 1, - slice: 2, - sort: 1, - }) -}) -``` - -#### pop - -数组更新操作符,对一个值为数组的字段,将数组尾部元素删除,仅可以删除末尾一个 - -##### 示例代码 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.pop() -}) -``` - -#### unshift - -数组更新操作符,对一个值为数组的字段,往数组头部添加一个或多个值。或字段原为空,则创建该字段并设数组为传入值。 - - -##### 示例代码 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.unshift(['mini-program', 'cloud']) -}) -``` - -#### shift - -数组更新操作符,对一个值为数组的字段,将数组头部元素删除。 - - -##### 示例代码 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.shift() -}) -``` - -#### pull - -数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值或查询条件的元素都移除掉。 - -##### 示例代码 1:根据常量匹配移除 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.pull('database') -}) -``` - -##### 示例代码 2:根据查询条件匹配移除 - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.pull(dbCmd.in(['database', 'cloud'])) -}) -``` - -##### 示例代码 3:对象数组时,根据查询条件匹配移除 - 假设有字段 `places` 数组中的元素结构如下 - - -```json -{ - "type": string - "area": number - "age": number -} -``` - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - places: dbCmd.pull({ - area: dbCmd.gt(100), - age: dbCmd.lt(2), - }) -}) -``` - -##### 示例代码 4:有嵌套对象的对象数组时,根据查询条件匹配移除 - 假设有字段 `cities` 数组中的元素结构如下 - - -```json -{ - "name": string - "places": Place[] -} -``` -`Place` 结构如下: - - -```json -{ - "type": string - "area": number - "age": number -} -``` -可用 `elemMatch` 匹配嵌套在对象数组里面的对象数组字段 places - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - cities: dbCmd.pull({ - places: dbCmd.elemMatch({ - area: dbCmd.gt(100), - age: dbCmd.lt(2), - }) - }) -}) -``` - -#### pullAll - -数组更新操作符。给定一个值或一个查询条件,将数组中所有匹配给定值的元素都移除掉。跟 `pull` 的差别在于只能指定常量值、传入的是数组。 - - -##### 示例代码:根据常量匹配移除 - 从 tags 中移除所有 database 和 cloud 字符串 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.pullAll(['database', 'cloud']) -}) -``` - -#### addToSet - -数组更新操作符。原子操作。给定一个或多个元素,除非数组中已存在该元素,否则添加进数组。 - - -##### 示例代码 1:添加一个元素 - 如果 tags 数组中不包含 database,添加进去 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.addToSet('database') -}) -``` - -##### 示例代码 2:添加多个元素 - -需传入一个对象,其中有一个字段 `each`,其值为数组,每个元素就是要添加的元素 - - -```js -const dbCmd = db.command -let res = await db.collection('todos').doc('doc-id').update({ - tags: dbCmd.addToSet({ - $each: ['database', 'cloud'] - }) -}) -``` - -## 数据库运算方法@aggregate-operator - -**等同于mongoDB聚合操作符概念** - -### 算术操作符 - -#### abs - - - -返回一个数字的绝对值。 - -##### API 说明 - -语法如下: - - -```js -db.command.aggregate.abs() -``` -`abs` 传入的值除了数字常量外,也可以是任何最终解析成一个数字的表达式。 - - 如果表达式解析为 `null` 或者指向一个不存在的字段,则 `abs` 的结果是 `null`。如果值解析为 `NaN`,则结果是 `NaN`。 - - -##### 示例代码 - 假设集合 `ratings` 有如下记录: - - -```json -{ _id: 1, start: 5, end: 8 } -{ _id: 2, start: 4, end: 4 } -{ _id: 3, start: 9, end: 7 } -{ _id: 4, start: 6, end: 7 } -``` -··· -可以用如下方式求得各个记录的 `start` 和 `end` 之间的绝对差异大小: - - -```js -const $ = db.command.aggregate -let res = await db.collection('ratings').aggregate() - .project({ - delta: $.abs($.subtract(['$start', '$end'])) - }) - .end() -``` -返回结果如下: - - -```json -{ "_id" : 1, "delta" : 3 } -{ "_id" : 2, "delta" : 0 } -{ "_id" : 3, "delta" : 2 } -{ "_id" : 4, "delta" : 1 } -``` - -#### add - - - -将数字相加或将数字加在日期上。如果数组中的其中一个值是日期,那么其他值将被视为毫秒数加在该日期上。 - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.add([<表达式1>, <表达式2>, ...]) -``` -表达式可以是形如 `$ + 指定字段`,也可以是普通字符串。只要能够被解析成字符串即可。 - - -##### 示例代码 - 假设集合 `staff` 有如下记录: - - -```json -{ _id: 1, department: "x", sales: 5, engineer: 10, lastUpdate: ISODate("2019-05-01T00:00:00Z") } -{ _id: 2, department: "y", sales: 10, engineer: 20, lastUpdate: ISODate("2019-05-01T02:00:00Z") } -{ _id: 3, department: "z", sales: 20, engineer: 5, lastUpdate: ISODate("2019-05-02T03:00:00Z") } -``` - - -**数字求和** - - 可以用如下方式求得各个记录人数总数: - - -```js -const $ = db.command.aggregate -let res = await db.collection('staff').aggregate() - .project({ - department: 1, - total: $.add(['$sales', '$engineer']) - }) - .end() -``` -返回结果如下: - - -```json -{ _id: 1, department: "x", total: 15 } -{ _id: 2, department: "y", total: 30 } -{ _id: 3, department: "z", total: 25 } -``` - - -**增加日期值** - - 如下操作可以获取各个记录的 `lastUpdate` 加一个小时之后的值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('staff').aggregate() - .project({ - department: 1, - lastUpdate: $.add(['$lastUpdate', 60*60*1000]) - }) - .end() -``` -返回结果如下: - - -```json -{ _id: 1, department: "x", lastUpdate: ISODate("2019-05-01T01:00:00Z") } -{ _id: 2, department: "y", lastUpdate: ISODate("2019-05-01T03:00:00Z") } -{ _id: 3, department: "z", lastUpdate: ISODate("2019-05-02T04:00:00Z") } -``` - -#### ceil - -向上取整,返回大于或等于给定数字的最小整数。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.ceil() -``` -`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 - - -##### 示例代码 - 假设集合 `sales` 有如下记录: - - -```json -{ _id: 1, sales: 5.2 } -{ _id: 2, sales: 1.32 } -{ _id: 3, sales: -3.2 } -``` -可以用如下方式取各个数字的向上取整值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('sales').aggregate() - .project({ - sales: $.ceil('$sales') - }) - .end() -``` -返回结果如下: - - -```json -{ _id: 1, sales: 6 } -{ _id: 2, sales: 2 } -{ _id: 3, sales: -3 } -``` - -#### divide - -传入被除数和除数,求商。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.divide([<被除数表达式>, <除数表达式>]) -``` -表达式可以是任意解析为数字的表达式。 - - -##### 示例代码 - 假设集合 `railroads` 有如下记录: - - -```js -{ _id: 1, meters: 5300 } -{ _id: 2, meters: 64000 } -{ _id: 3, meters: 130 } -``` -可以用如下方式取各个数字转换为千米之后的值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('railroads').aggregate() - .project({ - km: $.divide(['$meters', 1000]) - }) - .end() -``` -返回结果如下: - - -```js -{ _id: 1, km: 5.3 } -{ _id: 2, km: 64 } -{ _id: 3, km: 0.13 } -``` - -#### exp - -取 e(自然对数的底数,欧拉数) 的 n 次方。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.exp() -``` -`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 - - -##### 示例代码 - 假设集合 `math` 有如下记录: - - -```js -{ _id: 1, exp: 0 } -{ _id: 2, exp: 1 } -{ _id: 3, exp: 2 } -``` - -```js -const $ = db.command.aggregate -let res = await db.collection('math').aggregate() - .project({ - result: $.exp('$exp') - }) - .end() -``` -返回结果如下: - - -```js -{ _id: 1, result: 1 } -{ _id: 2, result: 2.71828182845905 } -{ _id: 3, result: 7.38905609893065 } -``` - -#### floor - -向下取整,返回大于或等于给定数字的最小整数。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.floor() -``` -`` 可以是任意解析为数字的表达式。如果表达式解析为 `null` 或指向一个不存在的字段,则返回 `null`,如果解析为 `NaN`,则返回 `NaN`。 - - -##### 示例代码 - 假设集合 `sales` 有如下记录: - - -```js -{ _id: 1, sales: 5.2 } -{ _id: 2, sales: 1.32 } -{ _id: 3, sales: -3.2 } -``` -可以用如下方式取各个数字的向下取整值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('sales').aggregate() - .project({ - sales: $.floor('$sales') - }) - .end() -``` -返回结果如下: - - -```js -{ _id: 1, sales: 5 } -{ _id: 2, sales: 1 } -{ _id: 3, sales: -4 } -``` - -#### ln - -计算给定数字在自然对数值。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.ln() -``` -`` 可以是任意解析为非负数字的表达式。 - - `ln` 等价于 `log([, Math.E])`,其中 `Math.E` 是 `JavaScript` 获取 `e` 的值的方法。 - - -##### 示例代码 - -##### db.command.aggregate.ln - 聚合操作符。计算给定数字在自然对数值。 - - 语法如下: - - -```js -db.command.aggregate.ln() -``` -`` 可以是任意解析为非负数字的表达式。 - - `ln` 等价于 `log([, Math.E])`,其中 `Math.E` 是 `JavaScript` 获取 `e` 的值的方法。 - -#### log - -计算给定数字在给定对数底下的 log 值。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.log([, ]) -``` -`` 可以是任意解析为非负数字的表达式。`` 可以是任意解析为大于 1 的数字的表达式。 - - 如果任一参数解析为 `null` 或指向任意一个不存在的字段,`log` 返回 `null`。如果任一参数解析为 `NaN`,`log` 返回 `NaN`。 - - -##### 示例代码 - 假设集合 `curve` 有如下记录: - - -```js -{ _id: 1, x: 1 } -{ _id: 2, x: 2 } -{ _id: 3, x: 3 } -``` -计算 `log2(x)` 的值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('curve').aggregate() - .project({ - log: $.log(['$x', 2]) - }) - .end() -``` -返回结果如下: - - -```js -{ _id: 1, log: 0 } -{ _id: 2, log: 1 } -{ _id: 3, log: 1.58496250072 } -``` - -#### log10 - -计算给定数字在对数底为 10 下的 log 值。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.log() -``` -`` 可以是任意解析为非负数字的表达式。 - - `log10` 等同于 `log` 方法的第二个参数固定为 10。 - - -##### 示例代码 - -##### db.command.aggregate.log10 - 聚合操作符。计算给定数字在对数底为 10 下的 log 值。 - - 语法如下: - - -```js -db.command.aggregate.log() -``` -`` 可以是任意解析为非负数字的表达式。 - - `log10` 等同于 `log` 方法的第二个参数固定为 10。 - -#### mod - -取模运算,取数字取模后的值。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.mod([, ]) -``` -第一个数字是被除数,第二个数字是除数。参数可以是任意解析为数字的表达式。 - - -##### 示例代码 - 假设集合 `shopping` 有如下记录: - - -```js -{ _id: 1, bags: 3, items: 5 } -{ _id: 2, bags: 2, items: 8 } -{ _id: 3, bags: 5, items: 16 } -``` -各记录取 `items` 除以 `bags` 的余数(`items % bags`): - - -```js -const $ = db.command.aggregate -let res = await db.collection('shopping').aggregate() - .project({ - overflow: $.mod(['$items', '$bags']) - }) - .end() -``` -返回结果如下: - - -```js -{ _id: 1, log: 2 } -{ _id: 2, log: 0 } -{ _id: 3, log: 1 } -``` - -#### multiply - -取传入的数字参数相乘的结果。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.multiply([, , ...]) -``` -参数可以是任意解析为数字的表达式。 - - -##### 示例代码 - 假设集合 `fruits` 有如下记录: - - -```js -{ "_id": 1, "name": "apple", "price": 10, "quantity": 100 } -{ "_id": 2, "name": "orange", "price": 15, "quantity": 50 } -{ "_id": 3, "name": "lemon", "price": 5, "quantity": 20 } -``` -求各个水果的的总价值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('fruits').aggregate() - .project({ - name: 1, - total: $.multiply(['$price', '$quantity']), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "name": "apple", "total": 1000 } -{ "_id": 2, "name": "orange", "total": 750 } -{ "_id": 3, "name": "lemo", "total": 100 } -``` - -#### pow - -求给定基数的指数次幂。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.pow([, ]) -``` -参数可以是任意解析为数字的表达式。 - - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ "_id": 1, "x": 2, "y": 3 } -{ "_id": 2, "x": 5, "y": 7 } -{ "_id": 3, "x": 10, "y": 20 } -``` -求 `x` 和 `y` 的平方和: - - -```js -const $ = db.command.aggregate -let res = await db.collection('stats').aggregate() - .project({ - sumOfSquares: $.add([$.pow(['$x', 2]), $.pow(['$y', 2])]), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "sumOfSquares": 13 } -{ "_id": 2, "sumOfSquares": 74 } -{ "_id": 3, "sumOfSquares": 500 } -``` - -#### sqrt - -求平方根。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.sqrt([]) -``` -参数可以是任意解析为非负数字的表达式。 - - -##### 示例代码 - 假设直角三角形集合 `triangle` 有如下记录: - - -```js -{ "_id": 1, "x": 2, "y": 3 } -{ "_id": 2, "x": 5, "y": 7 } -{ "_id": 3, "x": 10, "y": 20 } -``` -假设 `x` 和 `y` 分别为两直角边,则求斜边长: - - -```js -const $ = db.command.aggregate -let res = await db.collection('triangle').aggregate() - .project({ - len: $.sqrt([$.add([$.pow(['$x', 2]), $.pow(['$y', 2])])]), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "len": 3.605551275463989 } -{ "_id": 2, "len": 8.602325267042627 } -{ "_id": 3, "len": 22.360679774997898 } -``` - -#### subtract - -将两个数字相减然后返回差值,或将两个日期相减然后返回相差的毫秒数,或将一个日期减去一个数字返回结果的日期。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.subtract([, ]) -``` -参数可以是任意解析为数字或日期的表达式。 - - -##### 示例代码 - 假设集合 `scores` 有如下记录: - - -```js -{ "_id": 1, "max": 10, "min": 1 } -{ "_id": 2, "max": 7, "min": 5 } -{ "_id": 3, "max": 6, "min": 6 } -``` -求各个记录的 `max` 和 `min` 的差值。: - - -```js -const $ = db.command.aggregate -let res = await db.collection('scores').aggregate() - .project({ - diff: $.subtract(['$max', '$min']) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "diff": 9 } -{ "_id": 2, "diff": 2 } -{ "_id": 3, "diff": 0 } -``` - -#### trunc - -将数字截断为整形。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.trunc() -``` -参数可以是任意解析为数字的表达式。 - - -##### 示例代码 - 假设集合 `scores` 有如下记录: - - -```js -{ "_id": 1, "value": 1.21 } -{ "_id": 2, "value": 3.83 } -{ "_id": 3, "value": -4.94 } -``` - -```js -const $ = db.command.aggregate -let res = await db.collection('scores').aggregate() - .project({ - int: $.trunc('$value') - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "value": 1 } -{ "_id": 2, "value": 3 } -{ "_id": 3, "value": -4 } -``` - -### 数组操作符 - -#### arrayElemAt - -返回在指定数组下标的元素。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.arrayElemAt([, ]) -``` -`` 可以是任意解析为数组的表达式。 - - `` 可以是任意解析为整形的表达式。如果是正数,`arrayElemAt` 返回在 `index` 位置的元素,如果是负数,`arrayElemAt` 返回从数组尾部算起的 `index` 位置的元素。 - - -##### 示例代码 - 假设集合 `exams` 有如下记录: - - -```js -{ "_id": 1, "scores": [80, 60, 65, 90] } -{ "_id": 2, "scores": [78] } -{ "_id": 3, "scores": [95, 88, 92] } -``` -求各个第一次考试的分数和和最后一次的分数: - - -```js -const $ = db.command.aggregate -let res = await db.collection('exams').aggregate() - .project({ - first: $.arrayElemAt(['$scores', 0]), - last: $.arrayElemAt(['$scores', -1]), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "first": 80, "last": 90 } -{ "_id": 2, "first": 78, "last": 78 } -{ "_id": 3, "first": 95, "last": 92 } -``` - -#### arrayToObject - -将一个数组转换为对象。 - - -##### API 说明 - 语法可以取两种: - - 第一种:传入一个二维数组,第二维的数组长度必须为 2,其第一个值为字段名,第二个值为字段值 - - -```js -db.command.aggregate.arrayToObject([ - [, ], - [, ], - ... -]) -``` -第二种:传入一个对象数组,各个对象必须包含字段 `k` 和 `v`,分别指定字段名和字段值 - - -```js -db.command.aggregate.arrayToObject([ - { "k": , "v": }, - { "k": , "v": }, - ... -]) -``` -传入 `arrayToObject` 的参数只要可以解析为上述两种表示法之一即可。 - - -##### 示例代码 - 假设集合 `shops` 有如下记录: - - -```js -{ "_id": 1, "sales": [ ["max", 100], ["min", 50] ] } -{ "_id": 2, "sales": [ ["max", 70], ["min", 60] ] } -{ "_id": 3, "sales": [ { "k": "max", "v": 50 }, { "k": "min", "v": 30 } ] } -``` -将数组转换为对象: - - -```js -const $ = db.command.aggregate -let res = await db.collection('shops').aggregate() - .project({ - sales: $.arrayToObject('$sales'), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "sales": { "max": 100, "min": 50 } } -{ "_id": 2, "sales": { "max": 70, "min": 60 } } -{ "_id": 3, "sales": { "max": 50, "min": 30 } } -``` - -#### concatArrays - -将多个数组拼接成一个数组。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.concatArrays([ , , ... ]) -``` -参数可以是任意解析为数组的表达式。 - - -##### 示例代码 - 假设集合 `items` 有如下记录: - - -```js -{ "_id": 1, "fruits": [ "apple" ], "vegetables": [ "carrot" ] } -{ "_id": 2, "fruits": [ "orange", "lemon" ], "vegetables": [ "cabbage" ] } -{ "_id": 3, "fruits": [ "strawberry" ], "vegetables": [ "spinach" ] } -``` - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - list: $.concatArrays(['$fruits', '$vegetables']), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "list": [ "apple", "carrot" ] } -{ "_id": 2, "list": [ "orange", "lemon", "cabbage" ] } -{ "_id": 3, "list": [ "strawberry", "spinach" ] } -``` - -#### filter - -根据给定条件返回满足条件的数组的子集。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.filter({ - input: , - as: , - cond: -}) -``` - -|字段 |说明 | -|---- |---- | -|input|一个可以解析为数组的表达式 | -|as |可选,用于表示数组各个元素的变量,默认为 this | -|cond |一个可以解析为布尔值的表达式,用于判断各个元素是否满足条件,各个元素的名字由 as 参数决定(参数名需加 $$ 前缀,如 $$this)| - -参数可以是任意解析为数组的表达式。 - - -##### 示例代码 - 假设集合 `fruits` 有如下记录: - - -```json -{ - "_id": 1, - "stock": [ - { "name": "apple", "price": 10 }, - { "name": "orange", "price": 20 } - ], -} -{ - "_id": 2, - "stock": [ - { "name": "lemon", "price": 15 }, - ], -} -``` - -```js -const _ = db.command -const $ = db.command.aggregate -let res = await db.collection('fruits').aggregate() - .project({ - stock: $.filter({ - input: '$stock', - as: 'item', - cond: $.gte(['$$item.price', 15]) - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "stock": [ { "name": "orange", "price": 20} ] } -{ "_id": 2, "stock": [ { "name": "lemon", "price": 15 } ] } -``` - -#### in - -给定一个值和一个数组,如果值在数组中则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.in([, ]) -``` -`` 可以是任意表达式。 - - `` 可以是任意解析为数组的表达式。 - - -##### 示例代码 - 假设集合 `shops` 有如下记录: - - -```js -{ "_id": 1, "topsellers": ["bread", "ice cream", "butter"] } -{ "_id": 2, "topsellers": ["ice cream", "cheese", "yagurt"] } -{ "_id": 3, "topsellers": ["croissant", "cucumber", "coconut"] } -``` -标记销量最高的商品包含 `ice cream` 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - included: $.in(['ice cream', '$topsellers']) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "included": true } -{ "_id": 2, "included": true } -{ "_id": 3, "included": false } -``` - -#### indexOfArray - -在数组中找出等于给定值的第一个元素的下标,如果找不到则返回 -1。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.indexOfArray([ , , , ]) -``` - -|字段 |类型 |说明 | -|---- |---- |---- | -|- |string |一个可以解析为数组的表达式,如果解析为 null,则 indexOfArray 返回 null | -|- |string |对数据各个元素应用的条件匹配表达式 | -|- |integer|可选,用于指定搜索的开始下标,必须是非负整数 | -|- |integer|可选,用于指定搜索的结束下标,必须是非负整数,指定了 时也应指定 ,否则 默认当做| - -参数可以是任意解析为数组的表达式。 - - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```json -{ - "_id": 1, - "sales": [ 1, 6, 2, 2, 5 ] -} -{ - "_id": 2, - "sales": [ 4, 2, 1, 5, 2 ] -} -{ - "_id": 3, - "sales": [ 2, 5, 3, 3, 1 ] -} -``` - -```js -const $ = db.command.aggregate -let res = await db.collection('stats').aggregate() - .project({ - index: $.indexOfArray(['$sales', 2, 2]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "index": 2 } -{ "_id": 2, "index": 4 } -{ "_id": 3, "index": -1 } -``` - -#### isArray - -判断给定表达式是否是数组,返回布尔值。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.isArray() -``` -参数可以是任意表达式。 - - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ - "_id": 1, - "base": 10, - "sales": [ 1, 6, 2, 2, 5 ] -} -{ - "_id": 2, - "base": 1, - "sales": 100 -} -``` -计算总销量,如果 `sales` 是数字,则求 `sales * base`,如果 `sales` 是数组,则求数组元素之和与 `base` 的乘积。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('stats').aggregate() - .project({ - sum: $.cond({ - if: $.isArray('$sales'), - then: $.multiply([$.sum(['$sales']), '$base']), - else: $.multiply(['$sales', '$base']), - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "index": 160 } -{ "_id": 2, "index": 100 } -``` - -#### map - -类似 JavaScript Array 上的 `map` 方法,将给定数组的每个元素按给定转换方法转换后得出新的数组。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.map({ - input: , - as: , - in: -}) -``` - -|字段 |说明 | -|---- |---- | -|input|一个可以解析为数组的表达式 | -|as |可选,用于表示数组各个元素的变量,默认为 this | -|in |一个可以应用在给定数组的各个元素上的表达式,各个元素的名字由 as 参数决定(参数名需加 $$ 前缀,如 $$this)| - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ - "_id": 1, - "sales": [ 1.32, 6.93, 2.48, 2.82, 5.74 ] -} -{ - "_id": 2, - "sales": [ 2.97, 7.13, 1.58, 6.37, 3.69 ] -} -``` -将各个数字截断为整形,然后求和 - - -```js -const $ = db.command.aggregate -let res = await db.collection('stats').aggregate() - .project({ - truncated: $.map({ - input: '$sales', - as: 'num', - in: $.trunc('$$num'), - }) - }) - .project({ - total: $.sum('$truncated') - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "index": 16 } -{ "_id": 2, "index": 19 } -``` - -#### objectToArray - -将一个对象转换为数组。方法把对象的每个键值对都变成输出数组的一个元素,元素形如 `{ k: , v: }`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.objectToArray() -``` - -##### 示例代码 - 假设集合 `items` 有如下记录: - - -```js -{ "_id": 1, "attributes": { "color": "red", "price": 150 } } -{ "_id": 2, "attributes": { "color": "blue", "price": 50 } } -{ "_id": 3, "attributes": { "color": "yellow", "price": 10 } } -``` - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - array: $.objectToArray('$attributes') - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "array": [{ "k": "color", "v": "red" }, { "k": "price", "v": 150 }] } -{ "_id": 2, "array": [{ "k": "color", "v": "blue" }, { "k": "price", "v": 50 }] } -{ "_id": 3, "array": [{ "k": "color", "v": "yellow" }, { "k": "price", "v": 10 }] } -``` - -#### range - -返回一组生成的序列数字。给定开始值、结束值、非零的步长,`range` 会返回从开始值开始逐步增长、步长为给定步长、但不包括结束值的序列。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.range([, , ]) -``` - -|字段 |说明 | -|---- |---- | -|start |开始值,一个可以解析为整形的表达式 | -|end |结束值,一个可以解析为整形的表达式 | -|non-zero step|可选,步长,一个可以解析为非零整形的表达式,默认为 1 | - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ "_id": 1, "max": 52 } -{ "_id": 2, "max": 38 } -``` - -```js -const $ = db.command.aggregate -db.collection('stats').aggregate() - .project({ - points: $.range([0, '$max', 10]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "points": [0, 10, 20, 30, 40, 50] } -{ "_id": 2, "points": [0, 10, 20, 30] } -``` - -#### reduce - -类似 JavaScript 的 `reduce` 方法,应用一个表达式于数组各个元素然后归一成一个元素。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.reduce({ - input: - initialValue: , - in: -}) -``` - -|字段 |说明 | -|---- |---- | -|input |输入数组,可以是任意解析为数组的表达式 | -|initialValue |初始值 | -|in |用来作用于每个元素的表达式,在 in 中有两个可用变量,value 是表示累计值的变量,this 是表示当前数组元素的变量| - -##### 示例代码 - - -**简易字符串拼接** - - 假设集合 `player` 有如下记录: - - -```js -{ "_id": 1, "fullname": [ "Stephen", "Curry" ] } -{ "_id": 2, "fullname": [ "Klay", "Thompsom" ] } -``` -获取各个球员的全名,并加 `Player:` 前缀: - - -```js -const $ = db.command.aggregate -let res = await db.collection('player').aggregate() - .project({ - info: $.reduce({ - input: '$fullname', - initialValue: 'Player:', - in: $.concat(['$$value', ' ', '$$this']), - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "info": "Player: Stephen Curry" } -{ "_id": 2, "info": "Player: Klay Thompson" } -``` -获取各个球员的全名,不加前缀: - - -```js -const $ = db.command.aggregate -let res = await db.collection('player').aggregate() - .project({ - name: $.reduce({ - input: '$fullname', - initialValue: '', - in: $.concat([ - '$$value', - $.cond({ - if: $.eq(['$$value', '']), - then: '', - else: ' ', - }), - '$$this', - ]), - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "name": "Stephen Curry" } -{ "_id": 2, "name": "Klay Thompson" } -``` - -#### reverseArray - -返回给定数组的倒序形式。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.reverseArray() -``` -参数可以是任意解析为数组表达式。 - - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ - "_id": 1, - "sales": [ 1, 2, 3, 4, 5 ] -} -``` -取 `sales` 倒序: - - -```js -const $ = db.command.aggregate -let res = await db.collection('stats').aggregate() - .project({ - reversed: $.reverseArray('$sales'), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "reversed": [5, 4, 3, 2, 1] } -``` - -#### size - -返回数组长度。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.size() -``` -`` 可以是任意解析为数组的表达式。 - - -##### 示例代码 - 假设集合 `shops` 有如下记录: - - -```js -{ "_id": 1, "staff": [ "John", "Middleton", "George" ] } -{ "_id": 2, "staff": [ "Steph", "Jack" ] } -``` -计算各个商店的雇员数量: - - -```js -const $ = db.command.aggregate -let res = await db.collection('shops').aggregate() - .project({ - totalStaff: $.size('$staff') - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "totalStaff": 3 } -{ "_id": 2, "totalStaff": 2 } -``` - -#### slice - -类似 JavaScritp 的 `slice` 方法。返回给定数组的指定子集。 - - -##### API 说明 - 语法有两种: - - 返回从开头或结尾开始的 `n` 个元素: - - -```js -db.command.aggregate.slice([, ]) -``` -返回从指定位置算作数组开头、再向后或向前的 `n` 个元素: - - -```js -db.command.aggregate.slice([, , ]) -``` -`` 可以是任意解析为数组的表达式。 - - `` 可以是任意解析为整形的表达式。如果是正数,则将数组的第 `` 个元素作为数组开始;如果 `` 比数组长度更长,`slice` 返回空数组。如果是负数,则将数组倒数第 `` 个元素作为数组开始;如果 `` 的绝对值大于数组长度,则开始位置即为数组开始位置。 - - `` 可以是任意解析为整形的表达式。如果 `` 有提供,则 `` 必须为正整数。如果是正数,`slice` 返回前 `n` 个元素。如果是负数,`slice` 返回后 `n` 个元素。 - - -##### 示例代码 - 假设集合 `people` 有如下记录: - - -```js -{ "_id": 1, "hobbies": [ "basketball", "football", "tennis", "badminton" ] } -{ "_id": 2, "hobbies": [ "golf", "handball" ] } -{ "_id": 3, "hobbies": [ "table tennis", "swimming", "rowing" ] } -``` -统一返回前两个爱好: - - -```js -const $ = db.command.aggregate -let res = await db.collection('fruits').aggregate() - .project({ - hobbies: $.slice(['$hobbies', 2]), - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "hobbies": [ "basketball", "football" ] } -{ "_id": 2, "hobbies": [ "golf", "handball" ] } -{ "_id": 3, "hobbies": [ "table tennis", "swimming" ] } -``` - -#### zip - -把二维数组的第二维数组中的相同序号的元素分别拼装成一个新的数组进而组装成一个新的二维数组。如可将 `[ [ 1, 2, 3 ], [ "a", "b", "c" ] ]` 转换成 `[ [ 1, "a" ], [ 2, "b" ], [ 3, "c" ] ]`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.zip({ - inputs: [, , ...], - useLongestLength: , - defaults: -}) -``` -`inputs` 是一个二维数组(`inputs` 不可以是字段引用),其中每个元素的表达式(这个可以是字段引用)都可以解析为数组。如果其中任意一个表达式返回 `null`,`` 也返回 `null`。如果其中任意一个表达式不是指向一个合法的字段 / 解析为数组 / 解析为 `null`,则返回错误。 - - `useLongestLength` 决定输出数组的长度是否采用输入数组中的最长数组的长度。默认为 `false`,即输入数组中的最短的数组的长度即是输出数组的各个元素的长度。 - - `defaults` 是一个数组,用于指定在输入数组长度不一的情况下时采用的数组各元素默认值。指定这个字段则必须指定 `useLongestLength`,否则返回错误。如果 `useLongestLength` 是 `true` 但是 `defaults` 是空或没有指定,则 `zip` 用 `null` 做数组元素的缺省默认值。指定各元素默认值时 `defaults` 数组的长度必须是输入数组最大的长度。 - - -##### 示例代码 - 假设集合 `stats` 有如下记录: - - -```js -{ "_id": 1, "zip1": [1, 2], "zip2": [3, 4], "zip3": [5, 6] ] } -{ "_id": 2, "zip1": [1, 2], "zip2": [3], "zip3": [4, 5, 6] ] } -{ "_id": 3, "zip1": [1, 2], "zip2": [3] ] } -``` - - -**只传 inputs** - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - zip: $.zip({ - inputs: [ - '$zip1', // 字段引用 - '$zip2', - '$zip3', - ], - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } -{ "_id": 2, "zip": [ [1, 3, 4] ] } -{ "_id": 3, "zip": null } -``` - - -**设置 useLongestLength** - - 如果设 `useLongestLength` 为 `true`: - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - zip: $.zip({ - inputs: [ - '$zip1', // 字段引用 - '$zip2', - '$zip3', - ], - useLongestLength: true, - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } -{ "_id": 2, "zip": [ [1, 3, 4], [2, null, 5], [null, null, 6] ] } -{ "_id": 3, "zip": null } -``` - - -**设置 defaults** - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - zip: $.zip({ - inputs: [ - '$zip1', // 字段引用 - '$zip2', - '$zip3', - ], - useLongestLength: true, - defaults: [-300, -200, -100], - }) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "zip": [ [1, 3, 5], [2, 4, 6] ] } -{ "_id": 2, "zip": [ [1, 3, 4], [2, -200, 5], [-300, -200, 6] ] } -{ "_id": 3, "zip": null } -``` - -### 布尔操作符 - -#### and - -给定多个表达式,`and` 仅在所有表达式都返回 `true` 时返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.and([, , ...]) -``` -如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 - - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "min": 10, "max": 100 } -{ "_id": 2, "min": 60, "max": 80 } -{ "_id": 3, "min": 30, "max": 50 } -``` -求 `min` 大于等于 30 且 `max` 小于等于 80 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - fullfilled: $.and([$.gte(['$min', 30]), $.lte(['$max', 80])]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "fullfilled": false } -{ "_id": 2, "fullfilled": true } -{ "_id": 3, "fullfilled": true } -``` - -#### not - -给定一个表达式,如果表达式返回 `true`,则 `not` 返回 `false`,否则返回 `true`。注意表达式不能为逻辑表达式(`and`、`or`、`nor`、`not`)。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.not() -``` -如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 - - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "min": 10, "max": 100 } -{ "_id": 2, "min": 60, "max": 80 } -{ "_id": 3, "min": 30, "max": 50 } -``` -求 `min` 不大于 40 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - fullfilled: $.not($.gt(['$min', 40])) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "fullfilled": true } -{ "_id": 2, "fullfilled": false } -{ "_id": 3, "fullfilled": true } -``` - -#### or - -给定多个表达式,如果任意一个表达式返回 `true`,则 `or` 返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.or([, , ...]) -``` -如果表达式返回 `false`、`null`、`0`、或 `undefined`,表达式会解析为 `false`,否则对其他返回值都认为是 `true`。 - - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "min": 10, "max": 100 } -{ "_id": 2, "min": 60, "max": 80 } -{ "_id": 3, "min": 30, "max": 50 } -``` -求 `min` 小于 40 且 `max` 大于 60 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - fullfilled: $.or([$.lt(['$min', 30]), $.gt(['$max', 60])]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "fullfilled": true } -{ "_id": 2, "fullfilled": false } -{ "_id": 3, "fullfilled": true } -``` - -### 比较操作符 - -#### cmp - -给定两个值,返回其比较值: - - -##### API 说明 - 如果第一个值小于第二个值,返回 -1 -如果第一个值大于第二个值,返回 1 -如果两个值相等,返回 0 - - 语法如下: - - -```js -db.command.aggregate.cmp([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "shop1": 10, "shop2": 100 } -{ "_id": 2, "shop1": 80, "shop2": 20 } -{ "_id": 3, "shop1": 50, "shop2": 50 } -``` -求 `shop1` 和 `shop2` 的各个物品的价格对比。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - compare: $.cmp(['$shop1', '$shop2'])) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "compare": -1 } -{ "_id": 2, "compare": 1 } -{ "_id": 3, "compare": 0 } -``` - -#### eq - -匹配两个值,如果相等则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.eq([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -求 `value` 等于 50 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - matched: $.eq(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": false } -{ "_id": 2, "matched": false } -{ "_id": 3, "matched": true } -``` - -#### gt - -匹配两个值,如果前者大于后者则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.gt([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -判断 `value` 是否大于 50。 - - -```js -const $ = db.command.aggregate -db.collection('price').aggregate() - .project({ - matched: $.gt(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": false } -{ "_id": 2, "matched": true } -{ "_id": 3, "matched": false } -``` - -#### gte - -匹配两个值,如果前者大于或等于后者则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.gte([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -判断 `value` 是否大于或等于 50。 - - -```js -const $ = db.command.aggregate -let res = await b.collection('price').aggregate() - .project({ - matched: $.gte(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": false } -{ "_id": 2, "matched": true } -{ "_id": 3, "matched": true } -``` - -#### lt - -匹配两个值,如果前者小于后者则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.lt([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -``` -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -判断 `value` 是否小于 50。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - matched: $.lt(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": true } -{ "_id": 2, "matched": false } -{ "_id": 3, "matched": false } -``` - -#### lte - -匹配两个值,如果前者小于或等于后者则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.lte([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -判断 `value` 是否小于 50。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - matched: $.lte(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": true } -{ "_id": 2, "matched": false } -{ "_id": 3, "matched": true } -``` - -#### neq - -匹配两个值,如果不相等则返回 `true`,否则返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.neq([, ]) -``` - -##### 示例代码 - 假设集合 `price` 有如下记录: - - -```js -{ "_id": 1, "value": 10 } -{ "_id": 2, "value": 80 } -{ "_id": 3, "value": 50 } -``` -求 `value` 不等于 50 的记录。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('price').aggregate() - .project({ - matched: $.neq(['$value', 50]) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "matched": true } -{ "_id": 2, "matched": true } -{ "_id": 3, "matched": false } -``` - -### 条件操作符 - -#### cond - -计算布尔表达式,返回指定的两个值其中之一。 - - -##### API 说明 - `cond` 的使用形式如下: - - -```js -cond({ if: <布尔表达式>, then: <真值>, else: <假值> }) -``` -或者: - - -```js -cond([ <布尔表达式>, <真值>, <假值> ]) -``` -两种形式中,三个参数(`if`、`then`、`else`)都是必须的。 - - 如果布尔表达式为真,那么 `$cond` 将会返回 `<真值>`,否则会返回 `<假值>` - - -##### 示例代码 - 假设集合 `items` 的记录如下: - - -```js -{ "_id": "0", "name": "item-a", "amount": 100 } -{ "_id": "1", "name": "item-b", "amount": 200 } -{ "_id": "2", "name": "item-c", "amount": 300 } -``` -我们可以使用 `cond`,根据 `amount` 字段,来生成新的字段 `discount`: - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - name: 1, - discount: $.cond({ - if: $.gte(['$amount', 200]), - then: 0.7, - else: 0.9 - }) - }) - .end() -``` -输出如下: - - -```js -{ "_id": "0", "name": "item-a", "discount": 0.9 } -{ "_id": "1", "name": "item-b", "discount": 0.7 } -{ "_id": "2", "name": "item-c", "discount": 0.7 } -``` - -#### ifNull - -计算给定的表达式,如果表达式结果为 null、undefined 或者不存在,那么返回一个替代值;否则返回原值。 - - -##### API 说明 - `ifNull` 的使用形式如下: - - -```js -ifNull([ <表达式>, <替代值> ]) -``` - -##### 示例代码 - 假设集合 `items` 的记录如下: - - -```js -{ "_id": "0", "name": "A", "description": "这是商品A" } -{ "_id": "1", "name": "B", "description": null } -{ "_id": "2", "name": "C" } -``` -我们可以使用 `ifNull`,对不存在 `desc` 字段的文档,或者 `desc` 字段为 `null` 的文档,补充一个替代值。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - _id: 0, - name: 1, - description: $.ifNull(['$description', '商品描述空缺']) - }) - .end() -``` -输出如下: - - -```js -{ "name": "A", "description": "这是商品A" } -{ "name": "B", "description": "商品描述空缺" } -{ "name": "C", "description": "商品描述空缺" } -``` - -#### switch - -根据给定的 `switch-case-default` 计算返回值、 - - -##### API 说明 - `switch` 的使用形式如下: - - -```js -switch({ - branches: [ - case: <表达式>, then: <表达式>, - case: <表达式>, then: <表达式>, - ... - ], - default: <表达式> -}) -``` - -##### 示例代码 - 假设集合 `items` 的记录如下: - - -```js -{ "_id": "0", "name": "item-a", "amount": 100 } -{ "_id": "1", "name": "item-b", "amount": 200 } -{ "_id": "2", "name": "item-c", "amount": 300 } -``` -我们可以使用 `switch`,根据 `amount` 字段,来生成新的字段 `discount`: - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - name: 1, - discount: $.switch({ - branches: [ - { case: $.gt(['$amount', 250]), then: 0.8 }, - { case: $.gt(['$amount', 150]), then: 0.9 } - ], - default: 1 - }) - }) - .end() -``` -输出如下: - - -```js -{ "_id": "0", "name": "item-a", "discount": 1 } -{ "_id": "1", "name": "item-b", "discount": 0.9 } -{ "_id": "2", "name": "item-c", "discount": 0.8 } -``` - -### 日期操作符 - -**注意** - -- 以下日期操作符中`timezone`均支持以下几种形式 - -```js -timezone: "Asia/Shanghai" // Asia/Shanghai时区 -timezone: "+08" // utc+8时区 -timezone: "+08:30" // 时区偏移8小时30分 -timezone: "+0830" // 时区偏移8小时30分,同上 -``` - -#### dateFromParts - -给定日期的相关信息,构建并返回一个日期对象。 - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.dateFromParts({ - year: , - month: , - day: , - hour: , - minute: , - second: , - millisecond: , - timezone: -}) -``` -你也可以使用 ISO 8601 的标准: - - -```js -db.command.aggregate.dateFromParts({ - isoWeekYear: , - isoWeek: , - isoDayOfWeek: , - hour: , - minute: , - second: , - millisecond: , - timezone: -}) -``` - -##### 示例代码 - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - date: $.dateFromParts({ - year: 2017, - month: 2, - day: 8, - hour: 12, - timezone: 'America/New_York' - }), - }) - .end() -``` -输出如下: - - -```js -{ - "date": ISODate("2017-02-08T17:00:00.000Z") -} -``` - -#### dateFromString - -将一个日期/时间字符串转换为日期对象 - -##### API 说明 - 语法如下: - - -```js -db.command.aggregate.dateFromString({ - dateString: , - timezone: -}) -``` - -##### 示例代码 - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - date: $.dateFromString({ - dateString: "2019-05-14T09:38:51.686Z" - }) - }) - .end() -``` -输出如下: - - -```js -{ - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -#### dateToString - -根据指定的表达式将日期对象格式化为符合要求的字符串。 - -##### API 说明 - `dateToString` 的调用形式如下: - - -```js -db.command.aggregate.dateToString({ - date: <日期表达式>, - format: <格式化表达式>, - timezone: <时区表达式>, - onNull: <空值表达式> -}) -``` -下面是四种表达式的详细说明: - -|名称 |描述 | -|---- |---- | -|日期表达式 |必选。指定字段值应该是能转化为字符串的日期。 | -|格式化表达式 |可选。它可以是任何包含“格式说明符”的有效字符串。 | -|时区表达式 |可选。指明运算结果的时区。它可以解析格式为 [UTC Offset](https://en.wikipedia.org/wiki/List_of_UTC_time_offsets) 或者 [Olson Timezone Identifier](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) 的字符串。| -|空值表达式 |可选。当 <日期表达式> 返回空或者不存在的时候,会返回此表达式指明的值。 | - -下面是格式说明符的详细说明: - -|说明符 |描述 |合法值 | -|---- |---- |---- | -|%d |月份的日期(2位数,0填充) |01 - 31 | -|%G |ISO 8601 格式的年份 |0000 - 9999| -|%H |小时(2位数,0填充,24小时制) |00 - 23 | -|%j |一年中的一天(3位数,0填充) |001 - 366 | -|%L |毫秒(3位数,0填充) |000 - 999 | -|%m |月份(2位数,0填充) |01 - 12 | -|%M |分钟(2位数,0填充) |00 - 59 | -|%S |秒(2位数,0填充) |00 - 60 | -|%w |星期几 |1 - 7 | -|%u |ISO 8601 格式的星期几 |1 - 7 | -|%U |一年中的一周(2位数,0填充) |00 - 53 | -|%V |ISO 8601 格式的一年中的一周 |1 - 53 | -|%Y |年份(4位数,0填充) |0000 - 9999| -|%z |与 UTC 的时区偏移量 |+/-[hh][mm]| -|%Z |以分钟为单位,与 UTC 的时区偏移量|+/-mmm | -|%% |百分号作为字符 |% | - -##### 示例代码 - 假设集合 `students` 有如下记录: - - -```js -{ "date": "1999-12-11T16:00:00.000Z", "firstName": "Yuanxin", "lastName": "Dong" } -{ "date": "1998-11-10T16:00:00.000Z", "firstName": "Weijia", "lastName": "Wang" } -{ "date": "1997-10-09T16:00:00.000Z", "firstName": "Chengxi", "lastName": "Li" } -``` - - -**格式化日期** - - 下面是将 `date` 字段的值,格式化成形如 `年份-月份-日期` 的字符串: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - formatDate: $.dateToString({ - date: '$date', - format: '%Y-%m-%d' - }) - }) - .end() -``` -返回的结果如下: - - -```js -{ "formatDate": "1999-12-11" } -{ "formatDate": "1998-11-10" } -{ "formatDate": "1997-10-09" } -``` - - -**时区时间** - - 下面是将 `date` 字段值格式化为上海时区时间的例子: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - formatDate: $.dateToString({ - date: '$date', - format: '%H:%M:%S', - timezone: 'Asia/Shanghai' - }) - }) - .end() -``` -返回的结果如下: - - -```js -{ "formatDate": "00:00:00" } -{ "formatDate": "00:00:00" } -{ "formatDate": "00:00:00" } -``` - - -**缺失情况的默认值** - - 当指定的 `<日期表达式>` 返回空或者不存在的时候,可以设置缺失情况下的默认值: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - formatDate: $.dateToString({ - date: '$empty', - onNull: 'null' - }) - }) - .end() -``` -返回的结果如下: - - -```js -{ "formatDate": "null" } -{ "formatDate": "null" } -{ "formatDate": "null" } -``` - -#### dayOfMonth - -返回日期字段对应的天数(一个月中的哪一天),是一个介于 1 至 31 之间的数字。 - - -##### API 说明 - -该接口有以下两种用法,语法如下: - - -```js -db.command.aggregate.dayOfMonth(<日期字段>) - -db.command.aggregate.dayOfMonth({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - 假设集合 `dates` 有以下文档: - - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` -我们使用 `dayOfMonth()` 对 `date` 字段进行投影,获取对应的日期: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - dayOfMonth: $.dayOfMonth('$date') - }) - .end() -``` -输出如下: - - -```js -{ - "dayOfMonth": 14 -} -``` - -#### dayOfWeek - -返回日期字段对应的天数(一周中的第几天),是一个介于 1(周日)到 7(周六)之间的整数。 - - -##### API 说明 - -**注意:周日是每周的第 1 天** - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.dayOfWeek(<日期字段>) - -db.command.aggregate.dayOfWeek({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - 假设集合 `dates` 有以下文档: - - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` -我们使用 `dayOfWeek()` 对 `date` 字段进行投影,获取对应的天数(一周中的第几天): - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - dayOfWeek: $.dayOfWeek('$date') - }) - .end() -``` -输出如下: - - -```js -{ - "dayOfWeek": 3 -} -``` - -#### dayOfYear - -返回日期字段对应的天数(一年中的第几天),是一个介于 1 到 366 之间的整数。 - - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.dayOfYear(<日期字段>) - -db.command.aggregate.dayOfYear({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - 假设集合 `dates` 有以下文档: - - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` -我们使用 `dayOfYear()` 对 `date` 字段进行投影,获取对应的天数(一年中的第几天): - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - dayOfYear: $.dayOfYear('$date') - }) - .end() -``` -输出如下: - - -```js -{ - "dayOfYear": 134 -} -``` - -#### hour - -返回日期字段对应的小时数,是一个介于 0 到 23 之间的整数。 - - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.hour(<日期字段>) - -db.command.aggregate.hour({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - 假设集合 `dates` 有以下文档: - - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` -我们使用 `hour()` 对 `date` 字段进行投影,获取对应的小时数: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - hour: $.hour('$date') - }) - .end() -``` -输出如下: - - -```js -{ - "hour": 9 -} -``` - -#### isoDayOfWeek - -返回日期字段对应的 ISO 8601 标准的天数(一周中的第几天),是一个介于 1(周一)到 7(周日)之间的整数。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.isoDayOfWeek(<日期字段>) - -db.command.aggregate.isoDayOfWeek({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` -我们使用 `isoDayOfWeek()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的天数(一周中的第几天): - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - isoDayOfWeek: $.isoDayOfWeek('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "isoDayOfWeek": 2 -} -``` - -#### isoWeek - -返回日期字段对应的 ISO 8601 标准的周数(一年中的第几周),是一个介于 1 到 53 之间的整数。 - - -##### API 说明 - -根据 ISO 8601 标准,周一到周日视为一周,本年度第一个周四所在的那周,视为本年度的第 1 周。 - -例如:2016 年 1 月 7 日是那年的第一个周四,那么 2016.01.04(周一)到 2016.01.10(周日) 即为第 1 周。同理,2016 年 1 月 1 日的周数为 53。 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.isoWeek(<日期字段>) - -db.command.aggregate.isoWeek({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `isoWeek()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的周数(一年中的第几周): - - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - isoWeek: $.isoWeek('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "isoWeek": 20 -} -``` - -#### isoWeekYear - -返回日期字段对应的 ISO 8601 标准的天数(一年中的第几天)。 - -##### API 说明 - -此处的“年”以第一周的周一为开始,以最后一周的周日为结束。 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.isoWeekYear(<日期字段>) - -db.command.aggregate.isoWeekYear({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `isoWeekYear()` 对 `date` 字段进行投影,获取对应的 ISO 8601 标准的天数(一年中的第几天): - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - isoWeekYear: $.isoWeekYear('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "isoWeekYear": 2019 -} -``` - -#### millisecond - -返回日期字段对应的毫秒数,是一个介于 0 到 999 之间的整数。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.millisecond(<日期字段>) - -db.command.aggregate.millisecond({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `millisecond()` 对 `date` 字段进行投影,获取对应的毫秒数: - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - millisecond: $.millisecond('$date'), - }) - .end() -``` - -输出如下: - -```js -{ - "millisecond": 686 -} -``` - -#### minute - -返回日期字段对应的分钟数,是一个介于 0 到 59 之间的整数。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.minute(<日期字段>) - -db.command.aggregate.minute({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `minute()` 对 `date` 字段进行投影,获取对应的分钟数: - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - minute: $.minute('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "minute": 38 -} -``` - -#### month - -返回日期字段对应的月份,是一个介于 1 到 12 之间的整数。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.month(<日期字段>) - -db.command.aggregate.month({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - 假设集合 `dates` 有以下文档: - - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `month()` 对 `date` 字段进行投影,获取对应的月份: - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - month: $.month('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "month": 5 -} -``` - -#### second - -返回日期字段对应的秒数,是一个介于 0 到 59 之间的整数,在特殊情况下(闰秒)可能等于 60。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.second(<日期字段>) - -db.command.aggregate.second({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `second()` 对 `date` 字段进行投影,获取对应的秒数: - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - second: $.second('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "second": 51 -} -``` - -#### week - -返回日期字段对应的周数(一年中的第几周),是一个介于 0 到 53 之间的整数。 - -##### API 说明 - -每周以周日为开头,**每年的第一个周日**即为 `week 1` 的开始,这天之前是 `week 0`。 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.week(<日期字段>) - -db.command.aggregate.week({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `week()` 对 `date` 字段进行投影,获取对应的周数(一年中的第几周): - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - week: $.week('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "week": 19 -} -``` - -#### year - -返回日期字段对应的年份。 - -##### API 说明 - -该接口有以下两种用法,语法如下: - -```js -db.command.aggregate.year(<日期字段>) - -db.command.aggregate.year({date:<日期字段>,timezone:<时区>}) -``` - -##### 示例代码 - -假设集合 `dates` 有以下文档: - -```js -{ - "_id": 1, - "date": ISODate("2019-05-14T09:38:51.686Z") -} -``` - -我们使用 `year()` 对 `date` 字段进行投影,获取对应的年份: - -```js -const $ = db.command.aggregate -let res = await db - .collection('dates') - .aggregate() - .project({ - _id: 0, - year: $.year('$date') - }) - .end() -``` - -输出如下: - -```js -{ - "year": 2019 -} -``` - -#### subtract - -见[subtract](#subtract) - -### 常量操作符 - -#### literal - -直接返回一个值的字面量,不经过任何解析和处理。 - - -##### API 说明 - `literal` 使用形式如下: - - -```js -literal(<值>) -``` -如果 `<值>` 是一个表达式,那么 `literal` **不会**解析或者计算这个表达式,而是直接返回这个表达式。 - - -##### 示例代码 - 比如我们有一个 `items` 集合,其中数据如下: - - -```js -{ "_id": "0", "price": "$1" } -{ "_id": "1", "price": "$5.60" } -{ "_id": "2", "price": "$8.90" } -``` - - -**以字面量的形式使用 $** - - 下面的代码使用 `literal`,生成了一个新的字段 `isOneDollar`,表示 `price` 字段是否严格等于 `"$1"`。 - - 注意:我们这里无法使用 `eq(['$price', '$1'])`,因为 `"$1"` 是一个表达式,代表 `"1"` 字段对应的值,而不是字符串字面量 `"$1"`。 - - -```js -const $ = db.command.aggregate -let res = await db.collection('items').aggregate() - .project({ - isOneDollar: $.eq(['$price', $.literal('$1')]) - }) - .end() -``` -输出如下: - - -```js -{ "_id": "0", "isOneDollar": true } -{ "_id": "1", "isOneDollar": false } -{ "_id": "2", "isOneDollar": false } -``` - - -**投影一个字段,对应的值为 1** - - 下面的代码使用 `literal`,投影了一个新的字段 `amount`,其值为 `1`。 - - -```js -const $ = db.command.aggregate -db.collection('items').aggregate() - .project({ - price: 1, - amount: $.literal(1) - }) - .end() -``` -输出如下: - - -```js -{ "_id": "0", "price": "$1", "amount": 1 } -{ "_id": "1", "price": "$5.60", "amount": 1 } -{ "_id": "2", "price": "$8.90", "amount": 1 } -``` - -### 对象操作符 - -#### mergeObjects - -将多个文档合并为单个文档。 - -##### API 说明 - 使用形式如下: -在 `group()` 中使用时: - - -```js -mergeObjects() -``` -在其它表达式中使用时: - - -```js -mergeObjects([, , ...]) -``` - -##### 示例代码 - - -**搭配 `group()` 使用** - - 假设集合 `sales` 存在以下文档: - - -```js -{ "_id": 1, "year": 2018, "name": "A", "volume": { "2018Q1": 500, "2018Q2": 500 } } -{ "_id": 2, "year": 2017, "name": "A", "volume": { "2017Q1": 400, "2017Q2": 300, "2017Q3": 0, "2017Q4": 0 } } -{ "_id": 3, "year": 2018, "name": "B", "volume": { "2018Q1": 100 } } -{ "_id": 4, "year": 2017, "name": "B", "volume": { "2017Q3": 100, "2017Q4": 250 } } -``` -下面的代码使用 `mergeObjects()`,将用相同 `name` 的文档合并: - - -```js -const $ = db.command.aggregate -let res = await db.collection('sales').aggregate() - .group({ - _id: '$name', - mergedVolume: $.mergeObjects('$volume') - }) - .end() -``` -输出如下: - - -```js -{ "_id": "A", "mergedVolume": { "2017Q1": 400, "2017Q2": 300, "2017Q3": 0, "2017Q4": 0, "2018Q1": 500, "2018Q2": 500 } } -{ "_id": "B", "mergedVolume": { "2017Q3": 100, "2017Q4": 250, "2018Q1": 100 } } -``` - - -**一般用法** - -假设集合 `test` 存在以下文档: - - -```js -{ "_id": 1, "foo": { "a": 1 }, "bar": { "b": 2 } } -{ "_id": 2, "foo": { "c": 1 }, "bar": { "d": 2 } } -{ "_id": 3, "foo": { "e": 1 }, "bar": { "f": 2 } } -``` -下面的代码使用 `mergeObjects()`,将文档中的 `foo` 和 `bar` 字段合并为 `foobar`: - - -```js -const $ = db.command.aggregate -let res = await db.collection('sales').aggregate() - .project({ - foobar: $.mergeObjects(['$foo', '$bar']) - }) - .end() -``` -输出结果如下: - - -```js -{ "_id": 1, "foobar": { "a": 1, "b": 2 } } -{ "_id": 2, "foobar": { "c": 1, "d": 2 } } -{ "_id": 3, "foobar": { "e": 1, "f": 2 } } -``` - -#### objectToArray - -见[objectToArray](#objectToArray) - -### 集合操作符 - -#### allElementsTrue - -输入一个数组,或者数组字段的表达式。如果数组中所有元素均为真值,那么返回 `true`,否则返回 `false`。空数组永远返回 `true`。 - -##### API 说明 - -语法如下: - -```js -allElementsTrue([]) -``` - -##### 示例代码 - -假设集合 `test` 有如下记录: - -```js -{ "_id": 1, "array": [ true ] } -{ "_id": 2, "array": [ ] } -{ "_id": 3, "array": [ false ] } -{ "_id": 4, "array": [ true, false ] } -{ "_id": 5, "array": [ 0 ] } -{ "_id": 6, "array": [ "stark" ] } -``` -下面的代码使用 `allElementsTrue()`,判断 `array` 字段中是否均为真值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('price') - .aggregate() - .project({ - isAllTrue: $.allElementsTrue(['$array']) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "isAllTrue": true } -{ "_id": 2, "isAllTrue": true } -{ "_id": 3, "isAllTrue": false } -{ "_id": 4, "isAllTrue": false } -{ "_id": 5, "isAllTrue": false } -{ "_id": 6, "isAllTrue": true } -``` - -#### anyElementTrue - -输入一个数组,或者数组字段的表达式。如果数组中任意一个元素为真值,那么返回 `true`,否则返回 `false`。空数组永远返回 `false`。 - - -##### API 说明 - 语法如下: - - -```js -anyElementTrue([]) -``` - -##### 示例代码 - 假设集合 `test` 有如下记录: - - -```js -{ "_id": 1, "array": [ true ] } -{ "_id": 2, "array": [ ] } -{ "_id": 3, "array": [ false ] } -{ "_id": 4, "array": [ true, false ] } -{ "_id": 5, "array": [ 0 ] } -{ "_id": 6, "array": [ "stark" ] } -``` -下面的代码使用 `anyElementTrue()`,判断 `array` 字段中是否含有真值: - - -```js -const $ = db.command.aggregate -let res = await db.collection('price') - .aggregate() - .project({ - isAnyTrue: $.anyElementTrue(['$array']) - }) - .end() -``` -返回结果如下: - - -```js -{ "_id": 1, "isAnyTrue": true } -{ "_id": 2, "isAnyTrue": false } -{ "_id": 3, "isAnyTrue": false } -{ "_id": 4, "isAnyTrue": true } -{ "_id": 5, "isAnyTrue": false } -{ "_id": 6, "isAnyTrue": true } -``` - -#### setDifference - -输入两个集合,输出只存在于第一个集合中的元素。 - - -##### API 说明 - 使用形式如下: - - -```js -setDifference([, ]) -``` - -##### 示例代码 - 假设集合 `test` 存在以下数据: - - -```js -{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } -{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } -{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } -{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } -{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } -{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } -{ "_id": 7, "A": [ ], "B": [ ] } -{ "_id": 8, "A": [ ], "B": [ 1 ] } -``` -下面的代码使用 `setDifference`,找到只存在于 `B` 中的数字: - - -```js -let res = await db.collection('test') - .aggregate() - .project({ - isBOnly: $.setDifference(['$B', '$A']) - }) - .end() -``` - -```js -{ "_id": 1, "isBOnly": [] } -{ "_id": 2, "isBOnly": [] } -{ "_id": 3, "isBOnly": [3] } -{ "_id": 4, "isBOnly": [3] } -{ "_id": 5, "isBOnly": [] } -{ "_id": 6, "isBOnly": [{}, []] } -{ "_id": 7, "isBOnly": [] } -{ "_id": 8, "isBOnly": [1] } -``` - -#### setEquals - -输入两个集合,判断两个集合中包含的元素是否相同(不考虑顺序、去重)。 - - -##### API 说明 - 使用形式如下: - - -```js -setEquals([, ]) -``` - -##### 示例代码 - 假设集合 `test` 存在以下数据: - - -```js -{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } -{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } -{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } -{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } -{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } -{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } -{ "_id": 7, "A": [ ], "B": [ ] } -{ "_id": 8, "A": [ ], "B": [ 1 ] } -``` -下面的代码使用 `setEquals`,判断两个集合中包含的元素是否相同: - - -```js -let res = await db.collection('test') - .aggregate() - .project({ - sameElements: $.setEquals(['$A', '$B']) - }) - .end() -``` - -```js -{ "_id": 1, "sameElements": true } -{ "_id": 2, "sameElements": true } -{ "_id": 3, "sameElements": false } -{ "_id": 4, "sameElements": false } -{ "_id": 5, "sameElements": false } -{ "_id": 6, "sameElements": false } -{ "_id": 7, "sameElements": true } -{ "_id": 8, "sameElements": false } -``` - -#### setIntersection - -输入两个集合,输出两个集合的交集。 - - -##### API 说明 - 使用形式如下: - - -```js -setIntersection([, ]) -``` - -##### 示例代码 - 假设集合 `test` 存在以下数据: - - -```js -{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } -{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } -{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } -{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } -{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } -{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } -{ "_id": 7, "A": [ ], "B": [ ] } -{ "_id": 8, "A": [ ], "B": [ 1 ] } -``` -下面的代码使用 `setIntersection`,输出两个集合的交集: - - -```js -let res = await db.collection('test') - .aggregate() - .project({ - commonToBoth: $.setIntersection(['$A', '$B']) - }) - .end() -``` -输出如下: - - -```js -{ "_id": 1, "commonToBoth": [ 1, 2 ] } -{ "_id": 2, "commonToBoth": [ 1, 2 ] } -{ "_id": 3, "commonToBoth": [ 1, 2 ] } -{ "_id": 4, "commonToBoth": [ 1 ] } -{ "_id": 5, "commonToBoth": [ ] } -{ "_id": 6, "commonToBoth": [ ] } -{ "_id": 7, "commonToBoth": [ ] } -{ "_id": 8, "commonToBoth": [ ] } -``` - -#### setIsSubset - -输入两个集合,判断第一个集合是否是第二个集合的子集。 - - -##### API 说明 - 使用形式如下: - - -```js -setIsSubset([, ]) -``` - -##### 示例代码 - 假设集合 `test` 存在以下数据: - - -```js -{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } -{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } -{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } -{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } -{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } -{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } -{ "_id": 7, "A": [ ], "B": [ ] } -{ "_id": 8, "A": [ ], "B": [ 1 ] } -``` -下面的代码使用 `setIsSubset`,判断第一个集合是否是第二个集合的子集: - - -```js -let res = await db.collection('test') - .aggregate() - .project({ - AisSubsetOfB: $.setIsSubset(['$A', '$B']) - }) - .end() -``` - -```js -{ "_id": 1, "AisSubsetOfB": true } -{ "_id": 2, "AisSubsetOfB": true } -{ "_id": 3, "AisSubsetOfB": true } -{ "_id": 4, "AisSubsetOfB": false } -{ "_id": 5, "AisSubsetOfB": false } -{ "_id": 6, "AisSubsetOfB": false } -{ "_id": 7, "AisSubsetOfB": true } -{ "_id": 8, "AisSubsetOfB": true } -``` - -#### setUnion - -输入两个集合,输出两个集合的并集。 - - -##### API 说明 - 使用形式如下: - - -```js -setUnion([, ]) -``` - -##### 示例代码 - 假设集合 `test` 存在以下数据: - - -```js -{ "_id": 1, "A": [ 1, 2 ], "B": [ 1, 2 ] } -{ "_id": 2, "A": [ 1, 2 ], "B": [ 2, 1, 2 ] } -{ "_id": 3, "A": [ 1, 2 ], "B": [ 1, 2, 3 ] } -{ "_id": 4, "A": [ 1, 2 ], "B": [ 3, 1 ] } -{ "_id": 5, "A": [ 1, 2 ], "B": [ ] } -{ "_id": 6, "A": [ 1, 2 ], "B": [ {}, [] ] } -{ "_id": 7, "A": [ ], "B": [ ] } -{ "_id": 8, "A": [ ], "B": [ 1 ] } -``` -下面的代码使用 `setUnion`,输出两个集合的并集: - - -```js -let res = await db.collection('test') - .aggregate() - .project({ - AB: $.setUnion(['$A', '$B']) - }) - .end() -``` -输出如下: - - -```js -{ "_id": 1, "AB": [ 1, 2 ] } -{ "_id": 2, "AB": [ 1, 2 ] } -{ "_id": 3, "AB": [ 1, 2, 3 ] } -{ "_id": 4, "AB": [ 1, 2, 3 ] } -{ "_id": 5, "AB": [ 1, 2 ] } -{ "_id": 6, "AB": [ 1, 2, {}, [] ] } -{ "_id": 7, "AB": [ ] } -{ "_id": 8, "AB": [ 1 ] } -``` - -### 字符串操作符 - -#### concat - -连接字符串,返回拼接后的字符串。 - - -##### API 说明 - `concat` 的语法如下: - - -```js -db.command.aggregate.concat([<表达式1>, <表达式2>, ...]) -``` -表达式可以是形如 `$ + 指定字段`,也可以是普通字符串。只要能够被解析成字符串即可。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `concat` 可以拼接 `lastName` 和 `firstName` 字段,得到每位学生的名字全称: - - -```js -const $ = db.command.aggregate -db - .collection('students') - .aggregate() - .project({ - _id: 0, - fullName: $.concat(['$firstName', ' ', '$lastName']) - }) - .end() -``` -返回的结果如下: - - -```js -{ "fullName": "Yuanxin Dong" } -{ "fullName": "Weijia Wang" } -{ "fullName": "Chengxi Li" } -``` - -#### dateFromString - -见[dateFromString](#dateFromString) - -#### dateToString - -见[dateToString](#dateToString) - -#### indexOfBytes - -在目标字符串中查找子字符串,并返回第一次出现的 `UTF-8` 的字节索引(从0开始)。如果不存在子字符串,返回 -1。 - - -##### API 说明 - `indexOfBytes` 的语法如下: - - -```js -db.command.aggregate.indexOfBytes([<目标字符串表达式>, <子字符串表达式>, <开始位置表达式>, <结束位置表达式>]) -``` -下面是 4 种表达式的详细描述: - -|表达式 |描述 | -|---- |---- | -|目标字符串表达式 |任何可以被解析为字符串的表达式 | -|子字符串表达式 |任何可以被解析为字符串的表达式 | -|开始位置表达式 |任何可以被解析为非负整数的表达式 | -|结束位置表达式 |任何可以被解析为非负整数的表达式 | - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `indexOfBytes` 查找字符 `"a"` 在字段 `firstName` 中的位置: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - aStrIndex: $.indexOfBytes(['$firstName', 'a']) - }) - .end() -``` -返回的结果如下: - - -```js -{ "aStrIndex": 2 } -{ "aStrIndex": 5 } -{ "aStrIndex": -1 } -``` - -#### indexOfCP - -在目标字符串中查找子字符串,并返回第一次出现的 `UTF-8` 的 `code point` 索引(从0开始)。如果不存在子字符串,返回 -1。 - - -##### API 说明 - `code point` 是“码位”,又名“编码位置”。这里特指 `Unicode` 包中的码位,范围是从0(16进制)到10FFFF(16进制)。 - - `indexOfCP` 的语法如下: - - -```js -db.command.aggregate.indexOfCP([<目标字符串表达式>, <子字符串表达式>, <开始位置表达式>, <结束位置表达式>]) -``` -下面是 4 种表达式的详细描述: - -|表达式 |描述 | -|---- |---- | -|目标字符串表达式 |任何可以被解析为字符串的表达式 | -|子字符串表达式 |任何可以被解析为字符串的表达式 | -|开始位置表达式 |任何可以被解析为非负整数的表达式 | -|结束位置表达式 |任何可以被解析为非负整数的表达式 | - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `indexOfCP` 查找字符 `"a"` 在字段 `firstName` 中的位置: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - aStrIndex: $.indexOfCP(['$firstName', 'a']) - }) - .end() -``` -返回的结果如下: - - -```js -{ "aStrIndex": 2 } -{ "aStrIndex": 5 } -{ "aStrIndex": -1 } -``` - -#### split - -按照分隔符分隔数组,并且删除分隔符,返回子字符串组成的数组。如果字符串无法找到分隔符进行分隔,返回原字符串作为数组的唯一元素。 - - -##### API 说明 - `split` 的语法如下: - - -```js -db.command.aggregate.split([<字符串表达式>, <分隔符表达式>]) -``` -字符串表达式和分隔符表达式可以是任意形式的表达式,只要它可以被解析为字符串即可。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "birthday": "1999/12/12" } -{ "birthday": "1998/11/11" } -{ "birthday": "1997/10/10" } -``` -通过 `split` 将每条记录中的 `birthday` 字段对应值分隔成数组,每个数组分别由代表年、月、日的3个元素组成: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - birthday: $.split(['$birthday', '/']) - }) - .end() -``` -返回的结果如下: - - -```js -{ "birthday": [ "1999", "12", "12" ] } -{ "birthday": [ "1998", "11", "11" ] } -{ "birthday": [ "1997", "10", "10" ] } -``` - -#### strLenBytes - -计算并返回指定字符串中 `utf-8` 编码的字节数量。 - - -##### API 说明 - `strLenBytes` 的语法如下: - - -```js -db.command.aggregate.strLenBytes(<表达式>) -``` -只要表达式可以被解析成字符串,那么它就是有效表达式。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "name": "dongyuanxin", "nickname": "心谭" } -``` -借助 `strLenBytes` 计算 `name` 字段和 `nickname` 字段对应值的字节长度: - - -```js -const $ = db.command.aggregate -db - .collection('students') - .aggregate() - .project({ - _id: 0, - nameLength: $.strLenBytes('$name'), - nicknameLength: $.strLenBytes('$nickname') - }) - .end() -``` -返回结果如下: - - -```js -{ "nameLength": 11, "nicknameLength": 6 } -``` - -#### strLenCP - -计算并返回指定字符串的UTF-8 [code points](http://www.unicode.org/glossary/#code_point) 数量。 - - -##### API 说明 - `strLenCP` 的语法如下: - - -```js -db.command.aggregate.strLenCP(<表达式>) -``` -只要表达式可以被解析成字符串,那么它就是有效表达式。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "name": "dongyuanxin", "nickname": "心谭" } -``` -借助 `strLenCP` 计算 `name` 字段和 `nickname` 字段对应值的UTF-8 [code points](http://www.unicode.org/glossary/#code_point)的数量: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - nameLength: $.strLenCP('$name'), - nicknameLength: $.strLenCP('$nickname') - }) - .end() -``` -返回结果如下: - - -```js -{ "nameLength": 11, "nicknameLength": 2 } -``` - -#### strcasecmp - -对两个字符串在不区分大小写的情况下进行大小比较,并返回比较的结果。 - - -##### API 说明 - `strcasecmp` 的语法如下: - - -```js -db.command.aggregate.strcasecmp([<表达式1>, <表达式2>]) -``` -只要 `表达式1`和 `表达式2` 可以被解析成字符串,那么它们就是有效的。 - - 返回的比较结果有1,0和-1三种: - - -- 1:`表达式1` 解析的字符串 > `表达式2` 解析的字符串 - 0:`表达式1` 解析的字符串 = `表达式2` 解析的字符串 - -1:`表达式1` 解析的字符串 < `表达式2` 解析的字符串 - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `strcasecmp` 比较 `firstName` 字段值和 `lastName` 字段值的大小: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - result: $.strcasecmp(['$firstName', '$lastName']), - }) - .end() -``` -返回结果如下: - - -```js -{ "result": 1 } -{ "result": 1 } -{ "result": -1 } -``` - -#### substr - -返回字符串从指定位置开始的指定长度的子字符串。它是 `db.command.aggregate.substrBytes` 的别名,更推荐使用后者。 - - -##### API 说明 - `substr` 的语法如下: - - -```js -db.command.aggregate.substr([<表达式1>, <表达式2>, <表达式3>]) -``` -`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 - - 如果 `表达式2` 是负数,返回的结果为 `""`。 - - 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "birthday": "1999/12/12", "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "birthday": "1998/11/11", "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "birthday": "1997/10/10", "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `substr` 可以提取 `birthday` 中的年、月、日信息,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - year: $.substr(['$birthday', 0, 4]), - month: $.substr(['$birthday', 5, 2]), - day: $.substr(['$birthday', 8, -1]) - }) - .end() -``` -返回的结果如下: - - -```js -{ "day": "12", "month": "12", "year": "1999" } -{ "day": "11", "month": "11", "year": "1998" } -{ "day": "10", "month": "10", "year": "1997" } -``` - -#### substrBytes - -返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 `UTF-8` 字节索引的字符开始,长度为指定的字节数。 - - -##### API 说明 - `substrBytes` 的语法如下: - - -```js -db.command.aggregate.substrBytes([<表达式1>, <表达式2>, <表达式3>]) -``` -`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 - - 如果 `表达式2` 是负数,返回的结果为 `""`。 - - 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "birthday": "1999/12/12", "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "birthday": "1998/11/11", "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "birthday": "1997/10/10", "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `substrBytes` 可以提取 `birthday` 中的年、月、日信息,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - year: $.substrBytes(['$birthday', 0, 4]), - month: $.substrBytes(['$birthday', 5, 2]), - day: $.substrBytes(['$birthday', 8, -1]) - }) - .end() -``` -返回的结果如下: - - -```js -{ "day": "12", "month": "12", "year": "1999" } -{ "day": "11", "month": "11", "year": "1998" } -{ "day": "10", "month": "10", "year": "1997" } -``` - -#### substrCP - -返回字符串从指定位置开始的指定长度的子字符串。子字符串是由字符串中指定的 `UTF-8` 字节索引的字符开始,长度为指定的字节数。 - - -##### API 说明 - `substrCP` 的语法如下: - - -```js -db.command.aggregate.substrCP([<表达式1>, <表达式2>, <表达式3>]) -``` -`表达式1` 是任何可以解析为字符串的有效表达式,`表达式2` 和 `表达式3` 是任何可以解析为数字的有效表达式。 - - 如果 `表达式2` 是负数,返回的结果为 `""`。 - - 如果 `表达式3` 是负数,返回的结果为从 `表达式2` 指定的开始位置以及之后其余部分的子字符串。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "name": "dongyuanxin", "nickname": "心谭" } -``` -借助 `substrCP` 可以提取 `nickname` 字段值的第一个汉字: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - firstCh: $.substrCP(['$nickname', 0, 1]) - }) - .end() -``` -返回的结果如下: - - -```js -{ "firstCh": "心" } -``` - -#### toLower - -将字符串转化为小写并返回。 - - -##### API 说明 - `toLower` 的语法如下: - - -```js -db.command.aggregate.toLower(表达式) -``` -只要表达式可以被解析成字符串,那么它就是有效表达式。例如:`$ + 指定字段`。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `toLower` 将 `firstName` 的字段值转化为小写: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - result: $.toLower('$firstName'), - }) - .end() -``` -返回的结果如下: - - -```js -{ "result": "yuanxin" } -{ "result": "weijia" } -{ "result": "chengxi" } -``` - -#### toUpper - -将字符串转化为大写并返回。 - - -##### API 说明 - `toUpper` 的语法如下: - - -```js -db.command.aggregate.toUpper(表达式) -``` -只要表达式可以被解析成字符串,那么它就是有效表达式。例如:`$ + 指定字段`。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "firstName": "Yuanxin", "group": "a", "lastName": "Dong", "score": 84 } -{ "firstName": "Weijia", "group": "a", "lastName": "Wang", "score": 96 } -{ "firstName": "Chengxi", "group": "b", "lastName": "Li", "score": 80 } -``` -借助 `toUpper` 将 `lastName` 的字段值转化为大写: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .project({ - _id: 0, - result: $.toUpper('$lastName'), - }) - .end() -``` -返回的结果如下: - -```js -{ "result": "DONG" } -{ "result": "WANG" } -{ "result": "LI" } -``` - -### 分组运算方法@accumulator - -#### addToSet - -聚合运算符。向数组中添加值,如果数组中已存在该值,不执行任何操作。它只能在 `group stage` 中使用。 - -##### API 说明 - -`addToSet` 语法如下: - -```js -db.command.aggregate.addToSet(<表达式>) -``` - -表达式是形如 `$ + 指定字段` 的字符串。如果指定字段的值是数组,那么整个数组会被当作一个元素。 - -##### 示例代码 - -假设集合 `passages` 的记录如下: - -```js -{ "category": "web", "tags": [ "JavaScript", "CSS" ], "title": "title1" } -{ "category": "System", "tags": [ "C++", "C" ], "title": "title2" } -``` - - -**非数组字段** - - 每条记录的 `category` 对应值的类型是非数组,利用 `addToSet` 统计所有分类: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('passages') - .aggregate() - .group({ - _id: null, - categories: $.addToSet('$category') - }) - .end() -``` -返回的结果如下: - - -```js -{ "_id": null, "categories": [ "System", "web" ] } -``` - - -**数组字段** - - 每条记录的 `tags` 对应值的类型是数组,数组不会被自动展开: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('passages') - .aggregate() - .group({ - _id: null, - tagsList: $.addToSet('$tags') - }) - .end() -``` -返回的结果如下: - - -```js -{ "_id": null, "tagsList": [ [ "C++", "C" ], [ "JavaScript", "CSS" ] ] } -``` - -#### avg - - - -返回一组集合中,指定字段对应数据的平均值。 - - -##### API 说明 - `avg` 的语法如下: - - -```js -db.command.aggregate.avg() -``` -`avg` 传入的值除了数字常量外,也可以是任何最终解析成一个数字的表达式。它会忽略非数字值。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -借助 `avg` 可以计算所有记录的 `score` 的平均值: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .group({ - _id: null, - average: $.avg('$score') - }) - .end() -``` -返回的结果如下: - - -```js -{ "_id": null, "average": 90 } -``` - -#### first - -返回指定字段在一组集合的第一条记录对应的值。仅当这组集合是按照某种定义排序( `sort` )后,此操作才有意义。 - - -##### API 说明 - `first` 的语法如下: - - -```js -db.command.aggregate.first(<表达式>) -``` -表达式是形如 `$ + 指定字段` 的字符串。 - - `first` 只能在 `group` 阶段被使用,并且需要配合 `sort` 才有意义。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -如果需要得到所有记录中 `score` 的最小值,可以先将所有记录按照 `score` 排序,然后取出第一条记录的 `first`。 - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .sort({ - score: 1 - }) - .group({ - _id: null, - min: $.first('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": null, "min": 80 } -``` - -#### last - -返回指定字段在一组集合的最后一条记录对应的值。仅当这组集合是按照某种定义排序( `sort` )后,此操作才有意义。 - - -##### API 说明 - `last` 的语法如下: - - -```js -db.command.aggregate.last(<表达式>) -``` -表达式是形如 `$ + 指定字段` 的字符串。 - - `last` 只能在 `group` 阶段被使用,并且需要配合 `sort` 才有意义。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -如果需要得到所有记录中 `score` 的最大值,可以先将所有记录按照 `score` 排序,然后取出最后一条记录的 `last`。 - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .sort({ - score: 1 - }) - .group({ - _id: null, - max: $.last('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": null, "max": 100 } -``` - -#### max - -返回一组数值的最大值。 - - -##### API 说明 - `max` 的语法如下: - - -```js -db.command.aggregate.max(<表达式>) -``` -表达式是形如 `$ + 指定字段` 的字符串。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -借助 `max` 可以统计不同组( `group` )中成绩的最高值,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .group({ - _id: '$group', - maxScore: $.max('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": "b", "maxScore": 100 } -{ "_id": "a", "maxScore": 96 } -... -``` - -#### mergeObjects - -见[mergeObjects](#mergeObjects) - -#### min - -返回一组数值的最小值。 - - -##### API 说明 - `min` 的语法如下: - - -```js -db.command.aggregate.min(<表达式>) -``` -表达式是形如 `$ + 指定字段` 的字符串。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -借助 `min` 可以统计不同组( `group` )中成绩的最低值,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .group({ - _id: '$group', - minScore: $.min('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": "b", "minScore": 80 } -{ "_id": "a", "minScore": 84 } -``` - -#### push - -在 `group` 阶段,返回一组中表达式指定列与对应的值,一起组成的数组。 - - -##### API 说明 - `push` 语法如下: - - -```js -db.command.aggregate.push({ - <字段名1>: <指定字段1>, - <字段名2>: <指定字段2>, - ... -}) -``` - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "group": "a", "name": "stu1", "score": 84 } -{ "group": "a", "name": "stu2", "score": 96 } -{ "group": "b", "name": "stu3", "score": 80 } -{ "group": "b", "name": "stu4", "score": 100 } -``` -借助 `push` 操作,对不同分组( `group` )的所有记录,聚合所有数据并且将其放入一个新的字段中,进一步结构化和语义化数据。 - - -```js -const $ = db.command.aggregate -let res = await db - .collection('students') - .aggregate() - .group({ - _id: '$group', - students: $.push({ - name: '$name', - score: '$score' - }) - }) - .end() -``` -输出结果如下: - - -```js -{ "_id": "b", "students": [{ "name": "stu3", "score": 80 }, { "name": "stu4", "score": 100 }] } -{ "_id": "a", "students": [{ "name": "stu1", "score": 84 }, { "name": "stu2", "score": 96 }] } -``` - -#### stdDevPop - -返回一组字段对应值的标准差。 - - -##### API 说明 - `stdDevPop` 的使用形式如下: - - -```js -db.command.aggregate.stdDevPop(<表达式>) -``` -表达式传入的是指定字段,指定字段对应的值的数据类型必须是 `number` ,否则结果会返回 `null`。 - - -##### 示例代码 - 假设集合 `students` 的记录如下:`a` 组同学的成绩分别是84和96,`b`组同学的成绩分别是80和100。 - - -```js -{ "group":"a", "score":84 } -{ "group":"a", "score":96 } -{ "group":"b", "score":80 } -{ "group":"b", "score":100 } -``` -可以用 `stdDevPop` 来分别计算 `a` 和 `b` 两组同学成绩的标准差,以此来比较哪一组同学的成绩更稳定。代码如下: - - -```js -const $ = db.command.aggregate -let res = await db.collection('students').aggregate() - .group({ - _id: '$group', - stdDev: $.stdDevPop('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": "b", "stdDev": 10 } -{ "_id": "a", "stdDev": 6 } -``` - -#### stdDevSamp - -计算输入值的样本标准偏差。如果输入值代表数据总体,或者不概括更多的数据,请改用 `db.command.aggregate.stdDevPop`。 - - -##### API 说明 - `stdDevSamp` 的使用形式如下: - - -```js -db.command.aggregate.stdDevSamp(<表达式>) -``` -表达式传入的是指定字段,`stdDevSamp` 会自动忽略非数字值。如果指定字段所有的值均是非数字,那么结果返回 `null`。 - - -##### 示例代码 - 假设集合 `students` 的记录如下: - - -```js -{ "score": 80 } -{ "score": 100 } -``` -可以用 `stdDevSamp` 来计算成绩的标准样本偏差。代码如下: - - -```js -const $ = db.command.aggregate -let res = await db.collection('students').aggregate() - .group({ - _id: null, - ageStdDev: $.stdDevSamp('$score') - }) - .end() -``` -返回的数据结果如下: - - -```js -{ "_id": null, "ageStdDev": 14.142135623730951 } -``` -如果向集合 `students` 添加一条新记录,它的 `score` 字段类型是 `string`: - - -```js -{ "score": "aa" } -``` -用上面代码计算标准样本偏差时,`stdDevSamp` 会自动忽略类型不为 `number` 的记录,返回结果保持不变。 - -#### sum - - - -计算并且返回一组字段所有数值的总和。 - - -##### API 说明 - `sum` 的使用形式如下: - - -```js -db.command.aggregate.sum(<表达式>) -``` -表达式可以传入指定字段,也可以传入指定字段组成的列表。`sum` 会自动忽略非数字值。如果字段下的所有值均是非数字,那么结果返回 0。若传入数字常量,则当做所有记录该字段的值都给给定常量,在聚合时相加,最终值为输入记录数乘以常量。 - - -##### 示例代码 - 假设代表商品的集合 `goods` 的记录如下:`price` 代表商品销售额,`cost` 代表商品成本 - - -```js -{ "cost": -10, "price": 100 } -{ "cost": -15, "price": 1 } -{ "cost": -10, "price": 10 } -``` - - -**单独字段** - - 借助 `sum` 可以计算所有商品的销售总和,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('goods') - .aggregate() - .group({ - _id: null, - totalPrice: $.sum('$price') - }) - .end() -``` -返回的数据结果如下:销售额是 111 - - -```js -{ "_id": null, "totalPrice": 111 } -``` - - -**字段列表** - - 如果需要计算所有商品的利润总额,那么需要将每条记录的 `cost` 和 `price` 相加得到此记录对应商品的利润。最后再计算所有商品的利润总额。 - - 借助 `sum`,代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('goods') - .aggregate() - .group({ - _id: null, - totalProfit: $.sum( - $.sum(['$price', '$cost']) - ) - }) - .end() -``` -返回的数据结果如下:利润总额为 76 - - -```js -{ "_id": null, "totalProfit": 76 } -``` - -### 变量操作符 - -#### let - -自定义变量,并且在指定表达式中使用,返回的结果是表达式的结果。 - - -##### API 说明 - `let` 的语法如下: - - -```js -db.command.aggregate.let({ - vars: { - <变量1>: <变量表达式>, - <变量2>: <变量表达式>, - ... - }, - in: <结果表达式> -}) -``` -`vars` 中可以定义多个变量,变量的值由 `变量表达式` 计算而来,并且被定义的变量只有在 `in` 中的 `结果表达式` 才可以访问。 - - 在 `in` 的结果表达式中访问自定义变量时候,请在变量名前加上双美元符号( `$$` )并用引号括起来。 - - -##### 示例代码 - 假设代表商品的集合 `goods` 的记录如下:`price` 代表商品价格,`discount` 代表商品折扣率,`cost` 代表商品成本 - - -```js -{ "cost": -10, "discount": 0.95, "price": 100 } -{ "cost": -15, "discount": 0.98, "price": 1 } -{ "cost": -10, "discount": 1, "price": 10 } -``` -借助 `let` 可以定义并计算每件商品实际的销售价格,并将其赋值给自定义变量 `priceTotal`。最后再将 `priceTotal` 与 `cost` 进行取和( `sum` )运算,得到每件商品的利润。 - - 代码如下: - - -```js -const $ = db.command.aggregate -let res = await db - .collection('goods') - .aggregate() - .project({ - profit: $.let({ - vars: { - priceTotal: $.multiply(['$price', '$discount']) - }, - in: $.sum(['$$priceTotal', '$cost']) - }) - }) - .end() -``` -返回的数据结果如下: - - -``` -{ "profit": 85 } -{ "profit": -14.02 } -{ "profit": 0 } -``` - diff --git a/docs/uniCloud/jql.md b/docs/uniCloud/jql.md index 5191fada6c9f8995ca072b07afcd404fcb199aa7..33f543fd6ee31f4754cf986fa38d1a6810a1b999 100644 --- a/docs/uniCloud/jql.md +++ b/docs/uniCloud/jql.md @@ -2927,7 +2927,7 @@ unicloud-db组件也支持使用getTemp方法,结合multiSend可以与其他 ## MongoDB聚合操作@aggregate -JQL API支持使用聚合操作读取数据,关于聚合操作请参考[聚合操作](uniCloud/cf-database.md?id=aggregate) +JQL API支持使用聚合操作读取数据,关于聚合操作请参考[聚合操作](uniCloud/cf-database-aggregate.md) 例:取status等于1的随机20条数据 @@ -3458,7 +3458,7 @@ const res = await db.collection('article') .get() ``` -上述代码使用add方法将publish_date时间戳转为日期类型,再用dateToString将上一步的日期按照时区'+0800'(北京时间),格式化为`4位年-2位月-2位日`格式,完整格式化参数请参考[dateToString](uniCloud/cf-database.md?id=datetostring)。 +上述代码使用add方法将publish_date时间戳转为日期类型,再用dateToString将上一步的日期按照时区'+0800'(北京时间),格式化为`4位年-2位月-2位日`格式,完整格式化参数请参考[dateToString](uniCloud/cf-database-aggregate-operator.md?id=datetostring)。 上述代码执行结果为 diff --git a/docs/uniCloud/release.md b/docs/uniCloud/release.md index 707e4341dd4b03e364926ce9c2ec0b1ff94f508f..57e3af108e109b4008dc98a6bd06e7af7355c36d 100644 --- a/docs/uniCloud/release.md +++ b/docs/uniCloud/release.md @@ -275,7 +275,7 @@ + uni-id 新增 修改passwordSecret功能 [详情](https://uniapp.dcloud.net.cn/uniCloud/uni-id?id=modifysecret) #### 2020-11-13 - + 阿里云支持事务(startTransaction方式,暂不支持runTransaction)[详情](https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=starttransaction) + + 阿里云支持事务(startTransaction方式,暂不支持runTransaction)[详情](https://uniapp.dcloud.net.cn/uniCloud/cf-database?id=start-transaction) #### 2020-10-31 + 新增 ``组件支持remove方法,封装了删除确认框、删除数据库、删除前端data等操作,开发更便利 [详情](https://uniapp.dcloud.io/uniCloud/uni-clientdb-component?id=%e6%96%b9%e6%b3%95)