文档菜单
文档首页
/ / /
Laravel MongoDB
/

聚合构建器

本页内容

  • 概述
  • 添加聚合构建器依赖项
  • 创建聚合管道
  • 示例文档
  • 匹配阶段示例
  • 分组阶段示例
  • 排序阶段示例
  • 投影阶段示例
  • 聚合管道示例
  • 创建自定义运算符工厂

在本指南中,您可以学习如何使用 Laravel 集成聚合构建器执行聚合操作和构建管道。聚合构建器允许您使用类型安全的语法来构建 MongoDB 聚合管道

聚合管道是一个数据处理管道,它按顺序对 MongoDB 数据库中的数据进行转换和计算,然后将结果输出为新的文档或文档集。

聚合管道由 聚合阶段 组成。聚合阶段使用操作符处理输入数据,并生成下一个阶段作为其输入的数据。

Laravel MongoDB 聚合构建器允许您构建聚合阶段和聚合管道。以下部分展示了如何使用聚合构建器创建聚合管道的阶段示例

  • 添加聚合构建器依赖项

  • 创建聚合管道

  • 创建自定义运算符工厂

提示

聚合构建器功能仅适用于 Laravel MongoDB 4.3 及以后的版本。有关在不使用聚合构建器的情况下运行聚合的更多信息,请参阅查询构建器指南中的聚合

聚合构建器是 mongodb/builder 包的一部分。您必须将此包作为依赖项添加到项目中才能使用它。运行以下命令将聚合构建器依赖项添加到您的应用程序

composer require mongodb/builder:^0.2

安装完成后,请验证composer.json 文件中是否包含以下行在 require 对象中

"mongodb/builder": "^0.2",

要启动聚合管道,请调用 Model::aggregate() 方法。然后,按照您希望它们运行的顺序链接聚合阶段方法。

聚合构建器包括以下命名空间,您可以将它们导入以构建聚合阶段

  • MongoDB\Builder\Accumulator

  • MongoDB\Builder\Expression

  • MongoDB\Builder\Query

  • MongoDB\Builder\Type

本节包含以下示例,展示了如何使用常见的聚合阶段以及如何组合阶段来构建聚合管道

要了解有关 MongoDB 聚合运算符的更多信息,请参阅服务器手册中的 聚合阶段

以下示例在由 User 模型表示的集合上运行聚合管道。您可以通过运行以下 insert() 方法添加示例数据

User::insert([
['name' => 'Alda Gröndal', 'occupation' => 'engineer', 'birthday' => new UTCDateTime(new DateTimeImmutable('2002-01-01'))],
['name' => 'Francois Soma', 'occupation' => 'engineer', 'birthday' => new UTCDateTime(new DateTimeImmutable('1998-02-02'))],
['name' => 'Janet Doe', 'occupation' => 'designer', 'birthday' => new UTCDateTime(new DateTimeImmutable('1987-03-03'))],
['name' => 'Eliud Nkosana', 'occupation' => 'engineer', 'birthday' => new UTCDateTime(new DateTimeImmutable('1984-04-04'))],
['name' => 'Bran Steafan', 'occupation' => 'engineer', 'birthday' => new UTCDateTime(new DateTimeImmutable('1998-05-05'))],
['name' => 'Ellis Lee', 'occupation' => 'designer', 'birthday' => new UTCDateTime(new DateTimeImmutable('1996-06-06'))],
]);

您可以将 match() 方法链接到您的聚合管道以指定查询过滤器。如果您省略此阶段,则 aggregate() 方法会输出模型集合中对应以下阶段的全部文档。

此聚合阶段通常放在第一位,以便通过使用可用索引来检索数据,并减少后续阶段处理的数据量。

提示

如果您省略 match() 方法,聚合管道将在其他聚合阶段之前匹配模型集合中所有对应的文档。

