MongoDB
 sql >> Datenbank >  >> NoSQL >> MongoDB

$expr arrayElementAt funktioniert nicht in Aggregation für eingebettetes Dokument

Schnelle Lösung

Ihre "Pipeline" funktioniert hier hauptsächlich nicht weil Ihr anfänglicher $project Ihnen fehlt das Feld, das Sie zu einem späteren Zeitpunkt verwenden möchten. Die "schnelle Lösung" ist es daher grundsätzlich, dieses Feld in das "projizierte" Dokument aufzunehmen, da die Phasen der Aggregationspipeline so funktionieren:

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

Oder sogar seit Sie $$ROOT verwendet haben für Schüler qualifizieren Sie trotzdem einfach das Feld unter diesem Pfad:

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

jedoch Ich würde dringend* beschwören Sie, dass Sie es NICHT tun mach das.

Das gesamte Konzept des „Verkettens von Zeichenfolgen“, um später einen $übereinstimmung auf den Inhalt ist eine wirklich schlechte Idee, da dies bedeutet, dass die gesamte Sammlung in der Pipeline neu geschrieben wird, bevor tatsächlich eine "Filterung" durchgeführt wird.

Ebenso ist es auch ein Problem, nach Übereinstimmungen mit dem "letzten" Array-Element zu suchen. Ein weitaus besserer Ansatz besteht darin, stattdessen "neue Elemente" am "Anfang" des Arrays anstelle am "Ende" hinzuzufügen. Das ist eigentlich das, was der $position oder möglicherweise sogar der $sort Modifikatoren zu $push tun, indem Sie ändern, wo Elemente hinzugefügt werden, bzw. die sortierte Reihenfolge der Elemente.

Änderung des Arrays auf "neueste zuerst"

Dies erfordert ein wenig Arbeit, da Sie die Art und Weise ändern, wie Sie Dinge speichern, aber die Vorteile sind eine stark verbesserte Geschwindigkeit solcher Abfragen, wie Sie es möchten, ohne dass ein ausgewerteter $expr erforderlich ist Argument.

Die Kernkonzepte bestehen darin, neue Array-Elemente mit Syntax wie:

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

Wobei $alloments müssen ein Array sein, wie von $each gefordert und $position wird zu 0 verwendet um das neue Array-Element "first" hinzuzufügen.

Alternativ, wenn Sie tatsächlich so etwas wie created_date haben als Eigenschaft in jedem der Objekte im Array "könnten" Sie so etwas wie $sort stattdessen als Modifikator.

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

Es hängt wirklich davon ab, ob Ihre "Abfrage" und andere Zugriffsanforderungen auf "zuletzt hinzugefügt" oder "neuestes Datum" basieren, und dann auch normalerweise, wenn Sie beabsichtigen, ein solches created_date möglicherweise zu ändern oder eine andere "sortieren"-Eigenschaft in einer Weise, die die Reihenfolge der Array-Elemente beeinflussen würde, wenn sie "sortiert" werden.

Der Grund, warum Sie dies tun, ist, dass der Abgleich des "neuesten" (das jetzt das "erste") Element im Array einfach zu:

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

MongoDB ermöglicht die Angabe des "ersten" Array-Index mit "Dot Notation" , indem Sie 0 verwenden Index. Was Sie nicht können Geben Sie einen "negativen" Index an, z. B.:

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

Das ist der Grund, warum Sie die oben gezeigten Dinge auf "Aktualisieren" tun, um Ihr Array in die funktionsfähige Form "umzuordnen".

Verkettung ist schlecht

Das andere Hauptproblem ist die Verkettung von Zeichenfolgen. Wie bereits erwähnt, erzeugt dies unnötigen Overhead, nur um das gewünschte Matching durchzuführen. Es ist auch "unnötig", da Sie dies mit $oder vollständig vermeiden können mit den Bedingungen für jedes der Felder, wie sie bereits im eigentlichen Dokument vorhanden sind:

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

Und natürlich, was auch immer die "vollständigen" Abfragebedingungen tatsächlich sein müssen, aber Sie sollten die Grundidee verstehen.

Auch wenn Sie nicht wirklich nach "Teilwörtern" suchen, dann eine "Textsuche" über die Felder mit den "Namen" definiert. Nach dem Erstellen des Index wäre das:

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

Insgesamt würde ich wirklich empfehlen, sich alle anderen Optionen genau anzusehen, anstatt eine kleine Änderung an Ihrem vorhandenen Code vorzunehmen. Mit ein wenig sorgfältiger Neustrukturierung der Art und Weise, wie Sie Dinge speichern und tatsächlich "indizieren", erhalten Sie enorme Leistungsvorteile gegenüber Ihrem umfangreichen $concat "Brute-Force"-Ansatz kann einfach nicht liefern.