Първо, нека покажем вашите данни по начин, по който хората могат да ги използват и да създадат желания резултат:
{ value: 1,
_id: ObjectId('5cb9ea0c75c61525e0176f96'),
name: 'Test',
category: 'Development',
subcategory: 'Programming Languages',
status: 'Supported',
description: 'Test',
change:
[ { version: 1,
who: 'ATL User',
when: new Date('2019-04-19T15:30:39.912Z'),
what: 'Item Creation' },
{ version: 2,
who: 'ATL Other User',
when: new Date('2019-04-19T15:31:39.912Z'),
what: 'Name Change' } ],
}
Имайте предвид, че "when"
датите всъщност са различни, така че ще има $maxкод>
стойност и те не са просто еднакви. Сега можем да прегледаме случаите
Случай 1 – Извличане на „единично“ $max
стойност
Основният случай тук е да използвате $arrayElemAt
и $indexOfArray
оператори за връщане на съответстващия $max
стойност:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$arrayElemAt": [
"$change",
{ "$indexOfArray": [
"$change.when",
{ "$max": "$change.when" }
]}
]
}
}}
])
Връща:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : {
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
}
По принцип "$max":"$change.when"
връща стойността, която е "максималната" от този масив от стойности. След това намирате съответстващия „индекс“ на този масив от стойности чрез $indexOfArray
което връща първия намерен съответстващ индекс. Тази позиция на "индекс" ( всъщност само от масив от "when"
стойности, транспонирани в същия ред) след това се използва с $arrayElemAt
за да извлечете "целия обект" от "промяната"
масив на посочената индексна позиция.
Случай 2 – Връщане на „multiple“ $max
записи
Почти същото нещо с $max
, освен този път ние $filter
за връщане на множеството "възможно" стойности, съответстващи на това $max
стойност:
db.items.aggregate([
{ "$match": {
"subcategory": "Programming Languages", "name": { "$exists": true }
}},
{ "$addFields": {
"change": {
"$filter": {
"input": "$change",
"cond": {
"$eq": [ "$$this.when", { "$max": "$change.when" } ]
}
}
}
}}
])
Връща:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 2,
"who" : "ATL Other User",
"when" : ISODate("2019-04-19T15:31:39.912Z"),
"what" : "Name Change"
}
]
}
Така че $max
разбира се е същото, но този път единствената стойност, върната от този оператор, се използва в $eq
сравнение в рамките на $filter
. Това инспектира всеки елемент от масива и разглежда текущия "когато"
стойност ( "$$this.when"
). Където „равно“ тогава елементът се връща.
По принцип същият като първия подход, но с изключение на $filter
позволява "множество" елементи за връщане. Затовавсичкото със същите $max
стойност.
Случай 3 – Предварително сортиране на съдържанието на масива.
Сега може да забележите, че в примерните данни, които включих (адаптирани от вашите собствени, но с действителна „максимална“ дата) стойността „максимална“ всъщност е последната стойност в масива. Това може да се случи естествено в резултат на $pushкод>
(по подразбиране) "прилага" до края на съдържанието на съществуващия масив. Така че "по-ново" записите обикновено са в края от масива.
Това разбира се е по подразбиране поведение, но има основателни причини, поради които „можете“ искате да промените това. Накратконай-добрият начин да получите "най-новото" въвеждането в масив е всъщност да върне първия елемент от масива.
Всичко, което всъщност трябва да направите, е да осигурите „най-новото“ всъщност се добавяпърво вместо последен . Има два подхода:
-
Използвайте
$position
за "предварително зареждане" на елементи от масив: Това е прост модификатор за$push
използвайки0
позиция за да добавяте винаги къмотпред :db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [{ "version": 3, "who": "ATL User", "when": new Date(), "what": "Another change" }], "$position": 0 } }} )
Това ще промени документа на:
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96"), "value" : 1, "name" : "Test", "category" : "Development", "subcategory" : "Programming Languages", "status" : "Supported", "description" : "Test", "change" : [ { "version" : 3, "who" : "ATL User", "when" : ISODate("2019-04-20T02:40:30.024Z"), "what" : "Another change" }, { "version" : 1, "who" : "ATL User", "when" : ISODate("2019-04-19T15:30:39.912Z"), "what" : "Item Creation" }, { "version" : 2, "who" : "ATL Other User", "when" : ISODate("2019-04-19T15:31:39.912Z"), "what" : "Name Change" } ] }
Имайте предвид, че това ще изисква от вас действително да отидете и да "обърнете" всичките си елементи от масива предварително, така че "най-новият" вече да е отпред, така че редът да се запази. За щастие това донякъде е покрито във втория подход...
-
Използвайте
$sort
да променяте документите по ред на всеки$push
: И това е другият модификатор, който всъщност "пресортира" атомарно при всяко добавяне на нов елемент. Нормалното използване е основно същото с всички нови елементи към$each
както по-горе, или дори просто „празен“ масив, за да приложите$sort
само към съществуващи данни:db.items.updateOne( { "_id" : ObjectId("5cb9ea0c75c61525e0176f96") }, { "$push": { "change": { "$each": [], "$sort": { "when": -1 } } }} )
Резултати в:
{ "_id" : ObjectId("5cb9ea0c75c61525e0176f96"), "value" : 1, "name" : "Test", "category" : "Development", "subcategory" : "Programming Languages", "status" : "Supported", "description" : "Test", "change" : [ { "version" : 3, "who" : "ATL User", "when" : ISODate("2019-04-20T02:40:30.024Z"), "what" : "Another change" }, { "version" : 2, "who" : "ATL Other User", "when" : ISODate("2019-04-19T15:31:39.912Z"), "what" : "Name Change" }, { "version" : 1, "who" : "ATL User", "when" : ISODate("2019-04-19T15:30:39.912Z"), "what" : "Item Creation" } ] }
Може да отнеме минута, за да разберете защо бихте
$pushкод>
за да$sort
масив като този, но общото намерение е, когато могат да бъдат направени модификации на масив, които „променят“ свойство катоДата
стойност, по която се сортира и бихте използвали такъв израз, за да отразите тези промени. Или просто добавете нови елементи с$sort
и го оставете да се получи.
Така че защо „магазин“ масивът подреден по този начин? Както споменахме по-рано, вие искате първото елемент като "най-скорошен" , а след това заявката за връщане, която просто става:
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true }
},
{ "change": { "$slice": 1 } }
)
Връща:
{
"_id" : ObjectId("5cb9ea0c75c61525e0176f96"),
"value" : 1,
"name" : "Test",
"category" : "Development",
"subcategory" : "Programming Languages",
"status" : "Supported",
"description" : "Test",
"change" : [
{
"version" : 3,
"who" : "ATL User",
"when" : ISODate("2019-04-20T02:40:30.024Z"),
"what" : "Another change"
}
]
}
Така че $slice
след това може да се използва само за извличане на елементи от масив чрез известни индекси. Технически можете просто да използвате -1
там, за да върнете последния елемент от масива така или иначе, но пренареждането, където най-новото е първо, позволява други неща като потвърждение, че последната модификация е направена от определен потребител и/или други условия като ограничение за диапазон от дати. т.е.:
db.items.find(
{
"subcategory": "Programming Languages",
"name": { "$exists": true },
"change.0.who": "ATL User",
"change.0.when": { "$gt": new Date("2018-04-01") }
},
{ "change": { "$slice": 1 } }
)
Тук отбелязваме, че нещо като "change.-1.when"
е незаконно твърдение, което всъщност е причината да пренаредим масива, така че да можете да използвате законния 0
за първо вместо -1
за последно .
Заключение
Така че има няколко различни неща, които можете да направите, или като използвате подхода на агрегиране за филтриране на съдържанието на масива, или чрез стандартни формуляри за заявки, след като направите известна промяна в начина, по който данните действително се съхраняват. Кой да използвате зависи от вашите собствени обстоятелства, но трябва да се отбележи, че всеки от стандартните формуляри за заявки ще работи значително по-бързо от всяко манипулиране чрез рамката за агрегиране или изчислени оператори.