本例通过使用 MongoDB\Builder\Query 构建器构建一个查询过滤器,用于构建 匹配 聚合阶段的查询。匹配阶段包括以下标准

  • 使用 Query::or() 函数匹配查询过滤器之一的结果

  • 使用 Query::query()Query::eq() 函数匹配包含具有值为 "designer"occupation 字段的文档

  • 使用 Query::query()Query::eq() 函数匹配包含具有值为 "Eliud Nkosana"name 字段的文档

点击查看输出 按钮以查看运行代码后返回的文档

$pipeline = User::aggregate()
->match(Query::or(
Query::query(occupation: Query::eq('designer')),
Query::query(name: Query::eq('Eliud Nkosana')),
));
$result = $pipeline->get();
[
{
"_id": ...,
"name": "Janet Doe",
"occupation": "designer",
"birthday": {
"$date": {
"$numberLong": "541728000000"
}
}
},
{
"_id": ...,
"name": "Eliud Nkosana",
"occupation": "engineer",
"birthday": {
"$date": {
"$numberLong": "449884800000"
}
}
},
{
"_id": ...,
"name": "Ellis Lee",
"occupation": "designer",
"birthday": {
"$date": {
"$numberLong": "834019200000"
}
}
}
]

提示

Query::or() 函数对应于 MongoDB 查询操作符 $or。有关此操作符的更多信息,请参阅服务器手册中的 $or

您可以将 group() 方法链接到聚合管道中,通过执行计算并根据公共字段值对数据进行分组来修改数据结构。

此聚合阶段通常放置在匹配阶段之后,以减少后续阶段处理的数据。

本例使用 MongoDB\Builder\Expression 构造函数来在 group 聚合阶段中定义分组键。分组阶段指定以下分组行为

  • 将分组键的值(由 _id 字段表示)设置为 Expression 构造函数定义的字段值

  • 通过调用 Expression::fieldPath() 函数引用 occupation 字段中的文档值

单击 查看输出 按钮以查看运行代码返回的文档

$pipeline = User::aggregate()
->group(_id: Expression::fieldPath('occupation'));
$result = $pipeline->get();
[
{ "_id": "engineer" },
{ "_id": "designer" }
]

提示

此示例阶段执行与 distinct() 查询构建器方法类似的任务。有关 distinct() 方法的更多信息,请参阅 检索不同字段值 用例示例。

您可以将 sort() 方法链接到聚合管道中,以指定文档的输出顺序。

您可以将此聚合阶段添加到管道的任何位置。它通常放置在group阶段之后,因为它可能依赖于分组数据。我们建议尽可能晚地将排序阶段放置在管道中,以限制其处理的数据。

要指定排序,将字段值设置为Sort::Asc枚举进行升序排序,或设置为Sort::Desc枚举进行降序排序。

此示例显示了一个sort()聚合管道阶段,该阶段按name字段进行排序,以Sort::Desc排序,对应于逆字母顺序。点击查看输出按钮以查看运行代码返回的文档

$pipeline = User::aggregate()
->sort(name: Sort::Desc);
$result = $pipeline->get();
[
{
"_id": ...,
"name": "Janet Doe",
"occupation": "designer",
"birthday": {
"$date": {
"$numberLong": "541728000000"
}
}
},
{
"_id": ...,
"name": "Francois Soma",
"occupation": "engineer",
"birthday": {
"$date": {
"$numberLong": "886377600000"
}
}
},
{
"_id": ...,
"name": "Ellis Lee",
"occupation": "designer",
"birthday": {
"$date": {
"$numberLong": "834019200000"
}
}
},
{
"_id": ...,
"name": "Eliud Nkosana",
"occupation": "engineer",
"birthday": {
"$date": {
"$numberLong": "449884800000"
}
}
},
{
"_id": ...,
"name": "Bran Steafan",
"occupation": "engineer",
"birthday": {
"$date": {
"$numberLong": "894326400000"
}
}
},
{
"_id": ...,
"name": "Alda Gröndal",
"occupation": "engineer",
"birthday": {
"$date": {
"$numberLong": "1009843200000"
}
}
}
]

