MongoDB
 sql >> Base de données >  >> NoSQL >> MongoDB

$expr arrayElementAt ne fonctionne pas dans l'agrégation pour le document intégré

Solution rapide

Votre "pipeline" ne fonctionne pas ici principalement parce que votre $project initial manque le champ que vous souhaitez utiliser à une étape ultérieure. La "solution rapide" consiste donc essentiellement à inclure ce champ dans le document "projeté", puisque c'est ainsi que fonctionnent les étapes du pipeline d'agrégation :

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

Ou même depuis que vous avez utilisé $$ROOT pour Etudiant quoi qu'il en soit, qualifiez simplement le champ sous ce chemin :

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

cependant Je voudrais fortement* implorez que vous ne fassiez PAS fais ça.

Tout le concept de "concaténer des chaînes" afin de faire un $match sur le contenu est une très mauvaise idée car cela signifie que toute la collection est réécrite dans le pipeline avant que tout "filtrage" ne soit réellement effectué.

De même, chercher à faire correspondre le "dernier" élément du tableau est également un problème. Une bien meilleure approche consiste à ajouter à la place de "nouveaux éléments" au "début" du tableau, au lieu de la "fin". C'est en fait ce que la $position ou peut-être même le $sort modificateurs de $push faire pour vous, en modifiant respectivement l'endroit où les éléments sont ajoutés ou l'ordre de tri des éléments.

Changer le tableau en "plus récent en premier"

Cela demande un peu de travail en changeant la façon dont vous stockez les choses, mais les avantages sont une vitesse grandement améliorée de telles requêtes comme vous le souhaitez sans avoir besoin d'un $expr évalué arguments.

Les concepts de base sont de "pré-pendre" de nouveaux éléments de tableau avec une syntaxe comme :

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

$allocations doit être un tableau tel que requis par $each et $position est utilisé pour 0 afin d'ajouter le nouvel élément de tableau "first".

Alternativement, si vous avez réellement quelque chose comme created_date en tant que propriété dans chacun des objets du tableau, vous "pourriez" utiliser quelque chose comme $sort comme modificateur à la place.

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

Cela dépend vraiment si votre "requête" et d'autres exigences d'accès reposent sur "dernier ajout" ou "dernière date", et aussi généralement si vous avez l'intention de modifier éventuellement un tel created_date ou une autre propriété "trier" d'une manière qui affecterait l'ordre des éléments du tableau lorsqu'ils sont "triés".

La raison pour laquelle vous faites cela est alors que la correspondance de l'élément "dernier" (qui est maintenant le "premier" ) dans le tableau devient simplement :

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

MongoDB permet de spécifier le "premier" index de tableau avec "Dot Notation" , en utilisant le 0 indice. Ce que vous ne pouvez pas faire est de spécifier un index "négatif", c'est-à-dire :

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

C'est la raison pour laquelle vous effectuez les opérations indiquées ci-dessus lors de la "mise à jour" afin de "réorganiser" votre tableau sous une forme exploitable.

La concaténation est mauvaise

L'autre problème principal est la concaténation des chaînes. Comme déjà mentionné, cela crée une surcharge inutile juste pour faire la correspondance que vous voulez. C'est aussi "inutile" puisque vous pouvez éviter cela en utilisant $or avec les conditions de chacun des champs telles qu'elles existent déjà dans le document réel :

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

Et bien sûr, quelles que soient les conditions de requête "complètes", mais vous devriez comprendre l'idée de base.

De plus, si vous ne recherchez pas réellement des "mots partiels", alors une "text search" défini sur les champs avec les "noms". Après avoir créé l'index qui serait :

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

Dans l'ensemble, je recommanderais vraiment d'examiner de près toutes les autres options plutôt que d'apporter une petite modification à votre code existant. Avec une petite restructuration minutieuse de la façon dont vous stockez les choses et, en fait, "indexez" les choses, vous obtenez d'énormes avantages en termes de performances que votre vaste $concat l'approche "force brute" ne peut tout simplement pas être efficace.