Se è necessario calcolare qualcosa del genere in fase di esecuzione, con il contenuto "filtrato" dall'array che determina l'ordinamento, è meglio fare qualcosa con .aggregate()
per rimodellare e determinare un valore di ordinamento come questo:
db.collection.aggregate([
// Pre-filter the array elements
{ "$project": {
"tags": 1,
"score": {
"$setDifference": [
{ "$map": {
"input": "$tags",
"as": "tag",
"in": {
"$cond": [
{ "$eq": [ "$$el.id", "t1" ] },
"$$el.score",
false
]
}
}},
[false]
]
}
}},
// Unwind to denormalize
{ "$unwind": "$score" },
// Group back the "max" score
{ "$group": {
"_id": "$_id",
"tags": { "$first": "$tags" },
"score": { "$max": "$score" }
}},
// Sort descending by score
{ "$sort": { "score": -1 } }
])
Dove la prima parte della pipeline viene utilizzata per "prefiltrare" il contenuto dell'array (oltre a mantenere il campo originale) solo per quei valori di "score" in cui l'id è uguale a "t1". Questo viene fatto elaborando $map
che applica una condizione a ciascun elemento tramite $cond
per determinare se restituire il "punteggio" per quell'elemento o false
.
Il $setDifference
l'operazione esegue un confronto con un singolo elemento array [false]
che rimuove efficacemente qualsiasi false
valori restituiti da $map
. Come "set", rimuove anche le voci duplicate, ma ai fini dell'ordinamento qui è una buona cosa.
Con l'array ridotto e rimodellato ai valori, elabori $unwind
pronti per la fase successiva per affrontare i valori come elementi individuali. Il $group
la fase si applica essenzialmente $max
sul "punteggio" per restituire il valore più alto contenuto nei risultati filtrati.
Quindi si tratta solo di applicare il $sort
sul valore determinato per ordinare i documenti. Naturalmente se volevi il contrario, usa $min
e ordina invece in ordine crescente.
Ovviamente aggiungi un $match
dalla fase all'inizio se tutto ciò che vuoi veramente sono documenti che contengono effettivamente valori "t1" per id
all'interno dei tag. Ma quella parte è meno rilevante per l'ordinamento dei risultati filtrati che desideri ottenere.
L'alternativa al calcolo è fare tutto mentre scrivi le voci nell'array nei documenti. Un po' disordinato, ma è più o meno così:
db.collection.update(
{ "_id": docId },
{
"$push": { "tags": { "id": "t1", "score": 60 } },
"$max": { "maxt1score": 60 },
"$min": { "mint1score": 60 }
}
)
Qui il $max
l'operatore di aggiornamento imposta il valore per il campo specificato solo se il nuovo valore è maggiore del valore esistente o se non esiste ancora alcuna proprietà. Il caso inverso è vero per $min
, dove solo se minore di sarà sostituito con il nuovo valore.
Ciò avrebbe ovviamente l'effetto di aggiungere varie proprietà aggiuntive ai documenti, ma il risultato finale è che l'ordinamento è notevolmente semplificato:
db.collection.find().sort({ "maxt1score": -1 })
E funzionerà molto più velocemente rispetto al calcolo con una pipeline di aggregazione.
Quindi considera i principi di progettazione. Dati strutturati in matrici in cui si desidera ottenere risultati filtrati e accoppiati per l'ordinamento significa calcolare in fase di esecuzione per determinare su quale valore eseguire l'ordinamento. Aggiunta di ulteriori proprietà al documento su .update()
significa che puoi semplicemente fare riferimento a tali proprietà per ordinare direttamente i risultati.