$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.