$expr arrayElementAt 不适用于嵌入式文档的聚合
$expr arrayElementAt not working in aggregation for embedded document
我正在做 mongo 像
这样的数据库聚合
$cursor = $this->collection->aggregate(
array(
array(
'$project' => array(
'FullName' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'FirstMiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'FirstLast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'FirstName' => array('$concat' => array('$first_name')),
'MiddleName' => array('$concat' => array('$middle_name')),
'LastName' => array('$concat' => array('$last_name')),
'Student' => '$$ROOT'
)
),
array(
'$match' =>
array(
'$or' => array(
array("FullName" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstLast" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstMiddle" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstName" => new MongoDB\BSON\Regex($arg, 'i')),
array("MiddleName" => new MongoDB\BSON\Regex($arg, 'i')),
array("LastName" => new MongoDB\BSON\Regex($arg, 'i')),
array("Student.registration_temp_perm_no" => $arg),
'$expr' => array(
'$eq'=> array(
array('$arrayElemAt' => array('$allotment_details.room_id', -1)), $this->RoomId)),
),
// "Student.assigned_keys" => ['$exists' => false],
"Student.schoolId" => new MongoDB\BSON\ObjectID($this->SchoolId)
)
)
)
);
我有一个集合,其中包含像
这样的数据
"first_name": "John",
"middle_name": "",
"last_name": "Mayor",
"allotment_details": [
{
"allotment_id": "ff666d55-2fcc-79b2-e4da-e165939555bb",
"room_id": "5be2d9aad2ccda0fdc006a65",
"bay_id": ObjectId("5be2d9aad2ccda0fdc006a61"),
...
}
以上代码用于三种名称类型的精细串联并搜索传入 $arg
的所有数据。请注意,我添加了 array('$arrayElemAt' => array('$allotment_details.room_id', -1)), $this->RoomId))
以便根据姓名的串联获取学生,并且应该根据 $this->RoomId
.
获取这些学生
上面的代码不是获取分配到房间的学生,而是根据姓名的串联获取所有学生。
请帮助!!!
快速修复
您的 "pipeline" 在这里不起作用 主要是 因为您的初始 $project
缺少您想在以后阶段使用的字段。 "quick fix" 因此基本上是将该字段包含在 "projected" 文档中,因为这就是聚合管道阶段的工作方式:
array(
array(
'$project' => array(
'FullName' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'FirstMiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'FirstLast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'FirstName' => array('$concat' => array('$first_name')),
'MiddleName' => array('$concat' => array('$middle_name')),
'LastName' => array('$concat' => array('$last_name')),
'Student' => '$$ROOT',
'allotment_details' => 1 # that's the change
)
),
或者即使您使用 $$ROOT
作为 Student
,只需限定该路径下的字段:
'$expr' => array(
'$eq'=> array(
array('$arrayElemAt' => array('$Student.allotment_details.room_id', -1)),
$this->RoomId
)
),
但是我会强烈*恳求你不要 这样做。
"concatenating strings" 的整个概念以便稍后对内容进行 $match
是一个非常糟糕的主意,因为这意味着整个集合在任何 [=107= 之前在管道中被重写] 实际上完成了。
同样寻找匹配 "last" 数组元素也是一个问题。一个更好的方法是实际将 "new items" 添加到数组的 "beginning",而不是 "end"。这实际上是 $position
or possibly even the $sort
modifiers to $push
为您所做的,分别更改添加项目的位置或项目的排序顺序。
将数组更改为 "newest first"
这需要一些工作来改变你存储东西的方式,但好处是大大提高了你想要的查询速度,而不需要评估的 $expr
参数。
核心概念是 "pre-pend" 语法如下的新数组项:
$this->collection->updateOne(
$query,
[ '$push' => [ 'allotment_details' => [ '$each' => $allotments, '$position' => 0 ] ] ]
)
其中$alloments
必须是$each
and $position
要求的数组用于0
以添加新的数组项"first".
或者,如果您在数组中的每个对象中实际上都有 created_date
作为 属性,那么您 "could" 使用 $sort
作为改为修饰符。
$this->collection->updateOne(
$query,
[ '$push' => [
'allotment_details' => [ '$each' => $allotments, '$sort' => [ 'created_date' => -1 ] ]
]]
)
这实际上取决于您的 "query" 和其他访问要求是否依赖于 "last added" 或 "latest date",然后通常还取决于您是否打算可能更改这样的 created_date
或其他 "sort" 属性 在 "sorted".
时会影响数组元素顺序的方式
你这样做的原因是匹配数组中的 "latest" (现在是 "first" )项目简单地变成:
$this->collection->find([
'allotment_details.0.room_id': $this->RoomId
])
MongoDB 允许 "first" 数组索引用 "Dot Notation" 指定,使用 0
索引。你不能做的是指定一个"negative"索引,即:
$this->collection->find([
'allotment_details.-1.room_id': $this->RoomId # not allowed :(
])
这就是为什么您在 "update" 上执行上面显示的操作以便 "re-order" 您的数组成为可用形式的原因。
连接错误
另一个主要问题是字符串的连接。正如已经提到的那样,这会产生不必要的开销,只是为了进行您想要的匹配。它也是 "unnecessary",因为您可以使用 $or
和每个字段的条件来完全避免这种情况,因为它们已经存在于实际文档中:
$this->collection->find([
'$or' => [
[ 'first_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'last_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'middle_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'registration_temp_perm_no' => $arg ]
],
'schoolId' => new MongoDB\BSON\ObjectID($this->SchoolId),
'allotment_details.0.room_id': $this->RoomId
])
当然,无论 "full" 查询条件实际需要什么,但您应该了解基本概念。
此外,如果您实际上不是在寻找 "partial words",那么在 "names" 字段上定义一个 "text search"。创建索引后:
$this->collection->find([
'$text' => [ '$search' => $arg ],
'schoolId' => new MongoDB\BSON\ObjectID($this->SchoolId),
'allotment_details.0.room_id': $this->RoomId
])
总的来说,我真的建议仔细研究所有其他选项,而不是对现有代码进行微小的更改。通过对存储方式和 "index" 事物的存储方式进行一些仔细的重组,您可以获得巨大的性能优势,这是您广泛的 $concat
"brute force" 方法根本无法提供的。
N.B Modern PHP Releases generally support []
as a much more brief representation of array()
. It's a lot cleaner and far easier to read. So please use it.
我正在做 mongo 像
这样的数据库聚合$cursor = $this->collection->aggregate(
array(
array(
'$project' => array(
'FullName' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'FirstMiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'FirstLast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'FirstName' => array('$concat' => array('$first_name')),
'MiddleName' => array('$concat' => array('$middle_name')),
'LastName' => array('$concat' => array('$last_name')),
'Student' => '$$ROOT'
)
),
array(
'$match' =>
array(
'$or' => array(
array("FullName" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstLast" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstMiddle" => new MongoDB\BSON\Regex($arg, 'i')),
array("FirstName" => new MongoDB\BSON\Regex($arg, 'i')),
array("MiddleName" => new MongoDB\BSON\Regex($arg, 'i')),
array("LastName" => new MongoDB\BSON\Regex($arg, 'i')),
array("Student.registration_temp_perm_no" => $arg),
'$expr' => array(
'$eq'=> array(
array('$arrayElemAt' => array('$allotment_details.room_id', -1)), $this->RoomId)),
),
// "Student.assigned_keys" => ['$exists' => false],
"Student.schoolId" => new MongoDB\BSON\ObjectID($this->SchoolId)
)
)
)
);
我有一个集合,其中包含像
这样的数据"first_name": "John",
"middle_name": "",
"last_name": "Mayor",
"allotment_details": [
{
"allotment_id": "ff666d55-2fcc-79b2-e4da-e165939555bb",
"room_id": "5be2d9aad2ccda0fdc006a65",
"bay_id": ObjectId("5be2d9aad2ccda0fdc006a61"),
...
}
以上代码用于三种名称类型的精细串联并搜索传入 $arg
的所有数据。请注意,我添加了 array('$arrayElemAt' => array('$allotment_details.room_id', -1)), $this->RoomId))
以便根据姓名的串联获取学生,并且应该根据 $this->RoomId
.
上面的代码不是获取分配到房间的学生,而是根据姓名的串联获取所有学生。 请帮助!!!
快速修复
您的 "pipeline" 在这里不起作用 主要是 因为您的初始 $project
缺少您想在以后阶段使用的字段。 "quick fix" 因此基本上是将该字段包含在 "projected" 文档中,因为这就是聚合管道阶段的工作方式:
array(
array(
'$project' => array(
'FullName' => array('$concat' => array('$first_name', ' ', '$middle_name', ' ', '$last_name')),
'FirstMiddle' => array('$concat' => array('$first_name', ' ', '$middle_name')),
'FirstLast' => array('$concat' => array('$first_name', ' ', '$last_name')),
'FirstName' => array('$concat' => array('$first_name')),
'MiddleName' => array('$concat' => array('$middle_name')),
'LastName' => array('$concat' => array('$last_name')),
'Student' => '$$ROOT',
'allotment_details' => 1 # that's the change
)
),
或者即使您使用 $$ROOT
作为 Student
,只需限定该路径下的字段:
'$expr' => array(
'$eq'=> array(
array('$arrayElemAt' => array('$Student.allotment_details.room_id', -1)),
$this->RoomId
)
),
但是我会强烈*恳求你不要 这样做。
"concatenating strings" 的整个概念以便稍后对内容进行 $match
是一个非常糟糕的主意,因为这意味着整个集合在任何 [=107= 之前在管道中被重写] 实际上完成了。
同样寻找匹配 "last" 数组元素也是一个问题。一个更好的方法是实际将 "new items" 添加到数组的 "beginning",而不是 "end"。这实际上是 $position
or possibly even the $sort
modifiers to $push
为您所做的,分别更改添加项目的位置或项目的排序顺序。
将数组更改为 "newest first"
这需要一些工作来改变你存储东西的方式,但好处是大大提高了你想要的查询速度,而不需要评估的 $expr
参数。
核心概念是 "pre-pend" 语法如下的新数组项:
$this->collection->updateOne(
$query,
[ '$push' => [ 'allotment_details' => [ '$each' => $allotments, '$position' => 0 ] ] ]
)
其中$alloments
必须是$each
and $position
要求的数组用于0
以添加新的数组项"first".
或者,如果您在数组中的每个对象中实际上都有 created_date
作为 属性,那么您 "could" 使用 $sort
作为改为修饰符。
$this->collection->updateOne(
$query,
[ '$push' => [
'allotment_details' => [ '$each' => $allotments, '$sort' => [ 'created_date' => -1 ] ]
]]
)
这实际上取决于您的 "query" 和其他访问要求是否依赖于 "last added" 或 "latest date",然后通常还取决于您是否打算可能更改这样的 created_date
或其他 "sort" 属性 在 "sorted".
你这样做的原因是匹配数组中的 "latest" (现在是 "first" )项目简单地变成:
$this->collection->find([
'allotment_details.0.room_id': $this->RoomId
])
MongoDB 允许 "first" 数组索引用 "Dot Notation" 指定,使用 0
索引。你不能做的是指定一个"negative"索引,即:
$this->collection->find([
'allotment_details.-1.room_id': $this->RoomId # not allowed :(
])
这就是为什么您在 "update" 上执行上面显示的操作以便 "re-order" 您的数组成为可用形式的原因。
连接错误
另一个主要问题是字符串的连接。正如已经提到的那样,这会产生不必要的开销,只是为了进行您想要的匹配。它也是 "unnecessary",因为您可以使用 $or
和每个字段的条件来完全避免这种情况,因为它们已经存在于实际文档中:
$this->collection->find([
'$or' => [
[ 'first_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'last_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'middle_name' => new MongoDB\BSON\Regex($arg, 'i') ],
[ 'registration_temp_perm_no' => $arg ]
],
'schoolId' => new MongoDB\BSON\ObjectID($this->SchoolId),
'allotment_details.0.room_id': $this->RoomId
])
当然,无论 "full" 查询条件实际需要什么,但您应该了解基本概念。
此外,如果您实际上不是在寻找 "partial words",那么在 "names" 字段上定义一个 "text search"。创建索引后:
$this->collection->find([
'$text' => [ '$search' => $arg ],
'schoolId' => new MongoDB\BSON\ObjectID($this->SchoolId),
'allotment_details.0.room_id': $this->RoomId
])
总的来说,我真的建议仔细研究所有其他选项,而不是对现有代码进行微小的更改。通过对存储方式和 "index" 事物的存储方式进行一些仔细的重组,您可以获得巨大的性能优势,这是您广泛的 $concat
"brute force" 方法根本无法提供的。
N.B Modern PHP Releases generally support
[]
as a much more brief representation ofarray()
. It's a lot cleaner and far easier to read. So please use it.