$expr arrayElementAt not working in aggregation for embedded document

前端 未结 1 1047
迷失自我
迷失自我 2021-01-24 23:00

I am doing mongo db aggregation like

$cursor = $this->collection->aggregate(
            array(
                array(
                    \'$project\' =&         


        
1条回答
  •  野趣味
    野趣味 (楼主)
    2021-01-24 23:43

    Quick fix

    Your "pipeline" does not work here primarily because your initial $project lacks the field you want to use an a later stage. The "quick fix" is therefore basically to include that field in the "projected" document, since that's how aggregation pipeline stages work:

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

    Or even since you used $$ROOT for Student anyway, simply qualify the field under that path:

    '$expr' => array(
      '$eq'=> array(
        array('$arrayElemAt' => array('$Student.allotment_details.room_id', -1)),
        $this->RoomId
      )
    ),
    

    however I would strongly* implore that you do NOT do that.

    The whole concept of "concatenating strings" in order to do a later $match on the content is a really bad idea since it means the whole collection gets rewritten in the pipeline before any "filtering" actually gets done.

    Likewise looking to match on the "last" array element is also an issue. A far better approach is to instead actually add "new items" to the "beginning" of the array, instead of the "end". This is actually what the $position or possibly even the $sort modifiers to $push do for you, by changing where items get added or the sorted order of items respectively.

    Changing the Array to "newest first"

    This takes a little work by changing the way you store things, but the benefits are greatly improved speed of such queries like you want without needing an evaluated $expr argument.

    The core concepts are to "pre-pend" new array items with syntax like:

    $this->collection->updateOne(
      $query,
      [ '$push' => [ 'allotment_details' => [ '$each' => $allotments, '$position' => 0 ] ] ]
    )
    

    Where $alloments must be an array as required by $each and $position is used to 0 in order to add the new array item "first".

    Alternately if you actually have something like created_date as a property within each of the objects in the array, then you "could" use something like $sort as a modifier instead.

    $this->collection->updateOne(
      $query,
      [ '$push' => [
          'allotment_details' => [ '$each' => $allotments, '$sort' => [ 'created_date' => -1 ] ]
      ]]
    )
    

    It really depends on whether your "query" and other access requirements rely on "last added" or "latest date", and then also typically if you intend to possibly alter such a created_date or other "sort" property in a way which would effect the order of the array elements when "sorted".

    The reason you do this is then matching the "latest" ( which is now the "first" ) item in the array simply becomes:

    $this->collection->find([
     'allotment_details.0.room_id': $this->RoomId
    ])
    

    MongoDB allows the "first" array index to be specified with "Dot Notation", using the 0 index. What you cannot do is specify a "negative" index i.e:

    $this->collection->find([
     'allotment_details.-1.room_id': $this->RoomId  # not allowed :(
    ])
    

    That's the reason why you do the things shown above on "update" in order to "re-order" your array to the workable form.

    Concatenation is Bad

    The other main issue is the concatenation of strings. As already mentioned this creates unnecessary overhead just in order to do the matching you want. It's also "unnecessary" since you can complete avoid this using $or with the conditions on each of the fields as they exist already within the actual document:

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

    And of course whatever the "full" query conditions actually need to be, but you should be getting the basic idea.

    Also if you are not actually looking for "partial words", then a "text search" defined over the fields with the "names". After creating the index that would be:

     $this->collection->find([
       '$text' => [ '$search' => $arg ],
       'schoolId' => new MongoDB\BSON\ObjectID($this->SchoolId),
       'allotment_details.0.room_id': $this->RoomId
     ])
    

    Overall I would really recommend looking closely at all the other options rather than making one small change to your existing code. With a little careful re-structuring of how you store things and indeed "index" things, you get huge performance benefits that your extensive $concat "brute force" approach simply cannot deliver.

    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.

    0 讨论(0)
提交回复
热议问题