Это действительно зависит от того, что вы ищете, но для общего случая знания двух возможностей, вероятно, лучше использовать $ifNull
, чтобы вернуть значение для поля, еслиприсутствует, в противном случае возвращает значение другого поля.
Добавление немного больше данных для демонстрации, так как вы, вероятно, не хотите терять что-либо еще в элементах массива:
{
_id: 1,
fruits: [
{
id: 123,
data: 1
},
{
id: 456,
data: 2
}
]
},
{
_id: 2,
fruits: [
{
fruit_id: 'xxx',
data: 1
},
{
fruit_id: 'yyy',
data: 2
}
]
},
{
_id: 3,
fruits: [
{
fruit_id: 'xxx',
data: 1,
},
{
fruit_id: 'yyy',
data: 2
},
{
id: 123,
data: 3
},
{
id: 456,
data: 4
}
]
}
Тогда вы можетелибо выполните процесс, используя $unwind
в качестве шага первый , что делает именование путей более простым, особенно с $addFields
вместо $project
:
Model.aggregate([
{ "$unwind": "$fruits" },
{ "$addFields": {
"fruits": {
"id": "$$REMOVE",
"fruit_id": "$$REMOVE",
"general_id": { "$ifNull": [ "$fruits.id", "$fruits.fruit_id" ] }
}
}}
])
Использует $$REMOVE
из MongoDB 3.6 и выше (это должна быть минимальная версия, которую вы используете), чтобы "удалить" поля, которые выне хочуВам не нужно этого делать, и вы можете просто объявить все, что вы действительно хотите, с помощью $project
, если у вас нет поддержки.
Тогда, конечно, есть альтернатива с $ifNull
выражение.
Это дает результаты для этих данных, такие как:
{ "_id" : 1, "fruits" : { "data" : 1, "general_id" : 123 } }
{ "_id" : 1, "fruits" : { "data" : 2, "general_id" : 456 } }
{ "_id" : 2, "fruits" : { "data" : 1, "general_id" : "xxx" } }
{ "_id" : 2, "fruits" : { "data" : 2, "general_id" : "yyy" } }
{ "_id" : 3, "fruits" : { "data" : 1, "general_id" : "xxx" } }
{ "_id" : 3, "fruits" : { "data" : 2, "general_id" : "yyy" } }
{ "_id" : 3, "fruits" : { "data" : 3, "general_id" : 123 } }
{ "_id" : 3, "fruits" : { "data" : 4, "general_id" : 456 } }
Если вы хотите $group
для этого значения, тонет необходимости в каком-либо промежуточном «проекте».Просто выполните $ifNull
непосредственно на этом этапе:
Model.aggregate([
{ "$unwind": "$fruits" },
{ "$group": {
"_id": { "$ifNull": [ "$fruits.id", "$fruits.fruit_id" ] },
"count": { "$sum": 1 }
}}
])
И выведите:
{ "_id" : "yyy", "count" : 2 }
{ "_id" : "xxx", "count" : 2 }
{ "_id" : 456, "count" : 2 }
{ "_id" : 123, "count" : 2 }
Или, если вам на самом деле не нужно было $unwind
массив для других целей, вы можете использовать $map
и некоторые другие манипуляции с $objectToArray
и $arrayToObject
:
Model.aggregate([
{ "$addFields": {
"fruits": {
"$map": {
"input": "$fruits",
"in": {
"$mergeObjects": [
{ "$arrayToObject": {
"$filter": {
"input": { "$objectToArray": "$$this" },
"cond": { "$not": { "$in": [ "$$this.k", ["fruit_id","id"] ] } }
}
}},
{
"general_id": { "$ifNull": ["$$this.id","$$this.fruit_id"] }
}
]
}
}
}
}}
])
Который возвращает результаты, такие как:
{
"_id" : 1,
"fruits" : [
{
"data" : 1,
"general_id" : 123
},
{
"data" : 2,
"general_id" : 456
}
]
}
{
"_id" : 2,
"fruits" : [
{
"data" : 1,
"general_id" : "xxx"
},
{
"data" : 2,
"general_id" : "yyy"
}
]
}
{
"_id" : 3,
"fruits" : [
{
"data" : 1,
"general_id" : "xxx"
},
{
"data" : 2,
"general_id" : "yyy"
},
{
"data" : 3,
"general_id" : 123
},
{
"data" : 4,
"general_id" : 456
}
]
}
добавление $unwind
после , которое возвращает точно так же, как и раньше.Но более сложные операции, вероятно, лучше подходят для тех случаев, когда вы хотите сохранить это как массив.
На этот раз мы удалили id
и fruit_id
, преобразовав каждый элемент массива в массив "ключ /значение "пар через $objectToArray
.Затем мы $filter
массив на основе тех "k"
значений, которые являются именами полей.$arrayToObject
снова делает его объектом со всем другим содержимым, кроме этих полей.
$mergeObjects
- $map
то, что $addFields
является корневым «документом», поскольку он берет несколько объектов и «объединяет» их вместе.Таким образом, «отфильтрованный» объект, как описано выше, и новый объект только с ключом general_id
и его значением, переведенным из любого поля, присутствующего.
Списки из более чем двух полей
Какпоследнее замечание: $ifNull
работает лучше, чем $cond
, где у вас есть только два значения, но на самом деле ни то, ни другое не слишком велико, если есть большее возможноесписок.Вы можете вкладывать $cond
выражений или даже использовать $switch
, но на самом деле, вероятно, лучше всего фильтровать контент через $objectToArray
, как показано ранее:
var valid_names = [ "id", "fruit_id", "apple_id", "orange_id" ];
Model.aggregate([
{ "$unwind": "$fruits" },
{ "$group": {
"_id": {
"$arrayElemAt": [
{ "$map": {
"input": {
"$filter": {
"input": { "$objectToArray": "$fruits" },
"cond": { "$in": [ "$$this.k", valid_names ] }
}
},
"in": "$$this.v"
}},
0
]
},
"count": { "$sum": 1 }
}}
])
Это обычно имеет смысл, иначе для работы с таким списком в динамическом режиме вы в конечном итоге создадите этапы конвейера агрегации в коде, например, с использованием $switch
be:
var valid_names = [ "id", "fruit_id", "apple_id", "orange_id" ];
var branches = valid_names.map(name =>
({
"case": { "$gt": [`$fruits.${name}`, null ] },
"then": `$fruits.${name}`
})
)
Model.aggregate([
{ "$unwind": "$fruits" },
{ "$group": {
"_id": { "$switch": { branches, "default": null } },
"count": { "$sum": 1 }
}}
])
, который выглядит чище в вашем коде, но на самом деле отправляет гораздо больший конвейер в BSON:
[
{ "$unwind" : "$fruits" },
{ "$group" : {
"_id" : {
"$switch" : {
"branches" : [
{
"case" : { "$gt" : [ "$fruits.id", null ] },
"then" : "$fruits.id"
},
{
"case" : { "$gt" : [ "$fruits.fruit_id", null ] },
"then" : "$fruits.fruit_id"
},
{
"case" : { "$gt" : [ "$fruits.apple_id", null ] },
"then" : "$fruits.apple_id"
},
{
"case" : { "$gt" : [ "$fruits.orange_id", null ] },
"then" : "$fruits.orange_id"
}
],
"default" : null
}
},
"count" : { "$sum" : 1 }
}}
]