不幸的是,大约六个月前,数据结构发生了变化。因此,我有一个以前看起来像...的文件。
{
fruits: [
{
id: 123
},
{
id: 456
}
]
}
(需要注意的是id
不是ObjectId
BSON类型,它只是客户端生成的随机字符系列。)
...但是现在将id
键更改为
{
fruits: [
{
fruit_id: 'xxx'
},
{
fruit_id: 'yyy'
}
]
}
因此,我正在尝试做一个$project
,将id
和fruit_id
都更改为general_id
之类的通用名称,以便我可以继续进行其他聚合像$group
一样,只引用一个字段
我尝试了以下方法:
[
$unwind: {
path: '$fruits'
},
$project: {
general_id: {
$cond: {
if: {
'fruits.fruit_id': {
$type: ['string']
}
},
then: '$fruits.fruit_id',
else: '$fruits.id'
}
}
}
]
答案 0 :(得分:1)
这实际上取决于您在此之后的工作,但是对于了解两个可能性的一般情况,最好使用$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" ] }
}
}}
])
它使用MongoDB 3.6及更高版本中的$$REMOVE
(应该是您使用的最低版本)来“删除”不需要的字段。您不需要这样做,只要没有支持,就可以使用$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
用于其他目的的数组,则可以对$objectToArray
和$arrayToObject
使用$map
和其他一些操作:
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
之后,其返回结果与之前相同。但是,更复杂的操作可能更适合您要将其保留为数组的位置。
这次,我们通过$objectToArray
将每个数组元素转换为“键/值”对数组,从而删除了id
和fruit_id
。然后,我们根据这些字段的名称"k"
$filter
数组。 $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
将会是
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 }
}}
]