Docs 菜单

聚合构建器

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

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

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

Laravel MongoDB 聚合构建器允许您构建聚合阶段和聚合管道。 以下部分举例说明如何使用聚合构建器创建聚合管道的各个阶段:

提示

聚合构建器功能仅在 Laravel MongoDB 4.3及更高版本中可用。 要学习;了解有关在不使用聚合构建器的情况下运行聚合的更多信息,请参阅 查询构建器指南中的聚合

要启动聚合管道,请调用 Model::aggregate() 方法。然后,链接聚合阶段方法并指定该阶段的必要参数。示例,您可以调用 sort()操作符方法来构建$sort 阶段。

聚合构建器包括以下命名空间,您可以导入这些命名空间来构建聚合阶段:

  • MongoDB\Builder\Accumulator

  • MongoDB\Builder\Expression

  • MongoDB\Builder\Query

  • MongoDB\Builder\Type

本节通过以下示例展示如何使用常见聚合阶段:

要了解有关MongoDB 聚合操作符的更多信息,请参阅 MongoDB Server手册中的 聚合阶段 。

以下示例在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字段的文档

单击 VIEW OUTPUT按钮查看运行以下代码返回的文档:

$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()函数对应于$or MongoDB 查询运算符。 要了解有关此操作符的更多信息,请参阅 MongoDB Server手册中的 $or 。

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

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

此示例使用MongoDB\Builder\Expression构建器定义群组聚合阶段的群组键。 小组赛阶段指定以下分组行为:

  • _id字段表示的组键的值设置为Expression构建器定义的字段值

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

单击VIEW OUTPUT按钮,查看运行代码返回的文档:

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

提示

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

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

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

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

此示例显示了sort()聚合管道阶段,该阶段按name字段到Sort::Desc对文档进行排序,对应于反向字母顺序。 单击VIEW OUTPUT按钮,查看运行代码返回的文档:

$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字段并从输出中排除所有其他字段。 单击VIEW OUTPUT按钮可查看运行代码返回的数据:

$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" }
]

要构建聚合管道,请调用 Model::aggregate() 方法,然后按照您希望聚合阶段的运行顺序链接聚合阶段。本节中的示例改编自服务器手册。每个示例都提供了示例数据的链接,您可以将其插入数据库以测试聚合操作。

本节通过以下示例展示如何使用常见聚合阶段:

此示例使用服务器手册中 $group 阶段参考的 计算计数、总和和平均值 部分中给出的示例数据。

以下代码示例计算 2014 年每一天的总销售额、平均销售数量和销售计数。为此,它使用包含以下阶段的聚合管道:

  1. $match 阶段,用于过滤包含 date字段且年份为 2014 的文档

  2. $群组阶段,按日期对文档进行群组,并计算群组的总销售额、平均销售数量和销售计数

  3. $sort 阶段,用于按群组的总销售额降序对结果进行排序

单击VIEW OUTPUT按钮,查看运行代码返回的数据:

$pipeline = Sale::aggregate()
->match(
date: [
Query::gte(new UTCDateTime(new DateTimeImmutable('2014-01-01'))),
Query::lt(new UTCDateTime(new DateTimeImmutable('2015-01-01'))),
],
)
->group(
_id: Expression::dateToString(Expression::dateFieldPath('date'), '%Y-%m-%d'),
totalSaleAmount: Accumulator::sum(
Expression::multiply(
Expression::numberFieldPath('price'),
Expression::numberFieldPath('quantity'),
),
),
averageQuantity: Accumulator::avg(
Expression::numberFieldPath('quantity'),
),
count: Accumulator::sum(1),
)
->sort(
totalSaleAmount: Sort::Desc,
);
[
{ "_id": "2014-04-04", "totalSaleAmount": { "$numberDecimal": "200" }, "averageQuantity": 15, "count": 2 },
{ "_id": "2014-03-15", "totalSaleAmount": { "$numberDecimal": "50" }, "averageQuantity": 10, "count": 1 },
{ "_id": "2014-03-01", "totalSaleAmount": { "$numberDecimal": "40" }, "averageQuantity": 1.5, "count": 2 }
]

此示例使用服务器手册中 $unwind 阶段参考的 展开嵌入式数组 部分中给出的示例数据。

以下代码示例按标签对售出的商品进行分组,并计算每个标签的总销售额。为此,它使用包含以下阶段的聚合管道:

  1. $unwind 阶段,为 大量中的每个元素输出单独的文档items

  2. $unwind 阶段为 items.tags 数组中的每个元素输出单独的文档

  3. $ 群组阶段,按标签值对文档群组,并计算具有每个标签的商品的总销售额

单击VIEW OUTPUT按钮,查看运行代码返回的数据:

$pipeline = Sale::aggregate()
->unwind(Expression::arrayFieldPath('items'))
->unwind(Expression::arrayFieldPath('items.tags'))
->group(
_id: Expression::fieldPath('items.tags'),
totalSalesAmount: Accumulator::sum(
Expression::multiply(
Expression::numberFieldPath('items.price'),
Expression::numberFieldPath('items.quantity'),
),
),
);
[
{ "_id": "school", "totalSalesAmount": { "$numberDecimal": "104.85" } },
{ "_id": "electronics", "totalSalesAmount": { "$numberDecimal": "800.00" } },
{ "_id": "writing", "totalSalesAmount": { "$numberDecimal": "60.00" } },
{ "_id": "office", "totalSalesAmount": { "$numberDecimal": "1019.60" } },
{ "_id": "stationary", "totalSalesAmount": { "$numberDecimal": "264.45" } }
]

此示例使用服务器手册中 $lookup 阶段引用的“使用 $lookup 执行单个等值连接”部分中给出的示例数据。

以下代码示例使用 orders集合中的 item字段和 inventory集合中的 sku字段,将 orders集合中的文档与 inventory集合中的文档连接起来。

为此,该示例使用的聚合管道包含$lookup阶段,该阶段指定要从中检索数据的集合以及本地和外部字段名称。

单击VIEW OUTPUT按钮,查看运行代码返回的数据:

$pipeline = Order::aggregate()
->lookup(
from: 'inventory',
localField: 'item',
foreignField: 'sku',
as: 'inventory_docs',
);
[
{ "_id": 1, "item": "almonds", "price": 12, "quantity": 2, "inventory_docs": [
{ "_id": 1, "sku": "almonds", "description": "product 1", "instock": 120 }
] },
{ "_id": 2, "item": "pecans", "price": 20, "quantity": 1, "inventory_docs": [
{ "_id": 4, "sku": "pecans", "description": "product 4", "instock": 70 }
] },
{ "_id": 3, "inventory_docs": [
{ "_id": 5, "sku": null, "description": "Incomplete" },
{ "_id": 6 }
] }
]

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

此示例演示如何创建和使用自定义操作符工厂,该工厂返回从指定日期字段中提取年份的表达式。

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

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

示例聚合管道包括以下阶段:

  • addFields(),它会调用自定义操作符工厂函数以从birthday字段中提取年份并将其分配给birth_year字段

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

单击VIEW OUTPUT按钮,查看运行代码返回的数据:

$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
}
]