您可以将project()方法链接到您的聚合管道中,以指定此阶段要显示的文档的字段。

要指定要包含的字段,传递字段名和真值,例如1true。所有其他字段都将从输出中省略。

或者,要指定要排除的字段,传递每个字段名和假值,例如0false。所有其他字段都包含在输出中。

提示

当您指定要包含的字段时,默认包含_id字段。要排除_id字段,请明确在投影阶段排除它。

此示例显示了如何使用project()方法聚合阶段仅包含name字段,并排除所有其他字段。点击查看输出按钮以查看运行代码返回的数据

$pipeline = User::aggregate()
->project(_id: 0, name: 1);
$result = $pipeline->get();
[
{ "name": "Alda Gröndal" },
{ "name": "Francois Soma" },
{ "name": "Janet Doe" },
{ "name": "Eliud Nkosana" },
{ "name": "Bran Steafan" },
{ "name": "Ellis Lee" }
]

此聚合管道示例连接多个阶段。每个阶段都运行在每个前一个阶段的输出上。在本例中,阶段按顺序执行以下操作:

  • birth_year 字段添加到文档中,并将其值设置为从 birthday 字段提取的年份。

  • occupation 字段的值对文档进行分组,并使用 Accumulator::avg() 函数计算每个组的 birth_year 的平均值。将计算结果分配给 birth_year_avg 字段。

  • 按组键字段升序排序文档。

  • 从组键字段的值创建 profession 字段,包括 birth_year_avg 字段,并省略 _id 字段。

单击 查看输出 按钮,查看运行代码返回的数据

$pipeline = User::aggregate()
->addFields(
birth_year: Expression::year(
Expression::dateFieldPath('birthday'),
),
)
->group(
_id: Expression::fieldPath('occupation'),
birth_year_avg: Accumulator::avg(Expression::numberFieldPath('birth_year')),
)
->sort(_id: Sort::Asc)
->project(profession: Expression::fieldPath('_id'), birth_year_avg: 1, _id: 0);
[
{
"birth_year_avg": 1991.5,
"profession": "designer"
},
{
"birth_year_avg": 1995.5,
"profession": "engineer"
}
]

注意

由于此管道省略了 match() 阶段,因此初始阶段的输入是集合中的所有文档。

当使用聚合构建器创建聚合管道时,您可以在 自定义运算符工厂 中定义操作或阶段。自定义运算符工厂是一个返回聚合管道表达式或阶段的函数。您可以创建这些函数以提高代码的可读性和复用性。

本示例展示了如何创建和使用自定义运算符工厂,该工厂返回从指定日期字段提取年份的表达式。

以下函数接受包含日期的字段名称,并返回一个从日期中提取年份的表达式

public function yearFromField(string $dateFieldName): YearOperator
{
return Expression::year(
Expression::dateFieldPath($dateFieldName),
);
}

以下聚合管道包括以下阶段

  • addFields(),该函数调用自定义运算符工厂函数从 birthday 字段提取年份并将其分配给 birth_year 字段

  • project(),其输出仅包含 namebirth_year 字段

单击 查看输出 按钮,查看运行代码返回的数据

$pipeline = User::aggregate()
->addFields(birth_year: $this->yearFromField('birthday'))
->project(_id: 0, name: 1, birth_year: 1);
[
{
"name": "Alda Gröndal",
"birth_year": 2002
},
{
"name": "Francois Soma",
"birth_year": 1998
},
{
"name": "Janet Doe",
"birth_year": 1987
},
{
"name": "Eliud Nkosana",
"birth_year": 1984
},
{
"name": "Bran Steafan",
"birth_year": 1998
},
{
"name": "Ellis Lee",
"birth_year": 1996
}
]

返回

写入操作