Docs 菜单
Docs 主页
/ / /
Kotlin Sync 驱动程序

聚合表达式操作

在此页面上

  • 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()方法从聚合阶段创建管道。 然后,每个示例将管道传递给MongoCollectionaggregate()方法。

注意

驱动程序生成的 Query API 表达式可能与每个示例提供的 Query API 表达式有所不同。不过,这两种表达式将产生相同的聚合结果。

重要

该驾驶员不为 Query API中的所有聚合管道操作符提供方法。 要在聚合中使用不支持的操作,必须使用BSON Document类型定义整个表达式。

您可以使用本部分所述方法对类型为 MqlIntegerMqlNumber 的值执行算术操作。

假设您有特定年份的天气数据,其中包括每天的降水量测量值(以英寸为单位)。 您想要查找每个月的平均降水量(以毫米为单位)。

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
})
)
)
)
)

注意

为了提高可读性,上示例为totalSeatsisAvailable变量分配了中间值。 如果您没有将这些中间值分配给变量,代码仍会产生等效结果。

以下代码在 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 ,则表示成员资格级别,并会使用该值。 如果数据类型为布尔值,则为成员资格级别返回GoldGuest 。 如果数据类型为大量,则返回大量中与最新成员资格级别匹配的最新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" ]
}}}]

您可以使用本节中描述的方法对类型为 MqlMapMqlEntry 的值执行映射操作。

方法
聚合管道操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符
没有相应的操作符

假设您有一个库存数据集合,其中每个文档代表您负责供应的单个项目。 每个文档都包含一个字段,该字段是所有仓库的映射,以及它们在该物品的库存中拥有多少副本。 您想要确定所有仓库中物品的副本总数。 此集合中的文档可能如下所示:

{
"_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() 方法将 lastNameemployeeID 字段合并为一个用户名,而 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
} }
} } ]

后退

通过聚合转换数据