聚合表达式操作
Overview
在本指南中,您可以学习;了解如何使用Kotlin Sync驾驶员构造用于聚合管道的表达式。 您可以使用可发现且类型安全的Kotlin方法执行表达式操作,而无需使用BSON文档。 由于这些方法遵循流式接口模式,因此您可以聚合操作链接在一起以创建紧凑且自然可读的代码。
本指南中的操作使用 com.mongodb.client.model.mql 包。这些方法提供了使用 Query API 的惯用方式,而 Query API 正是驱动程序与 MongoDB 部署交互的机制。 要了解有关 Query API的更多信息,请参阅MongoDB Server手册文档。
如何使用运算
本指南中的示例假定您的代码中包含以下导入语句:
import com.mongodb.client.model.Aggregates import com.mongodb.client.model.Accumulators import com.mongodb.client.model.Projections import com.mongodb.client.model.Filters import com.mongodb.client.model.mql.MqlValues
要访问权限表达式中的文档字段,您必须使用 current()
方法引用聚合管道正在处理的当前文档。 要访问权限字段的值,必须使用适当类型的方法,例如getString()
或getDate()
。 为字段指定类型时,您要确保驾驶员仅提供与该类型兼容的方法。 以下代码展示了如何引用名为 name
的string字段:
current().getString("name")
要在某一操作中指定值,请将其传递给 of()
构造函数方法以将其转换为有效类型。以下代码展示了如何引用 1.0
值:
of(1.0)
要创建操作,请将方法链接到字段或值引用。 您可以通过链接多个方法来构建更复杂的操作。
以下示例创建一个操作,用于查找新墨西哥州至少看过一次医生的患者。该操作执行以下任务:
使用
gt()
方法检查visitDates
大量值的大小是否大于0
使用
eq()
方法检查state
字段值是否为“新墨西哥州”。
and()
方法将这些操作联系起来,因此管道阶段仅匹配满足这两个条件的文档。
current() .getArray("visitDates") .size() .gt(of(0)) .and(current() .getString("state") .eq(of("New Mexico")))
group()
等聚合阶段直接接受操作,而其他阶段则希望您首先将操作包含在 computed()
或 expr()
等方法中。这些方法采用 TExpression
类型的值,支持您在某些聚合中使用表达式。
要完成聚合管道阶段,请将表达式包含在聚合生成器方法中。以下列表举例说明了如何将表达式包含在常用聚合生成器方法中:
match(expr(<expression>))
project(fields(computed("<field name>", <expression>)))
group(<expression>)
要学习;了解有关这些方法的详情,请参阅 《使用聚合转换数据》指南。
构造器方法
您可以使用这些构造方法来定义 Kotlin 聚合表达式所使用的值。
方法 | 说明 |
---|---|
引用聚合管道正在处理的当前文档。 | |
将聚合管道正在处理的当前文档引用为映射值。 | |
返回与所提供基元相对应的 MqlValue 类型。 | |
返回与所提供基元数组相对应的 MqlValue 类型数组。 | |
返回条目值。 | |
返回一个空的地图值。 | |
返回 Query API 中存在的空值。 |
重要
当您向这些方法之一提供值时,驱动程序会按字面意思处理。例如,of("$x")
表示字符串值 "$x"
,而不是名为 x
的字段。
有关使用这些方法的示例,请参阅操作下的任何部分。
操作
以下部分提供了驱动程序中可用聚合表达式操作的相关信息和示例。这些操作会按用途和功能进行分类。
每个部分都有一个表,其中描述了驾驶员中可用的聚合方法以及查询API中相应的表达式操作符。 方法名称链接到API文档,聚合管道操作符名称链接到服务器手册文档中的描述和示例。 虽然每种方法实际上等效于相应的聚合操作符,但它们在预期参数和实施方面可能有所不同。
每节中的示例使用listOf()
方法从聚合阶段创建管道。 然后,每个示例将管道传递给MongoCollection
的aggregate()
方法。
注意
驱动程序生成的 Query API 表达式可能与每个示例提供的 Query API 表达式有所不同。不过,这两种表达式将产生相同的聚合结果。
重要
该驾驶员不为 Query API中的所有聚合管道操作符提供方法。 要在聚合中使用不支持的操作,必须使用BSON Document
类型定义整个表达式。
算术运算
您可以使用本部分所述方法对类型为 MqlInteger
或 MqlNumber
的值执行算术操作。
方法 | 聚合管道操作符 |
---|---|
假设您有特定年份的天气数据,其中包括每天的降水量测量值(以英寸为单位)。 您想要查找每个月的平均降水量(以毫米为单位)。
multiply()
操作符将precipitation
字段乘以25.4
,将字段值转换为毫米。 avg()
累加器方法将平均值作为avgPrecipMM
字段返回。 group()
方法按每个文档的date
字段中给出的月份对值进行分组。
以下代码展示此聚合的管道:
val month = current().getDate("date").month(of("UTC")) val precip = current().getInteger("precipitation") val results = collection.aggregate<Document>( listOf( Aggregates.group( month, Accumulators.avg("avgPrecipMM", precip.multiply(25.4)) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $group: { _id: { $month: "$date" }, avgPrecipMM: { $avg: { $multiply: ["$precipitation", 25.4] } } } } ]
数组运算
您可以使用本节中描述的方法对类型为 MqlArray
的值执行数组操作。
方法 | 聚合管道操作符 |
---|---|
假设您有一个电影集合,其中每个集合都包含近期放映时间的嵌套文档数组。每个嵌套文档都包含一个数组,表示电影院的座位总数,其中第一个数组条目是高级座位数,第二个条目是普通座位数。每个嵌套文档还包含已购电影票的数量。此集合中的文档可能如下所示:
{ "_id": ..., "movie": "Hamlet", "showtimes": [ { "date": "May 14, 2023, 12:00 PM", "seats": [ 20, 80 ], "ticketsBought": 100 }, { "date": "May 20, 2023, 08:00 PM", "seats": [ 10, 40 ], "ticketsBought": 34 }] }
filter()
方法仅显示与提供的谓词匹配的结果。 在本例中,谓词使用sum()
计算座位总数,并使用lt()
方法将该值与ticketsBought
的数量进行比较。 project()
方法将这些筛选结果存储为新的availableShowtimes
大量字段。
提示
使用getArray()
方法时,必须指定大量包含的值的类型,才能将值作为任何特定类型来处理。 示例,必须指定包含整数的大量,才能在应用程序的其他位置使用这些整数执行计算。
本节中的示例指定seats
大量包含类型为MqlDocument
的值,以便它可以从每个大量条目中提取嵌套字段。
以下代码展示此聚合的管道:
val showtimes = current().getArray<MqlDocument>("showtimes") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed("availableShowtimes", showtimes .filter { showtime -> val seats = showtime.getArray<MqlInteger>("seats") val totalSeats = seats.sum { n -> n } val ticketsBought = showtime.getInteger("ticketsBought") val isAvailable = ticketsBought.lt(totalSeats) isAvailable }) ) ) ) )
注意
为了提高可读性,上示例为totalSeats
和isAvailable
变量分配了中间值。 如果您没有将这些中间值分配给变量,代码仍会产生等效结果。
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { availableShowtimes: { $filter: { input: "$showtimes", as: "showtime", cond: { $lt: [ "$$showtime.ticketsBought", { $sum: "$$showtime.seats" } ] } } } } } ]
布尔运算
您可以使用本节中描述的方法对类型为 MqlBoolean
的值执行布尔运算。
假设您想将极低或极高的天气温度读数(华氏度)归类为极端温度。
or()
操作符通过使用lt()
和gt()
方法将temperature
字段与预定义值进行比较,以检查温度是否极端。 project()
方法将此结果记录在extremeTemp
字段。
以下代码展示此聚合的管道:
val temperature = current().getInteger("temperature") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed( "extremeTemp", temperature .lt(of(10)) .or(temperature.gt(of(95))) ) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { extremeTemp: { $or: [ { $lt: ["$temperature", 10] }, { $gt: ["$temperature", 95] } ] } } } ]
比较运算
您可以使用本节中描述的方法对类型为 MqlValue
的值执行比较操作。
提示
cond()
方法类似于Kotlin中的三元操作符,您可以将其用于基于布尔值的简单分支。 使用switchOn()
方法进行更复杂的比较,例如对值类型执行模式匹配或对值执行其他任意检查。
方法 | 聚合管道操作符 |
---|---|
以下示例展示与 location
字段的值为 "California"
的所有文档相匹配的管道:
val location = current().getString("location") val results = collection.aggregate<Document>( listOf( Aggregates.match( Filters.expr(location.eq(of("California"))) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $match: { location: { $eq: "California" } } } ]
条件操作
您可以使用本部分所述方法执行条件操作。
方法 | 聚合管道操作符 |
---|---|
假设您有一个客户集合,其中包含客户的会员信息。最初,客户要么是会员,要么不是。随着时间的推移,会员级别被引入并使用相同的字段。该字段所存储的信息可以有不同的类型,而您希望通过标准化的值来表示客户的会员级别。
switchOn()
方法按顺序检查每个子句。 如果该值与子句指示的类型匹配,则该子句将确定与成员资格级别相对应的string值。 如果原始值为string ,则表示成员资格级别,并会使用该值。 如果数据类型为布尔值,则为成员资格级别返回Gold
或Guest
。 如果数据类型为大量,则返回大量中与最新成员资格级别匹配的最新string 。 如果member
字段的类型未知, switchOn()
方法将提供默认值Guest
。
以下代码展示此聚合的管道:
val member = current().getField("member") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed("membershipLevel", member.switchOn { field -> field .isString { s -> s } .isBoolean { b -> b.cond(of("Gold"), of("Guest")) } .isArray { a -> a.last() } .defaults { d -> of("Guest") } }) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { membershipLevel: { $switch: { branches: [ { case: { $eq: [ { $type: "$member" }, "string" ] }, then: "$member" }, { case: { $eq: [ { $type: "$member" }, "bool" ] }, then: { $cond: { if: "$member", then: "Gold", else: "Guest" } } }, { case: { $eq: [ { $type: "$member" }, "array" ] }, then: { $last: "$member" } } ], default: "Guest" } } } } ]
便捷操作
您可以使用本节中描述的方法将自定义函数应用于类型为 MqlValue
的值。
为了提高可读性并允许代码重用,您可以将冗余代码移至静态方法中。 但是,您不能在Kotlin中直接链接静态方法。 passTo()
方法允许您将值链接到自定义静态方法中。
方法 | 聚合管道操作符 |
---|---|
没有相应的操作符 |
假设您想根据某些基准确定某个班级的表现。 您想要找到每个班级的平均期末成绩,并将其与基准值进行比较。
以下自定义方法 gradeAverage()
接收文档数组以及这些文档之间共享的整数字段的名称。该方法计算所提供数组的所有文档中该字段的平均值,并确定所提供数组的所有元素中该字段的平均值。evaluate()
方法将提供的值与两个提供的范围限值进行比较,并根据比较结果生成响应字符串:
fun gradeAverage(students: MqlArray<MqlDocument>, fieldName: String): MqlNumber { val sum = students.sum { student -> student.getInteger(fieldName) } val avg = sum.divide(students.size()) return avg } fun evaluate(grade: MqlNumber, cutoff1: MqlNumber, cutoff2: MqlNumber): MqlString { val message = grade.switchOn { on -> on .lte(cutoff1) { g -> of("Needs improvement") } .lte(cutoff2) { g -> of("Meets expectations") } .defaults { g -> of("Exceeds expectations") } } return message }
提示
通过使用passTo()
方法,您可以将自定义方法重复用于其他聚合。 示例,您可以使用gradeAverage()
方法查找按入学年份或学区(而不仅仅是班级)筛选的学生群组的平均成绩。 同样,您可以使用evaluate()
方法评估单个学生的表现或整个学校的表现。
passArrayTo()
方法获取所有学生的大量,并使用gradeAverage()
方法计算平均分数。 然后, passNumberTo()
方法使用evaluate()
方法来确定这些类的性能。 此示例使用project()
方法将结果存储为evaluation
字段。
以下代码展示此聚合的管道:
val students = current().getArray<MqlDocument>("students") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed("evaluation", students .passArrayTo { s -> gradeAverage(s, "finalGrade") } .passNumberTo { grade -> evaluate(grade, of(70), of(85)) }) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { evaluation: { $switch: { branches: [ { case: { $lte: [ { $avg: "$students.finalGrade" }, 70 ] }, then: "Needs improvement" }, { case: { $lte: [ { $avg: "$students.finalGrade" }, 85 ] }, then: "Meets expectations" } ], default: "Exceeds expectations" } } } } ]
转换操作
您可以使用本部分所介绍的方法执行转换操作,在某些 MqlValue
类型之间进行转换。
方法 | 聚合管道操作符 |
---|---|
没有相应的操作符 | |
没有相应的操作符 | |
假设您有一个包含毕业年份的学生数据集合,并且这些数据以字符串形式存储。您想要计算他们五年重聚的年份并将该值存储在新字段中。
parseInteger()
方法将 graduationYear
转换为整数,这样 add()
就可以计算出重聚年份。addFields()
方法将此结果存储为新的 reunionYear
字段。
以下代码展示此聚合的管道:
val students = current().getArray<MqlDocument>("students") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed("evaluation", students .passArrayTo { s -> gradeAverage(s, "finalGrade") } .passNumberTo { grade -> evaluate(grade, of(70), of(85)) }) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $addFields: { reunionYear: { $add: [ { $toInt: "$graduationYear" }, 5 ] } } } ]
日期操作符
您可以使用本部分介绍的方法对 MqlDate
类型的值执行日期操作。
方法 | 聚合管道操作符 |
---|---|
假设您有有关包裹递送的数据,并且想要匹配"America/New_York"
时区任何星期一发生的递送。
如果 deliveryDate
字段包含任何代表有效日期的字符串值,如 "2018-01-15T16:00:00Z"
或 "Jan 15, 2018, 12:00
PM EST"
,则您可以使用 parseDate()
方法将这些字符串转换为日期类型。
dayOfWeek()
方法可确定日期是星期几,然后将其转换为数字。 使用"America/New_York"
时区时,数字分配使用0
平均值星期日。 eq()
方法将此值与2
(即星期一)进行比较。
以下代码展示此聚合的管道:
val deliveryDate = current().getString("deliveryDate") val results = collection.aggregate<Document>( listOf( Aggregates.match( Filters.expr( deliveryDate .parseDate() .dayOfWeek(of("America/New_York")) .eq(of(2)) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $match: { $expr: { $eq: [ { $dayOfWeek: { date: { $dateFromString: { dateString: "$deliveryDate" } }, timezone: "America/New_York" }}, 2 ] } } } ]
文档操作
您可以使用本节中描述的方法对类型为 MqlDocument
的值执行文档操作。
方法 | 聚合管道操作符 |
---|---|
没有相应的操作符 | |
假设您有一个旧客户数据集合,其中包括作为mailing.address
字段下的子文档的地址。 您想要查找居住在华盛顿州的所有客户。 此集合中的文档可能如下所示:
{ "_id": ..., "customer.name": "Mary Kenneth Keller", "mailing.address": { "street": "601 Mongo Drive", "city": "Vasqueztown", "state": "CO", "zip": 27017 } }
getDocument()
方法会将 mailing.address
字段作为文档进行检索,因此可使用 getString()
方法来检索嵌套的 state
字段。eq()
方法会检查 state
字段的值是否为 "WA"
。
以下代码展示此聚合的管道:
val address = current().getDocument("mailing.address") val results = collection.aggregate<Document>( listOf( Aggregates.match( Filters.expr( address .getString("state") .eq(of("WA")) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $match: { $expr: { $eq: [{ $getField: { input: { $getField: { input: "$$CURRENT", field: "mailing.address"}}, field: "state" }}, "WA" ] }}}]
映射操作
您可以使用本节中描述的方法对类型为 MqlMap
或 MqlEntry
的值执行映射操作。
方法 | 聚合管道操作符 |
---|---|
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 |
假设您有一个库存数据集合,其中每个文档代表您负责供应的单个项目。 每个文档都包含一个字段,该字段是所有仓库的映射,以及它们在该物品的库存中拥有多少副本。 您想要确定所有仓库中物品的副本总数。 此集合中的文档可能如下所示:
{ "_id": ..., "item": "notebook" "warehouses": [ { "Atlanta", 50 }, { "Chicago", 0 }, { "Portland", 120 }, { "Dallas", 6 } ] }
entries()
方法会以数组的形式返回 warehouses
字段中的地图条目。sum()
方法会根据使用 getValue()
方法所检索到的数组中的值来计算各商品的总价值。此示例使用 project()
方法将结果存储为新的 totalInventory
字段。
以下代码展示此聚合的管道:
val warehouses = current().getMap<MqlNumber>("warehouses") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed("totalInventory", warehouses .entries() .sum { v -> v.getValue() }) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { totalInventory: { $sum: { $getField: { $objectToArray: "$warehouses" }, } } } } ]
字符串操作
您可以使用本节介绍的方法对 MqlString
类型的值执行字符串操作。
方法 | 聚合管道操作符 |
---|---|
假设您要根据员工的姓氏和员工 ID 为公司员工生成小写用户名。
append()
方法将 lastName
和 employeeID
字段合并为一个用户名,而 toLower()
方法可以使整个用户名小写。此示例使用 project()
方法将结果存储为新的 username
字段。
以下代码展示此聚合的管道:
val lastName = current().getString("lastName") val employeeID = current().getString("employeeID") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed( "username", lastName .append(employeeID) .toLower() ) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { username: { $toLower: { $concat: ["$lastName", "$employeeID"] } } } } ]
类型检查操作
您可以使用本节中描述的方法对类型为 MqlValue
的值执行类型检查操作。
这些方法不返回布尔值。 相反,您提供与该方法指定的类型匹配的默认值。 如果检查的值与方法类型匹配,则返回检查的值。 否则,将返回所提供的默认值。 要根据数据类型对分支逻辑进行编程,请参阅switchOn()
。
方法 | 聚合管道操作符 |
---|---|
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 | |
没有相应的操作符 |
假设您有评级数据的集合。早期版本的评论模式允许用户提交没有星级的负面评论。您希望将这些没有星级的负面评论转换为最低值 1 星。
isNumberOr()
方法返回 rating
的值,如果 rating
不是数字或为空,则返回 1
的值。project()
方法将此值作为新的 numericalRating
字段返回。
以下代码展示此聚合的管道:
val rating = current().getField("rating") val results = collection.aggregate<Document>( listOf( Aggregates.project( Projections.fields( Projections.computed( "numericalRating", rating .isNumberOr(of(1)) ) ) ) ) )
以下代码在 Query API 中提供等效的聚合管道:
[ { $project: { numericalRating: { $cond: { if: { $isNumber: "$rating" }, then: "$rating", else: 1 } } } } ]